The case against which-key: a polemic

4 months ago 4

The case against which-key: a polemic

Published: <2025-07-04 Fri>. Comments on Mastodon.

It’s Friday and jcs over at Irreal has started a tradition of being polemical on what he calls Red Meat Fridays. I’m joining in on the fun today to explain why I don’t like which-key ―but going somewhat against the red meat spirit I don’t want to merely criticize: I’ll offer up a suggestion of something better at the end, too.

What which-key-mode does is popup in the echo area a list of key bindings and commands found under the current prefix. Here’s what it looks like after I pressed C-x C-k waited a second for the popup to appear:

which-key-example.png

Since the commands under C-x C-k didn’t all fit on a single page, which-key splits them into multiple pages and you can advance through them with C-h n and C-h p. Reading through the which-key popup isa great way to learn what is bound under a prefix. People love this and which-key is now bundled with Emacs.

So why don’t I like it? There’s a minor reason and a major reason. The minor reason is simply that it is automatic by default. The default configuration has the which-key popup appear 1 second after typing a prefix. Some people love this because it pops up precisely when they don’t know or can’t remember what key to press next. I dislike almost all forms of automatically appearing UI finding them jittery and distracting; I almost always prefer to summon UI explicitly with a key press or maybe mouse action. I understand this only a personal preference, and it is an easily fixed one at that! There is a simple way to configure which-key to wait for you to type C-h to appear:

(setq which-key-show-early-on-C-h t which-key-idle-delay 1e6 ; 11 days which-key-idle-secondary-delay 0.05)

So that was just a small matter of personal preference, what’s the major reason at the heart of this polemic then? It is this: I believe we should use computers for automation, to assist us in finding information. Clearly which-key-mode has a different philosophy: it merely displays all the key bindings under a prefix and then lets humans do all the work! A human has to read the key bindings, possibly paging through a couple of pages them. It feels ridiculously to be sitting at a computer, a very powerful information retrieval machine, equipped with a high bandwidth input device (namely: a keyboard) and to read a bunch of boring text as if it were printed on a piece of paper (or two or sometimes even three pieces of paper!)

Now, sometimes you do want to read all the key bindings under a prefix, to find out or to refresh your memory about what’s there. In those cases I have no problem with which-key’s approach, but I find that most often I’m looking for a specific key binding and I remember a portion of the command name or maybe a bit of the binding (some Emacs key bindings are long enough that I can remember them partially but not completely!) When I’m looking for a specific binding, I really want the computer to do the searching for me, that’s what the damn thing is for!

