Emacs gotchas

Emacs gotchas

#emacs

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

  1. with-restriction, called without :label, signals an error if called twice, OR
  2. with-restriction, called with or without :label, signals an error if operating on an already narrowed buffer that wasn't narrowed by with-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:

  1. For anything to do with printing time, use format-time-string if possible.
  2. 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.
  3. 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.

  1. 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.
  2. 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 call display-buffer-use-some-frame. (And maybe fset the various siblings of display-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:

  1. font-terminus wasn't installed, so naturally the line exec 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.

What links here

Created (6 years ago)