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`.