Persistent prefix keymaps in Emacs
Or: Further Musings on the Tedium of Long Key-Chords. In the past I’ve covered various bespoke approaches to the problem of repeating long key sequences: Transient, Hydra, repeat-mode (and repeat-mode’s helpers) require progressively less forethought and custom elisp chops to set up. Today we continue our aggressive descent into laziness with the simplest way yet to use any Emacs key prefix as a springboard for one-key access to commands.
A quick glossary
-
keymap
: A “keymap” is an Emacs data structure that maps a collection of keybindings (i.e. keyboard shortcuts) to commands. -
key-chord
: This is a sequence of keys, some or all of which can require modifier keys to be held down. Examples:C-x C-f
(control + x
, followed bycontrol + f
), which runs thefind-file
commandC-c @ C-t
(control + c
,@
, followed bycontrol + t
), which runs the commandoutline-hide-body
.
Needless to say, these can get pretty tedious for repeated invocations, even if you avoid the modifiers by using Vim-style leader keys.
-
prefix-key
: Any “incomplete” part of akey-chord
that will cause Emacs to wait for further keyboard input. In the above examples,C-x
,C-c
andC-c @
are all prefix keys.
repeat-mode
, Hydra, Hercules and Transient all enable variations of the following interaction:
- You (the user) define a special keymap – a set of mappings from key bindings to commands,
- where the bindings are short and mnemonic/ergonomic,
- where the commands are related at some conceptual level in your mind,
- which are displayed in some kind of menu or table,
- and can be invoked in quick succession if required without exiting this menu. Additionally,
- this menu (and associated editor state) can be enabled by using a dedicated keybinding, or
- (
repeat-mode
only) by calling any one of these commands the long way.
The price you pay for this convenience is that you have to create this command group and specify the menu elements manually. This is not a tall order if you only need a few such menus, and the results can be pretty neat. In this situation these packages solve the tedium issue almost incidentally, since easing repetition is not even their primary purpose.
As I’ve written at length before, Emacs’ built-in repeat-mode
straddles the divide between requiring a bespoke menu with associated elisp configuration and a minimally specified approach to repeating commands.
But Emacs is full of prefix maps, and we still don’t have the ability to enter a persistent state – on the fly and without premeditation – where an arbitrary keymap can be “activated” until we are done running or repeating commands in this map.
As it turns out, a 90% solution for this is actually really straightforward, no repeat-mode
needed. With some inspiration from Embark:
(defun repeated-prefix-help-command ()
(interactive)
(when-let* ((keys (this-command-keys-vector))
(prefix (seq-take keys (1- (length keys))))
(orig-keymap (key-binding prefix 'accept-default))
(keymap (copy-keymap orig-keymap))
(exit-func (set-transient-map keymap t #'which-key-abort)))
(define-key keymap [remap keyboard-quit]
(lambda () (interactive) (funcall exit-func)))
(which-key--create-buffer-and-show nil keymap)))
(setq prefix-help-command #'repeated-prefix-help-command)
That’s all of it. Here’s how it works:
- When you’ve typed any prefix key (say
C-c @
) and Emacs is waiting for further input, pressC-h
. - Emacs now remembers whatever you’ve already typed, activates a “repeat state”
or
repeat-mode
, as it were. This is basically modal editing with bespoke modes. and awhich-key
prompt/menu with available keybindings. - You can now continue to call available commands in sequence without having to re-type the annoying prefix key.
- This state persists until you exit it with
C-g
(bound tokeyboard-quit
).
Note:
which-key
is only a visual aid here – this works if you replace it with a different prompter or if you remove it from the code entirely.
Here is a demo of creating a “repeat state” on the fly, in this case for the ever annoying outline-minor-mode
commands on the C-c @
prefix. (The keypresses are shown at the top of the window.)
In principle, repeated-prefix-help-command
functions similar to repeat-mode
, but with one difference. The latter requires that you specify beforehand which prefix keymaps should automatically enter the “repeat state”, and define maps that group together related commands if they don’t exist already. The above method lacks this automaticity, but is more flexible: you can enter this state at any time (and for any keymap) with an additional keypress (here C-h
).
repeated-prefix-help-command
also has an advantage over repeat-mode
: pressing keys that are not bound in the “repeat state” will cause repeat-mode
to end, and you will have to start over with the full, long key sequence to enter it again.
Although this can be remedied with some monkey patching. See repeat-help.
The keymap we activate above stays active until we call keyboard-quit
, so we can freely mix regular Emacs editing commands and the ones we have temporary one-key access to.
We could, of course, use both approaches together. We are approaching peak laziness, but I don’t think we’re there yet!