summaryrefslogtreecommitdiff
path: root/personal/itches/emacs/narrow-lighter.md
blob: e5987b80a74c0dcc0c14612330c6aa3123f9d181 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# Make " Narrow" lighter customizable

The " Narrow" string comes from `src/xdisp.c:decode_mode_spec`:

``` c
case 'n':
  if (BUF_BEGV (b) > BUF_BEG (b) || BUF_ZV (b) < BUF_Z (b))
    return " Narrow";
```

This is probably just a matter of returning the contents of a Lisp
variable instead of this constant string.

TODO:

1. get the string value of a variable in C
2. define a customizable string variable
3. write a news entry
4. write a patch
5. extra credits: display string properties

## Get the string value of a variable in C

`decode_mode_spec` has some relevant snippets:

- Given a `Lisp_Object obj`, `SSDATA(obj)` gives the string value as a
  `char*`.

- How to get a variable's `Lisp_Object`?
    - `BVAR` works for buffer-local variables
    - `V${lispname//-/_}`

## Define a customizable string variable

### Defining variables visible to C code

The C macro `DEFVAR_LISP(string-name, field-name)` does the following:

    define a static `Lisp_Objfwd` variable v
    get the address of globals._f##field-name &f

    defvar_lisp(v, string-name, &f)

As explained in the comments above `DEFVAR_LISP`, `globals` is a
global variable defined in `globals.h`, which is "auto-generated by
make-docfile" and exposes fields, `#define`s and `Lisp_Object`s for
every global variable.

make-docfile (`lib-src/make-docfile.c`) takes C files as input and
searches all occurences of `^ +DEFSYM[ \t(]`, `^ +DEFVAR_[ILB]` or
`^DEFU`, analyses what comes after and generates appropriate
definitions for `globals.h`.

`defvar_lisp` allocates a symbol using `Fmake_symbol`.

### Making it customizable

`lisp/cus-start.el` defines customizable properties of symbols defined
by C code.

AFAICT, there is no need to assign the default value right after
defining the variable with `DEFVAR_LISP`: e.g. `shell-file-name` is
`DEFVAR_LISP`ed in `src/callproc.c` and its default value is set in…
Mmm.  Not in `cus-start.el`.  There is this snippet in
`callproc.c:init_callproc`:

  ``` c
sh = getenv ("SHELL");
Vshell_file_name = build_string (sh ? sh : "/bin/sh");
  ```

But when starting with `SHELL=rofl emacs -Q`, Custom says that the
value "has been changed outside Customize".  Changed from what to
what?

`cus-start.el` may contain a hint:

``` elisp
;; Elements of this list have the form:
;; …
;; REST is a set of :KEYWORD VALUE pairs.  Accepted :KEYWORDs are:
;; :standard - standard value for SYMBOL (else use current value)
;; …
```

Except that nope, this does not work.  Giving `:standard " Narrow"`
and looking at the variable in Custom yields

    narrow-lighter: nil
        [State]: CHANGED outside Customize. (mismatch)

A better example might be `overlay-arrow-string`, whose default value
is set right after `DEFVAR_LISP` by calling `build_pure_c_string`.

Why `build_pure_c_string` and not `build_string`?  From "(elisp) Pure
Storage":

> Emacs Lisp uses two kinds of storage for user-created Lisp objects:
> “normal storage” and “pure storage”.  Normal storage is where all
> the new data created during an Emacs session are kept (see Garbage
> Collection).  Pure storage is used for certain data in the preloaded
> standard Lisp files—data that should never change during actual use
> of Emacs.
>
> Pure storage is allocated only while ‘temacs’ is loading the
> standard preloaded Lisp libraries.  In the file ‘emacs’, it is
> marked as read-only (on operating systems that permit this), so that
> the memory space can be shared by all the Emacs jobs running on the
> machine at once.

"(elisp) Building Emacs" explains that "temacs" is the minimal Elisp
interpreter built by compiling all C files in `src/`; temacs then
loads Elisp sources and creates the "emacs" executable by dumping its
current state into a file.

## Debug stuff

### Unicode characters represented as octal sequences

Trying to customize the new variable to any string with non-ASCII
characters fails: they show up as sequences of backslash-octal codes.
For some reason they show up fine in the Help and Custom buffers.

Things to investigate:

1. Should the `Lisp_Object` be created with something other than
   `build_pure_c_string`? 🙅
2. What does the code calling `decode_mode_spec` do with the returned
   string? **🎉**
3. (Does `SSDATA` make some transformation before returning the
   string? 🤷)
4. (Should a specialized Custom setter be defined? 🤷)

#### Should the `Lisp_Object` be created with something other than    `build_pure_c_string`?

Maybe this would work?

``` c
Vnarrow_lighter = make_multibyte_string(" Narrow", strlen(" Narrow"),
                                        strlen(" Narrow)");
```

That looks too ugly though, let's try something else.

Maybe `STRING_SET_MULTIBYTE(Vnarrow_lighter)` would help?

*compiles and tries*

… Nope, it does not.

#### What does the code calling `decode_mode_spec` do with the returned string?

``` c
spec = decode_mode_spec (it->w, c, field, &string);
multibyte = STRINGP (string) && STRING_MULTIBYTE (string);
```

*slowly turns around*

*finds `string` standing right there with a blank stare*

Gah!  How long have you been there?

``` c
/* Return a string for the output of a mode line %-spec for window W,
   generated by character C.  […]  Return a Lisp string in
   *STRING if the resulting string is taken from that Lisp string.
   […] */
static const char *
decode_mode_spec (struct window *w, register int c, int field_width,
                  Lisp_Object *string)
{
  Lisp_Object obj;
  /* … */
  obj = Qnil;
  *string = Qnil;

  switch (c)
    {
    /* … */
    }

  if (STRINGP (obj))
    {
      *string = obj;
      return SSDATA (obj);
    }
  else
    return "";
}
```

Alright then:

``` c
case 'n':
  if (BUF_BEGV (b) > BUF_BEG (b) || BUF_ZV (b) < BUF_Z (b))
      obj = Vnarrow_lighter;
  break;
```

### Why do string properties not show up?

🤷

## Extra credit

Maybe it would be simpler to have the narrowing lighter work like the
" Compiling" lighter (cf. `compilation-in-progress` variable), i.e. adding an entry to `minor-mode-alist`.