memory-leaks

Still reachable: lots of words in many pages.
git clone https://git.kevinlegouguec.net/memory-leaks
Log | Files | Refs | README | LICENSE

narrow-lighter.md (6060B)


      1 # Make " Narrow" lighter customizable
      2 
      3 The " Narrow" string comes from `src/xdisp.c:decode_mode_spec`:
      4 
      5 ``` c
      6 case 'n':
      7   if (BUF_BEGV (b) > BUF_BEG (b) || BUF_ZV (b) < BUF_Z (b))
      8     return " Narrow";
      9 ```
     10 
     11 This is probably just a matter of returning the contents of a Lisp
     12 variable instead of this constant string.
     13 
     14 TODO:
     15 
     16 1. get the string value of a variable in C
     17 2. define a customizable string variable
     18 3. write a news entry
     19 4. write a patch
     20 5. extra credits: display string properties
     21 
     22 ## Get the string value of a variable in C
     23 
     24 `decode_mode_spec` has some relevant snippets:
     25 
     26 - Given a `Lisp_Object obj`, `SSDATA(obj)` gives the string value as a
     27   `char*`.
     28 
     29 - How to get a variable's `Lisp_Object`?
     30     - `BVAR` works for buffer-local variables
     31     - `V${lispname//-/_}`
     32 
     33 ## Define a customizable string variable
     34 
     35 ### Defining variables visible to C code
     36 
     37 The C macro `DEFVAR_LISP(string-name, field-name)` does the following:
     38 
     39     define a static `Lisp_Objfwd` variable v
     40     get the address of globals._f##field-name &f
     41 
     42     defvar_lisp(v, string-name, &f)
     43 
     44 As explained in the comments above `DEFVAR_LISP`, `globals` is a
     45 global variable defined in `globals.h`, which is "auto-generated by
     46 make-docfile" and exposes fields, `#define`s and `Lisp_Object`s for
     47 every global variable.
     48 
     49 make-docfile (`lib-src/make-docfile.c`) takes C files as input and
     50 searches all occurences of `^ +DEFSYM[ \t(]`, `^ +DEFVAR_[ILB]` or
     51 `^DEFU`, analyses what comes after and generates appropriate
     52 definitions for `globals.h`.
     53 
     54 `defvar_lisp` allocates a symbol using `Fmake_symbol`.
     55 
     56 ### Making it customizable
     57 
     58 `lisp/cus-start.el` defines customizable properties of symbols defined
     59 by C code.
     60 
     61 AFAICT, there is no need to assign the default value right after
     62 defining the variable with `DEFVAR_LISP`: e.g. `shell-file-name` is
     63 `DEFVAR_LISP`ed in `src/callproc.c` and its default value is set in…
     64 Mmm.  Not in `cus-start.el`.  There is this snippet in
     65 `callproc.c:init_callproc`:
     66 
     67   ``` c
     68 sh = getenv ("SHELL");
     69 Vshell_file_name = build_string (sh ? sh : "/bin/sh");
     70   ```
     71 
     72 But when starting with `SHELL=rofl emacs -Q`, Custom says that the
     73 value "has been changed outside Customize".  Changed from what to
     74 what?
     75 
     76 `cus-start.el` may contain a hint:
     77 
     78 ``` elisp
     79 ;; Elements of this list have the form:
     80 ;; …
     81 ;; REST is a set of :KEYWORD VALUE pairs.  Accepted :KEYWORDs are:
     82 ;; :standard - standard value for SYMBOL (else use current value)
     83 ;; …
     84 ```
     85 
     86 Except that nope, this does not work.  Giving `:standard " Narrow"`
     87 and looking at the variable in Custom yields
     88 
     89     narrow-lighter: nil
     90         [State]: CHANGED outside Customize. (mismatch)
     91 
     92 A better example might be `overlay-arrow-string`, whose default value
     93 is set right after `DEFVAR_LISP` by calling `build_pure_c_string`.
     94 
     95 Why `build_pure_c_string` and not `build_string`?  From "(elisp) Pure
     96 Storage":
     97 
     98 > Emacs Lisp uses two kinds of storage for user-created Lisp objects:
     99 > “normal storage” and “pure storage”.  Normal storage is where all
    100 > the new data created during an Emacs session are kept (see Garbage
    101 > Collection).  Pure storage is used for certain data in the preloaded
    102 > standard Lisp files—data that should never change during actual use
    103 > of Emacs.
    104 >
    105 > Pure storage is allocated only while ‘temacs’ is loading the
    106 > standard preloaded Lisp libraries.  In the file ‘emacs’, it is
    107 > marked as read-only (on operating systems that permit this), so that
    108 > the memory space can be shared by all the Emacs jobs running on the
    109 > machine at once.
    110 
    111 "(elisp) Building Emacs" explains that "temacs" is the minimal Elisp
    112 interpreter built by compiling all C files in `src/`; temacs then
    113 loads Elisp sources and creates the "emacs" executable by dumping its
    114 current state into a file.
    115 
    116 ## Debug stuff
    117 
    118 ### Unicode characters represented as octal sequences
    119 
    120 Trying to customize the new variable to any string with non-ASCII
    121 characters fails: they show up as sequences of backslash-octal codes.
    122 For some reason they show up fine in the Help and Custom buffers.
    123 
    124 Things to investigate:
    125 
    126 1. Should the `Lisp_Object` be created with something other than
    127    `build_pure_c_string`? 🙅
    128 2. What does the code calling `decode_mode_spec` do with the returned
    129    string? **🎉**
    130 3. (Does `SSDATA` make some transformation before returning the
    131    string? 🤷)
    132 4. (Should a specialized Custom setter be defined? 🤷)
    133 
    134 #### Should the `Lisp_Object` be created with something other than    `build_pure_c_string`?
    135 
    136 Maybe this would work?
    137 
    138 ``` c
    139 Vnarrow_lighter = make_multibyte_string(" Narrow", strlen(" Narrow"),
    140                                         strlen(" Narrow)");
    141 ```
    142 
    143 That looks too ugly though, let's try something else.
    144 
    145 Maybe `STRING_SET_MULTIBYTE(Vnarrow_lighter)` would help?
    146 
    147 *compiles and tries*
    148 
    149 … Nope, it does not.
    150 
    151 #### What does the code calling `decode_mode_spec` do with the returned string?
    152 
    153 ``` c
    154 spec = decode_mode_spec (it->w, c, field, &string);
    155 multibyte = STRINGP (string) && STRING_MULTIBYTE (string);
    156 ```
    157 
    158 *slowly turns around*
    159 
    160 *finds `string` standing right there with a blank stare*
    161 
    162 Gah!  How long have you been there?
    163 
    164 ``` c
    165 /* Return a string for the output of a mode line %-spec for window W,
    166    generated by character C.  […]  Return a Lisp string in
    167    *STRING if the resulting string is taken from that Lisp string.
    168    […] */
    169 static const char *
    170 decode_mode_spec (struct window *w, register int c, int field_width,
    171                   Lisp_Object *string)
    172 {
    173   Lisp_Object obj;
    174   /* … */
    175   obj = Qnil;
    176   *string = Qnil;
    177 
    178   switch (c)
    179     {
    180     /* … */
    181     }
    182 
    183   if (STRINGP (obj))
    184     {
    185       *string = obj;
    186       return SSDATA (obj);
    187     }
    188   else
    189     return "";
    190 }
    191 ```
    192 
    193 Alright then:
    194 
    195 ``` c
    196 case 'n':
    197   if (BUF_BEGV (b) > BUF_BEG (b) || BUF_ZV (b) < BUF_Z (b))
    198       obj = Vnarrow_lighter;
    199   break;
    200 ```
    201 
    202 ### Why do string properties not show up?
    203 
    204 🤷
    205 
    206 ## Extra credit
    207 
    208 Maybe it would be simpler to have the narrowing lighter work like the
    209 " Compiling" lighter (cf. `compilation-in-progress` variable), i.e. adding an entry to `minor-mode-alist`.