Emacs gotchas
Do not nest unlabeled with-restriction
Gotcha! This prints 1, not 100!
(save-excursion (with-restriction 100 500 (with-restriction 300 500 (message "inner restriction starts at %s" (point-min))) (message "outer restriction starts at %s" (point-min))))
Explanation
Unlike many other similar macros, like with-current-buffer
or save-excursion
, the with-restriction
is not nestable. The innermost call undoes all the others.
Basically, if you don't use the :label
argument, you may as well just use narrow-to-region
and widen
and reason accordingly. That'll be clearer code, I think. More verbose, because this macro is effectively a shorthand for
(unwind-protect (progn (narrow-to-region 100 500) ...) (widen))
and it's true, that a shorthand is handy. I might suggest that the shorthand have a different name though, that better communicates that it's not nestable. Like widen-on-exit
. It could signal an error if it was in fact nested!
Once that exists, with-restriction
could make mandatory the :label
argument. Win win.
What should I use instead?
(save-restriction
(narrow-to-region 100 500)
...
)
That's what I thought with-restriction did.
For emacs-devel
Two ideas
with-restriction
, called without:label
, signals an error if called twice, ORwith-restriction
, called with or without:label
, signals an error if operating on an already narrowed buffer that wasn't narrowed bywith-restriction
with:label
gc-cons-threshold appears stuck on a value🔗
Probably your config uses gcmh-mode
(e.g. Doom Emacs has it). Disable it, then you're able to fiddle with gc-cons-threshold.
Although with gcmh-mode you shouldn't need to fiddle. It's a great package. I just needed to fiddle when I was performance-profiling some code and worrying how people on lower settings would experience my code. Because not everyone has gcmh
.
Pull gcmh
into core and no one will have to think about this again.
Truncating the output of float-time in a format string🔗
This can result in confusing messages.
Because float-time or time-to-seconds (and, I infer, any Elisp function at all that returns a number) can represent the returned number in "scientific notation" like this: 2.4863e-05s instead of 0.000024863… then if you do this pattern:
(let ((T (current-time)) (message "Elapsed: %.4s secs" (float-time (time-since T)))
then you'd see the message
Elapsed: 2.48 secs
when you'd really expect this:
Elapsed: 0.00 secs
The solutions for you and me:
- For anything to do with printing time, use
format-time-string
if possible. - In
format
,message
,error
etc, don't assume%s
does what you expect. For numbers, there's always a more specific %-sequence. In this example, write%.2f
instead of%.4s
. - Double-check how you use
number-to-string
,prin1-to-string
.
The solution for Elisp itself to stop confusing future devs is… maybe just not allowing the %s construct in format
?
The funny thing is that the docstring of number-to-string
says "return the decimal representation of NUMBER as a string". I guess I was wrong to think of decimal as not implying scientific notation:
(number-to-string 2.4e-05) => "2.4e-05"
Hide symbol from the global namespace🔗
Here's a way to "hide" a function named SYM, and a way to reveal it later:
(defvar dei--obarray (obarray-make 999331)) (defun dei--hide-function (sym) (fset (obarray-put dei--obarray (symbol-name sym)) (symbol-function sym)) (obarray-remove obarray sym)) (defun dei--reveal-function (sym) (fset sym (symbol-function (obarray-get dei--obarray (symbol-name sym)))))
Can I separate Tab from C-i
?🔗
The manual says it all, at (emacs)Named ASCII Chars. Emphasis mine:
<TAB>, <RET>, <BS>, <LFD>, <ESC>, and <DEL> started out as names for certain ASCII control characters, used so often that they have special keys of their own. For instance, <TAB> was another name for ‘C-i’. Later, users found it convenient to distinguish in Emacs between these keys and the corresponding control characters typed with the <Ctrl> key. Therefore, on most modern terminals, they are no longer the same: <TAB> is different from ‘C-i’.
Emacs can distinguish these two kinds of input if the keyboard does. It treats the special keys as function keys named ‘tab’, ‘return’, ‘backspace’, ‘linefeed’, ‘escape’, and ‘delete’. These function keys translate automatically into the corresponding ASCII characters if they have no bindings of their own. As a result, neither users nor Lisp programs need to pay attention to the distinction unless they care to.
If you do not want to distinguish between (for example) <TAB> and ‘C-i’, make just one binding, for the ASCII character <TAB> (octal code 011). If you do want to distinguish, make one binding for this ASCII character, and another for the function key ‘tab’.
With an ordinary ASCII terminal, there is no way to distinguish between <TAB> and ‘C-i’ (and likewise for other such pairs), because the terminal sends the same character in both cases.
Have you ever wondered if you could unbind C-m
without unbinding RET
(or C-i
without unbinding TAB
)? The answer is no. Even on GUI Emacs, those are the same key; however, you can bind <return>
to whichever command RET
was bound.
The annoying part is that various major and minor modes might include a rebinding for RET
but forget to include one for <return>
, and that's why it doesn't always behave as you'd expect.
It isn't even a solution to hack the default function-key-map
, because what you really want is for <return>
to map to RET
but not to C-m
, but for that to be possible C-m
would have to not be a synonym for RET
.
The Emacs devs did succeed in decoupling C-h
from DEL
, so you could look up how they did that.
A misunderstanding of frames-only-mode🔗
I liked the idea of frames-only-mode, so when I tried out the tiling WM Sway, coming from EXWM, I added it. However… it did the exact opposite of what I imagined.
Here's the thing. There are two ways of achieving a "frame-based" workflow.
- The first way is to advise everything to create or destroy a frame: what
frames-only-mode
does. This enables a proliferation of frames – you could end up with dozens, and most frames in your workspace will be fairly recently created. Then it's up to your WM to keep most of them hidden most of the time. - The second way is to never create or destroy a frame, much like Emacs' internal WM never creates more than 2 windows for you. Instead, you just prepopulate your workspace with the amount of frames you want, then configure
display-buffer-alist
to almost-always calldisplay-buffer-use-some-frame
. (And maybefset
the various siblings ofdisplay-buffer-in-side-window
to the same.) With this, you could still be on the same 2-4 frames after weeks of uptime.
M-next
For people who like the PageUp/PageDown keys:
I rarely use C-v, and as a result, I also rarely used C-M-v (scroll the other window) and had to switch window to scroll it. In retrospect that feels fairly dumb… I never realized that M-<next>
works! (Alt + PageDown) Instant love affair.
Digression: We could use a variant of the built-in tutorial that talks about these keys instead of C-M-v.
You can ignore org-modules
I thought there was something special about the variable org-modules
… but no. It's just a non-programmer's way to require
packages. If you want to load the org-id
module you can just call (use-package org-id)
at some point. No difference! I know that must be unsurprising to some of you, but hopefully a relief to others like me who labored under a different impression.
xinit emacs failure
Reasons:
font-terminus
wasn't installed, so naturally the lineexec emacs -fn "Terminus 9"
led to X closing (without informative errors!)
The concept of a "Symbol"
Disclaimer: This is not quite right, but I wrote this in 2019. I'm letting it stand as a record of what confuses newbies.
In Lisp, a "symbol" means what you might call a kebab, because they look like kebabs (example: elisp-slime-nav-mode-map
looks like a stick with five food pieces on it). It's anything that's not a variable or value.
Examples of values:
- 4
- "some text"
- "a"
- nil
- #<buffer emacs-gotchas.org>
Examples of variables:
- a
- b
- fill-column
- key-translation-map
Examples of function calls returning a value
(current-buffer) ;; returns e.g. #<buffer emacs-gotchas.org> (list a b) ;; returns a list containing values 4 and "some text" (list 'a 'b) ;; returns a list containing symbols a and b (setq a 4) ;; returns 4, not that you often use the return value from setq (setq b "some text") ;; returns "some text"
Examples of symbols:
- 'setq
- 'key-translation-map
- 'list
- 'current-buffer
- 'a
- 'b
- 'fill-column
- 'add-to-string
- '4
- '"a"
But '(add-to-string testvar "a" "b") is not a symbol, because with the apostrophe in front of the paren, it returns a list of the four symbols add-to-string, testvar, "a", and "b".
The apostrophe out in front '(...)
is a shorthand for (quote (...))
which tells the Lisp interpreter not to evaluate (...)
as a function call.
Evaluating (concat testvar "a" "b")
would instead return a single string. If testvar contains "foo", then evaluating that expression gives us "fooab"
.
If you were to instead attempt (list (concat testvar "a" "b"))
it would first call the concat function, so it is the same as just running (list "fooab")
. The "quote" function is a macro just for preventing this from happening, and important enough in Lisp that it gets a shorthand.
The most important gotcha: the above isn't always true, because many macros allow you to get away with not quoting things. Common are setq and use-package, which implicitly quote the first argument, like this:
(setq a "some text")
actually runs the following code
(set 'a "some text")
The word "setq" is short for "set-quote".
To undo key-translations
Suppose you have these settings in place.
(define-key key-translation-map (kbd "[") (kbd "(")) (define-key key-translation-map (kbd "]") (kbd ")"))
To undo them and revert to the normal state of affairs,
(define-key key-translation-map (kbd "[") nil) (define-key key-translation-map (kbd "]") nil)
Rebinding tab, esc and return
Emacs has no less than five Enter keys, each of which do slightly different things (C-o, C-j, C-m, <return> and RET). It also makes a distinction between the keys "<escape>" and "ESC", which are located on the same key, so if you must be sure about inputting the latter, you can press "C-[" as a synonym…
For fun, check what all of these return:
(kbd "<iso-lefttab>") (kbd "S-<iso-lefttab>") (kbd "<tab>") (kbd "S-<tab>") (kbd "TAB") (kbd "S-TAB") (kbd "C-i") ;; same as TAB (kbd "C-S-i") ;; same as S-TAB
In short, in graphical Emacs you're supposed to bind <tab>
(for tab) and <S-iso-lefttab>
(for shift-tab), though keeping in mind it will not have an effect on C-i
, which will continue to be a synonym for TAB
, which is what the tab key inputs on console emacs, and which may or may not have been bound to the same thing <tab>
is bound to.
The Return key emits <return>
in a GUI frame, and RET
in a console frame. C-m always emits RET
.
Emacs contains some hacks to make C-h
do something different from DEL
(backspace), ordinarily on Unix systems they are considered the same key.
Setting a variable from inside a function
Broken function, showing why you need a macro. Doesn't set the variable globally, it stays at its previous value (but it will work on variables defined by defvar).
(require 'subr-x) (defun add-to-string* (string &rest additions) (setq string (concat string (string-join additions))))
This works.
(defmacro add-to-string (string &rest additions) `(setq ,string (concat ,string ,@additions)))
PS: to see what the macro expands to, another gotcha:
;; to see what the macro expands into, do this WITH apostrophe out front: (macroexpand '(add-to-string testvar "a" "b" "c"))
In this example, (add-to-string testvar "a" "b" "c")
actually runs (setq testvar (concat testvar "a" "b" "c"))
– that's what a macro is, it translates code before running it.
Sexpwise editing also operates on symbols when given no sexp
Self-explanatory.
Keyboard macros and Lisp macros are different things
Self-explanatory.