The *Completions* Buffer Gets a Big Upgrade in Emacs 29
There’s been a lot of talk about how eglot
and tree-sitter
will be distributed
with Emacs 29, but I’ve seen less buzz around the new functionality coming to
the vanilla ∗Completions∗ buffer. Now, I’ve been an ardent vertico +
orderless + marginalia + corfu user since seriously picking up Emacs over the
summer, and when initially looking for options I found Prot’s MCT pretty
alluring. I didn’t choose it since he had already decided to discontine
development given upcoming changes in Emacs 29, and as of writing even he
opted for vertico and corfu.
There is still that tempting, bitter fruit on the horizon though - maximizing
everything I can out of the vanilla Emacs experience. Getting to that mythical
“vanilla extract” that keeps my muscle memory nearly entirely intact between
emacs -Q
and my config (check out “Goals” in my .emacs.d to see the reasoning
behind why I would want this).
Now that treesit.el
, use-package
, and eglot
are all merged into the emacs-29
branch, I finally decided to give our good old friend the ∗Completions∗ buffer
another try, so that you don’t have to.
(Some verbiage below is taken directly from C-h n
(view-emacs-news
))
New ‘visible’ and ‘always’ values for ‘completion-auto-help’ #
There are two new values to control the way the “∗Completions∗” buffer behaves after pressing a ‘TAB’ if completion is not unique.
The (old) default value t
always hides the completion buffer after some
completion is made.
(setq completion-auto-help t)
![](/ox-hugo/auto-help-t.gif)
The value ‘always’ updates or shows the ∗Completions∗ buffer after any attempt
to complete, including the first time we press TAB. Comparing to the one above,
notice that the buffer pops up as soon as I complete ~/.emacs.d/
. Before, I had
to start another completion by typing tra<TAB>
. Also, after completing
transient/
, the buffer once again updates with the contents of that directory.
(setq completion-auto-help 'always)
![](/ox-hugo/auto-help-always.gif)
The value ‘visible’ is like ‘always’, but only updates the completions if they
are already visible. The main difference in this one is that we don’t get the
∗Completions∗ buffer on the first TAB for ~/.emacs.d/
:
(setq completion-auto-help 'visible)
![](/ox-hugo/auto-help-visible.gif)
If your goal is reduction of visual noise because you already know how a chain
of TAB
’s are going to complete, then ‘visible’ seems like a good option.
The ∗Completions∗ buffer can now be automatically selected. #
This was my biggest gripe with ∗Completions∗ and what made it downright unusable for completion-at-point. Here’s what the current behavior looks like with completion in a buffer:
(setq completion-auto-select nil)
![](/ox-hugo/auto-select-nil.gif)
In the minibuffer, we’ve always had M-v
to switch to ∗Completions∗, but there
was no analogue for completion-in-region. Now, in Emacs 29, we can set
completion-auto-select
to one of t
or second-tab
to enable automatic selection
of the “∗Completions∗” buffer
(setq completion-auto-select t)
![](/ox-hugo/auto-select-t.gif)
If the value is ‘second-tab’, then the first TAB
will display “∗Completions∗”,
and the second one will switch to the “∗Completions∗” buffer.
(setq completion-auto-select 'second-tab)
![](/ox-hugo/auto-select-second-tab.gif)
With ‘second-tab’, I can use the “∗Completions∗” buffer a lot like how I would
use corfu
: type a bit, request completion with TAB, examine the list, and keep
typing to narrow the candidates, and request completion again. If I see the
option I like, I just hit TAB a few times to get it.
New commands for navigating completions from the minibuffer. #
M-<up>
andM-<down>
forminibuffer-next-completion
andminibuffer-previous-completion
M-RET
to choose active candidateC-u M-RET
to insert active candidate without exiting minibufferC-x <up>
(minibuffer-complete-history
) is likeminibuffer-complete
but completes on the history items instead of the default completion table.C-x <down>
(minibuffer-complete-defaults
) is likeminibuffer-complete
, but completes on the default items instead of the completion table.
The first two also work for completion-at-point
(in-buffer completion).
![](/ox-hugo/completion-nav-commands.gif)
Some may find the arrow keys an unfortunate choice, though, and bind something more convenient:
;; Up/down when completing in the minibuffer
(define-key minibuffer-local-map (kbd "C-p") #'minibuffer-previous-completion)
(define-key minibuffer-local-map (kbd "C-n") #'minibuffer-next-completion)
;; Up/down when competing in a normal buffer
(define-key completion-in-region-mode-map (kbd "C-p") #'minibuffer-previous-completion)
(define-key completion-in-region-mode-map (kbd "C-n") #'minibuffer-next-completion)
My apologies to Mohamed Suliman, since I was also not able to figure out a fix
for eshell
that permits the use of M-<up>
and M-<down>
with M-RET
. The issue
there, it seems, is that eshell
uses its own pcomplete
instead of
completion-at-point
, which comes from minibuffer.el
. I have, however, had
success simply using TAB
and BACKTAB
with RET
, by setting completion-auto-select
to 'second-tab
, as shown above.
New user option ‘completions-sort’. #
Much like how oantolin’s live-completions gave us a way to sort candidates in ∗Completions∗, we now have a built-in method for specifying the sorting function. I took inspiration from Prot’s MCT documentation here to put candidates I use frequently near the top, followed by the length of their name.
(defun renz/sort-by-alpha-length (elems)
"Sort ELEMS first alphabetically, then by length."
(sort elems (lambda (c1 c2)
(or (string-version-lessp c1 c2)
(< (length c1) (length c2))))))
(defun renz/sort-by-history (elems)
"Sort ELEMS by minibuffer history.
Use `mct-sort-sort-by-alpha-length' if no history is available."
(if-let ((hist (and (not (eq minibuffer-history-variable t))
(symbol-value minibuffer-history-variable))))
(minibuffer--sort-by-position hist elems)
(renz/sort-by-alpha-length elems)))
(defun renz/completion-category ()
"Return completion category."
(when-let ((window (active-minibuffer-window)))
(with-current-buffer (window-buffer window)
(completion-metadata-get
(completion-metadata (buffer-substring-no-properties
(minibuffer-prompt-end)
(max (minibuffer-prompt-end) (point)))
minibuffer-completion-table
minibuffer-completion-predicate)
'category))))
(defun renz/sort-multi-category (elems)
"Sort ELEMS per completion category."
(pcase (renz/completion-category)
('nil elems) ; no sorting
('kill-ring elems)
('project-file (renz/sort-by-alpha-length elems))
(_ (renz/sort-by-history elems))))
(setq completions-sort #'renz/sort-multi-category)
Other Niceties #
completions-max-height
limits the height of the “∗Completions∗” buffercompletions-header-format
is a string to control the heading line to show in the “∗Completions∗” buffer before the list of completions
Do We Stick With Vanilla Extract? #
Now the fun part - let’s tally pros and cons to see if I should abandon everything for the Vanilla behavior:
property | score |
---|---|
Consistent minibuffer + CAP | +1 |
Vanilla GUI + TTY support | +1 |
No marginalia for sole completion | -0.5 |
Extra key press to cycle/complete | -0.5 |
Candidates not buffered until requested | -2 |
Eyes shift focus to another part of screen for CAP | -0.5 |
Total | -1.5 |
In my typical day, I need to have a working TTY and GUI version of Emacs, so when something just works for both, that’s a +1 for me. Corfu does have corfu-terminal, but it’s maintained separately. Also, having a consistent interface for both the minibuffer and completion-at-point shrinks the configuration domain, making it easier to maintain my config over time.
Unfortunately, in the case that there’s only one completion candidate,
marginalia isn’t triggered, so I don’t get to see a key binding or flavor text
alongside the candidate I choose. Vanilla Emacs will remind me about what key
combination I could have used, which I can check any time with C-h e
(the
∗Messages∗ buffer), and I can use C-h f
directly from the minibuffer, so this
only get -0.5. The fact that I need extra key strikes compared to something
like Corfu’s Tab-N-Go is an annoyance, but just requires a bit of muscle memory
change. The real impasse here, though, is that candidates aren’t shown until
requested. I think Prot summed it up best here:
Vertico has official extensions which can make it work exactly like MCT without any of MCT’s drawbacks. These extensions can also expand Vertico’s powers such as by providing granular control over the exact style of presentation for any given completion category (e.g. display Imenu in a separate buffer, show the switch-to-buffer list horizontally in the minibuffer, and present find-file in a vertical list—whatever the user wants).
So will I stick with just ∗Completions∗? No, probably not. But these changes do put the default completion system squarely in the “usable” category, which I’m not sure I could have said before Emacs 29. I will give it an honest chance to see just how far I can push it, much in the spirit of MCT, before switching Vertico and Corfu back on.