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