And now I’ll tell you about the improved which-key I promised at the start. It has all the paper-like functionality of which-key, but additionally will help you search instead of patiently waiting while you to do all the work yourself. This replacement is a function called embark-prefix-help-command that comes with my Embark package. To use it simply (setq prefix-help-command #'embark-prefix-help-command). The prefix-help-command variable controls what happens when you press C-h after typing a prefix. What embark-prefix-help-command does is prompt you in the minibuffer, with completion, for a key-binding−command pair under the current prefix. You can view your options or type some input in the minibuffer to narrow the listing. It is best paired with a minibuffer completion UI that automatically displays the current completion candidates in real time as you type. (I discussed several of these in another blog post: My quest for completion.) I recommend Vertico, because it has an option to display completions in a grid, which makes embark-prefix-help-command look a lot like which-key:

(add-to-list 'vertico-multiform-categories '(embark-keybinding grid))

Here’s what that looks like, for the same C-x C-k prefix as above:

embark-prefix-help-command-all.png

You could use this just like which-key, reading all the commands and key bindings; Vertico even lets you use C-v and M-v to page through the results (which I find slightly more comfortable than the default in which-key of C-h n and C-h p). But you can also use the minibuffer input to narrow the options. This works best if you have a completion style that lets you type a substring from anywhere inside a candidate to match it. I recommend the Orderless completion style, or I wouldn’t have written it, but the built-in substring completion style works too. Say I was trying to remember a key binding for some command that deals with keyboard macro counters ―see (info "(emacs) Keyboard Macro Counter") if you haven’t heard of these. Then you just type “cou” and get this:

embark-prefix-help-command-narrowed.png

You can also narrow by a part of the key binding rather than the command name. For example, I’m writing this blog post in org-mode and have inline images displayed, but when you add a new one that one is not displayed automatically. I could toggle the display of inline images off and on again with C-c C-x C-v, but I remembered there was a key binding for that, under the same C-c C-x prefix that involved v with some other modifiers, so I typed C-c C-x C-h and then “-v” and saw that C-M-v was the rest of the binding I was looking for.

This UI is very flexible: besides the narrowing via completion, you can do lots of other things. You can can navigate with C-n, C-p or the arrow keys among the completion candidates, press RET on a command to run it, and you have the full power of Embark: say you have embark-act bound to C-., then you can press C-. h when a candidate is displayed to show its documentation, or C-. d to go to its definition, or C-. w to copy the command name to the kill-ring. Or say you want to explore some subset of the commands more in depth, you can narrow to the subset you want and run embark-export (with C-. E) to get a nice apropos-mode buffer with a summary of those functions, like this one:

embark-prefix-help-command-apropos.png

Or say, you wanted a little table of those keyboard macro counter commands and their key bindings. Instead of embark-export you’d use embark-collect to get this buffer:

embark-prefix-help-command-collect.png

From there I copied the first two columns as a rectangle, plopped them into this org-mode buffer, added a few pipe symbols (using string-rectangle), and a header, to get this table:

Key Command
C-a kmacro-add-counter
TAB kmacro-insert-counter
C-c kmacro-set-counter
C-q > kmacro-quit-counter-greater
C-q < kmacro-quit-counter-less
C-q = kmacro-quit-counter-equal
C-r s kmacro-reg-save-counter
C-r l kmacro-reg-load-counter
C-r a > kmacro-reg-add-counter-greater
C-r a < kmacro-reg-add-counter-less
C-r a = kmacro-reg-add-counter-equal

I hope this taste of the flexibility of embark-prefix-help-command whets your appetite for more.

Emacs keymaps can have helpful and even dynamic prompts

Published: <2025-07-03 Thu>. Comments on Mastodon.

Nowadays M-o is unbound in Emacs by default, but it used to be used for changing fonts in enriched text mode buffers. Enriched text is what I consider an obscure format that Emacs supports pretty well for some reason. I think rms had hopes of basing some Emacs word processing functionality on enriched text, but I’m a little hazy on the history ―which I’d love more knowledgeable people to educate me on. Enriched text (not to be confused with Microsoft’s Rich Text Format) is the MIME text/enriched type defined by internet RFC 1563. In Emacs its supported by the built-in enriched library ―see (finder-commentary "enriched").

Now that M-o is unbound by default, I recommend binding it to other-window and turning off repeat-mode for that command with (put 'other-window 'repeat-map nil) so you can switch to another window and type a word starting with “o” without trouble ―therwise you wind up with an o-less word in the next window! In fact, I already used that binding back when M-o was used for enriched text fonts.

But enough rambling, the reason I’m talking about this is that back when M-o was bound to facemenu-keymap, pressing it would show this message in the echo area:

Set face: default, bold, italic, l = bold-italic, underline, Other…

That’s right: M-o was bound not to a command, but to a keymap, and pressing it would show a message in the echo area!

One day redditor sebhoagie, asked how to display a dynamic message when a keymap is active, and I had that curiosity about M-o filed away in my brain. I didn’t know what exactly M-o was bound to at that point in time, but I did now it was a keymap, because I had attempted to use C-h c M-o only to have describe-key-briefly wait for more input. So I set out to find out how that keymap did its magic. I ran emacs -q, found out what M-o was bound to by default (recall that I was using it for other-window), and here’s the result (this is mostly what I wrote on reddit back then, with the code modernized a bit):

You can add a prompt for the prefix keymap when you create it, and also prompts for the individual keys bound in the keymap! For example, the following code defines a keymap for package-related operations:

(defvar-keymap pkg-ops-map :name "Package" "h" '("describe" . describe-package) "r" '("reinstall" . package-reinstall) "a" '("autoremove" . package-autoremove) "d" '("delete" . package-delete) "i" '("install" . package-install) "l" '("list" . list-packages)) (keymap-global-set "C-c p" pkg-ops-map)

(Here I’m using the recent family of commands, macros and functions for dealing with keymaps, keymap-set and friends from keymap-el, which only accept the key description strings that kbd uses; I recommend these above the older define-key and friends.)

Now, as soon as you press C-c p the following message appears in the echo area:

Packages: list, install, delete, autoremove, reinstall, h = describe

Notice that Emacs cleverly noticed that for the key h you gave the prompt “describe”, which does not start with h, so it actually prints h = describe. The other prompts do start with the letter they are bound to, so Emacs just lists the prompt you gave.

Of course, if you don’t give a prompt for a binding, it’s not mentioned in the overall prompt for the keymap, but the key is still bound. For example, if you replace '("reinstall" . package-reinstall) with a simple 'package-reinstall in the above code, then the prompt will change to:

Packages: list, install, delete, autoremove, h = describe

but r will still be bound to reinstall-package.

Now, sebhoagie said that they had an application in mind for which they wanted the prompt to change dynamically, so this trick wasn’t good enough. And I remember thinking: “Emacs Lisp is super imperative, I bet keymaps are mutable!”. I was right and as a proof of concept I cooked up this little example:

(defvar count 0) (defvar-keymap counter-map :name "Dynamic counter!" "i" '("increment" . inc-counter) "r" '("reset" . reset-counter)) (defun update-counter-map-prompt () (rplaca (last counter-map) (format "Counter is at %d!" count))) (defun inc-counter () (interactive) (setq count (1+ count)) (update-counter-map-prompt)) (defun reset-counter () (interactive) (setq count 0) (update-counter-map-prompt)) (keymap-global-set "<f5>" counter-map)

After running that code, try <f5> i a few times to watch the counter increment or <f5> r to reset it back to zero.

If you are a keymap description you might think you are set for life and just kick back and relax, but you are as rplaca-ble as anyone else.

My quest for completion

Published:<2025-07-01 Tue>. Comments on Mastodon.

This is the entry I would have written for the “Take Two”-themed Emacs carnival if it had taken me less than a month to decide to participate. It’s about all the myriad minibuffer completion UIs I’ve been through as an Emacs user, and is quite long because I’ve tried so many!

Just to make sure we’re on the same page: Emacs commands prompt for user input in the minibuffer and offer completion, which means some sort of assistance typing sensible inputs. There are many possible user interfaces for this in Emacs and I’ve tried many but all of them (By the way, this is a constant in my Emacs usage: I try many but not all of the options, since there are always too many. For example, of the built-in ways to list buffers I’ve used switch-to-buffer, ibuffer, electric-buffer-list and several third party options ―I currently use consult-buffer from the Consult package― but I’ve never tried the built-in bs-show, for example. Similarly, of the built-ins I’ve only used GNUS to read email and RSS feeds, but never tried Rmail for email or NewsTicker for RSS; let alone trying all third party options ―I did use mu4e for email for a bit and elfeed for RSS feeds!)

Just so this doesn’t take forever to write, I will write it all from memory, and let people correct me on details I get wrong ―it’s only fair since I do that to other people all the freaking time (it’s a wonder I have any friends). This list attempts to be in chronological order of my usage, but I’m sure I’m getting some of this slightly wrong (and fortunately no-one is likely to be able to call me out about errors in the ordering of my personal usage of these UIs).

Default completion

In my early days as an Emacs user I simply used the default completion UI. This UI doesn’t not show the completion candidates all the time. If all the candidates have the next bit of text in common, pressing TAB inserts it into the minibuffer (people call this TAB completion). I’ve always found this funny since it makes zero progress towards choosing a single completion candidate: it only inserts as much text as will not narrow down things at all! But people like it for some reason, I guess as visual feedback that what you thought the completion candidates had in common they really do have in common. (To be fair you can configure completion-cycle-threshold to have TAB cycle among candidates when there aren’t many left.)

Pressing TAB a second time will pop up the *Completions* buffer, which shows a list of all completion candidates. From there you can click on a candidates, navigate to it and press RET, or just type some more and press TAB again to get an updated candidate list.

Don’t knock the default completion UI. It is functional, powerful and perfectly usable out of the box. But most people want to see how the available candidates change automatically as you type. The other UIs I tried all have that feature in common; they also all have a notion of the “current completion candidate”, and there is a way to select that current candidate without finishing typing it ―usually but not always this done simply by pressing RET.

Ido

Ido also comes with Emacs and is pretty funky. For one thing it doesn’t take over all minibuffer completion services, it only provides special versions of certain commands, mainly to open files and switch buffers. Those commands also enable some additional key bindings while you are using the commands: while opening a file you can press a key to delete a file, or while switching buffers you can press a key to kill a buffer. This sort of thing is called “acting on a completion candidate”, and boy do I like it ―more about this later. Ido uses fuzzy completion, where characters only have to appear in order in a completion candidate, not necessarily consecutively; for example epnf matches eww-open-file.

If you like Ido but want to use it for all minibuffer completion there are some options: there’s the ido-completing-read+ package and the built-in fido-mode. I don’t particularly like fuzzy matching since it feels inefficient to me, there are always two many matches for my taste.

Helm

Helm is a comprehensive package that not only takes over all minibuffer completion duties but comes with many commands that take advantage of Helm’s additional features. It’s big and brash and opinionated. I used it for many months and was quite happy with it. I particularly liked that the Helm commands came with many actions you could perform on the completion candidates. I didn’t much like its default aesthetics or its long load time (although since you only incur that once per session it doesn’t really matter). One thing I didn’t like much is that somehow it didn’t seem to blend in very well with the rest of my Emacs experience. For example, only commands written specially to use Helm had actions for the completion candidates, and the actions had to be implemented in a particular way, you couldn’t use any old Emacs command as an action. But all in all Helm is great, an impressive piece of software that sprouted its own mini-ecosystem of related packages.

Ivy

After Helm I used Ivy and its companion Swiper and Counsel packages for a while. It felt pretty similar to using Helm to me, except I liked the default look of it better (which is not to say you couldn’t easily configure the visual aspects of Helm). It also had a notion of actions, but similarly to Helm, you needed to write them specially for each command. I think it was less batteries-included than Helm, for example I have a vague recollection of counsel-find-file, its substitute for find-file, not coming with actions to rename or copy files, which I wrote in my own configuration. It’s then that it started to bother me that I couldn’t simply say “I want the Emacs commands rename-file and copy-file as actions”, I was forced to write little wrappers for them. Like Helm, I think the Ivy/Swiper/Counsel family is great software and was a happy user. I did however, as I did with Helm, that it reinvented the wheel too much and that something like it would be possible that took more advantage of existing Emacs APIs and functions.

Icomplete

This is another built-in option, which I think I only started using after having used Helm and Ivy. I used it for a quite a while and still think it is perfectly workable. One thing I like about it is that, like the default completion UI, it only concerns itself with displaying the completion candidates and leaves the important matter of which candidates are considered to match the minibuffer input to the current completion styles. There is no formal notion in Emacs of a “well-behaved” completion UI, but in my head certainly such a UI should limit itself to showing you the candidates and leave the user to configure completion styles separately. This what I dislike about fido-mode: it ignores your completion style configuration and makes you use the flex completion style instead (you can change this but it requires being sneaky, not simply setting completion-styles).

One difference between Icomplete and Helm or Ivy, is that it displays the completion candidates in a compact horizontal list like Ido, instead of one per line, like Helm or Ivy. When I was an Icomplete user, I even wrote a package called icomplete-vertical that would configure Icomplete to display the candidates vertically, one per line. Nowadays there is also a built-in package of the same name which I did not write, nor does it use my code. I remember thinking the built-in package had a bug mine lacked, triggered when you switched from horizontal to vertical during a minibuffer completion session, but I couldn’t reproduce it now so either they fixed it, or I couldn’t remember exactly what the bug was, or my memory is playing tricks on me.

live-completions

The mention of icomplete-vertical above is the start of an embarrassing parade of completion UIs I wrote myself, for myself and which I don’t think more than a handful people ever used. What marred all of my feeble attempts, other than icomplete-vertical which is just some configuration code on top of icomplete, was a distinct lack of speed. The idea of these incremental, automatic completion UIs is to show you how the candidate list changes in real time, but most of mine struggled to do this fast enough.

My live-completions package had a very simple idea: pop up the *Completions* buffer that the default UI uses and just update it after every key press. It let you format the completions in either a grid or a single column, back before the default UI had the single column view.

I think I used this for quite a while though it wasn’t very good. I believe that by the time I wrote this, I already had an initial version of Embark that took care of acting on completion candidates solving all of the complaints I had about actions in Ido, Helm or Ivy: Embark lets you use any Emacs command directly as an action, there is no need to write wrappers over existing commands; it endows every single command that has a minibuffer prompt with actions on its candidates, commands do not have to be written specially with Embark in mind to acquire actions.

grille

Another bad completion UI I wrote. Hey, at least it’s small. I don’t think I used this for very long, maybe a couple of weeks. It’s called grille because it displays completions in a grid. I have nothing else to say about it.

embark-completions

For a while Embark included its own completions UI! This is obviously a bad idea, which I only did because it was easy, since Embark had grown to have almost all the components necessary for this. It was slow, but other than that I found it surprisingly good. It was certainly featureful: it displayed the completions either one per line or in a grid, the grid optionally with zebra stripes to guide the eye. I stubbornly kept using it until I dropped it for Vertico and finally removed it from Embark.

Vertico

The excellent Vertico package is what I use now as a completion UI. The author, Daniel Mendler, and I have long worked together on a suite of packages that provide a full completion experience for Emacs. I wanted to switch from embark-completions to Vertico but held out until Daniel added a grid display to Vertico. In retrospect this was silly on my part, but I believe my stubbornness might have helped motivate Daniel to add the grid feature. Of this suite of packages, Daniel wrote Vertico, Corfu, Cape; I wrote Embark and Orderless (the latter of which we now co-maintain); and we wrote Marginalia together after noticing we were both writing something like it.

Verticoa highly flexible completion UI, that can display completions one per line or in a grid, in the minibuffer or in a dedicated buffer. Orderlessa highly configurable completion style, whose main feature is matching space-separated bits of the input in any order against the completion candidates; so op eww matches eww-open-file. Embarklets you use any Emacs command as an action on any minibuffer completion candidate or on a thing at point in a non-mini buffer; it also comes with an extensive default configuration assigning convenient keybindings to the most commonly used actions (but you can always M-x to use whatever command you want as an action!) Marginaliaprovides extra information about completion candidates of common types, most completion UIs that display candidates as one per line will shows this extra information to the right of the candidate. Corfuthis is a completion UI for completion-at-point, which I haven’t talked about here at all ―it is the type of completion that you get when writing code in a buffer, for example. Again here I stubbornly held out a long time using my own contraption (consult-completion-in-region, which I contributed to consult). Capea suite of completion-at-point functions; again I stubbornly held out using an embark-based substitute for this for a long time ―I’m starting to notice an unflattering pattern.

Selectrum and MCT

I already finished listing the completion UIs that I actually used for a period of time, but there are a couple of others I tried and probably some I did not try (maybe snails, though I’m not exactly sure what it is, since I’ve only every briefly skimmed its README). I tried Selectrum once and, to my discredit, only complained about some minor issues with it on reddit. At least I’m focused thematically in my complaints: I thought it wasn’t Emacsy enough, that it disrespected some Emacs variables it could easily respect. Well, that, and there something I didn’t like about its completing-read-multiple experience (I think it was that you couldn’t easily see what you had already selected). I never used Prot’s MCT package extensively, but I would call it similar in idea to my live-completions package, and would hope Prot does not disagree with this characterization. It somehow seemed smoother than my live-completions, probably because Prot had more patience tweaking the experience than I did.

Welcome to M-x apropos Emacs

Published:<2025-07-01 Tue>.

Recently Christian Tietze called for Emacs blog posts with the topic “Take Two”, meaning things that took two or more tries to get right. He also said:

Don’t have a blog, yet?

Well, just start one already! :)

It’s the future of the internet!

I mulled over this the whole month and finally decided to start a blog about Emacs just in time to miss that first round of the Emacs carnival! (I’m likely to miss deadlines like that again in the future.)

Now, about twenty years ago, when blogs did actually seem like the future of the internet, I had one on a hosted service. I think I may have started on Blogspot, but then moved to Wordpress.com. It was a personal blog about whatever random topic I wanted to write about. I did not write very frequently on that blog, and am unlikely to write often here either, although maybe constraining the topic to Emacs might inspire me more than having no constraints did.

I was already an Emacs user back then, but I wasn’t as gung-ho about it as now, and back then I meekly accepted Wordpress making all sorts of decisions for me, which seems cowardly today; so I definitely want a more Emacsy way to write this new blog. I figure all a blog needs is a publicly accessible web page and a publicly accessible RSS feed. An important optional component is a system for comments. So, to do the easiest thing possible, I will write my blog in a single Org file (for now, I’ll manually paginate if it turns out I do keep it up!), use ox-rss to create an RSS feed, and for comments I’ll just use Mastodon.

Since I threw this together in about ten minutes, I’m sure there will be bugs. I probably don’t have permalinks correct at all.

Read Entire Article