Default emacs completions are good, I swear!
UPDATE : The bug described below with regards to
minibuffer-choose-completion has been fixed in emacs 29.
As part of my use-the-least-amount-of-external-packages-so-you-can-learn-emacs-lisp mode of operation, I’m using the builtin completion system. That means no vertico, ivy, helm, selectrum, etc. I’ve been using it for a while and it’s perfect for me. I like to have a completions buffer instead of all the completion candidates appearing in the minibuffer, and the fact that it’s builtin means that it works with almost everything. I’ll get back to that later.
Emacs 29 introduced some functionality that made the task of making the default completion system usable much easier than before. You can read the NEWS file about it.
(setq completions-format 'one-column) (setq completions-header-format nil) (setq completions-max-height 20) (setq completion-auto-select nil)
That’s really it. Well, almost. What we have here is a single column,
*Completions* buffer that is never larger than
20 lines and isn’t auto selected when it’s opened up.
What I’m trying to do here is replicate some of the functionality of
mct.el, my favourite completion framework. Its development was
discontinued because of these new features introduced in emacs 29, and
so I like to believe that
mct, or at least its spirit, lives on
through my own dotemacs config.
Another feature of
mct.el was the ability to cycle through and
choose completions from the completions buffer while keeping focus on
the minibuffer or buffer if its completion at point. Emacs 29 has this
by default with the
M-<up> keybindings, but I’d like
to change those to
C-n, like our beloved
(define-key minibuffer-mode-map (kbd "C-n") 'minibuffer-next-completion) (define-key minibuffer-mode-map (kbd "C-p") 'minibuffer-previous-completion)
And for completion in region/at point as well:
(define-key completion-in-region-mode-map (kbd "C-n") 'minibuffer-next-completion) (define-key completion-in-region-mode-map (kbd "C-p") 'minibuffer-previous-completion)
M-RET chooses a completion, but this doesn’t always work as of Oct
2022 . E.g in eshell,
cd <TAB> opens the completions buffer and you
can select a completion e.g a directory with
inserting it with
M-RET gives the message: “Text is read-only”. I
think this is because emacs is trying to insert the completion into
the minibuffer, and I submitted a bug report about it.
But… wait a moment now, isn’t this emacs? Surely we can figure out why this is happening and fix it, right? Isn’t that the whole appeal? All you emacs folk talk about is its customizability, so go on then… customize it!
Well you’re in luck. I’ve done just that. Let’s go through it:
M-RET is bound to
minibuffer-choose-completion, and here’s its
(defun minibuffer-choose-completion (&optional no-exit no-quit) "Run `choose-completion' from the minibuffer in its completions window. With prefix argument NO-EXIT, insert the completion at point to the minibuffer, but don't exit the minibuffer. When the prefix argument is not provided, then whether to exit the minibuffer depends on the value of `completion-no-auto-exit'. If NO-QUIT is non-nil, insert the completion at point to the minibuffer, but don't quit the completions window." (interactive "P") (with-minibuffer-completions-window (let ((completion-use-base-affixes t)) (choose-completion nil no-exit no-quit))))
Here’s what I’ve rebound it to:
(defun my/minibuffer-choose-completion (&optional no-exit no-quit) (interactive "P") (with-minibuffer-completions-window (let ((completion-use-base-affixes nil)) (choose-completion nil no-exit no-quit)))) (define-key completion-in-region-mode-map (kbd "M-RET") 'my/minibuffer-choose-completion)
Essentially the problem was the fact that the original function set
completion-use-base-affixes variable to
t before calling
choose-completion. The variable is defined as:
(defvar completion-use-base-affixes nil "Non-nil means to restore original prefix and suffix in the minibuffer.")
The prefix and suffix in this context is text preceding and following
the place where the completion should be inserted. When non-nil,
choose-completion is trying to
restore the original prefix and suffix in the minibuffer
however the minibuffer (when doing completion in region) is read only,
so trying to insert anything returns the “Text is read-only” error. By
jove, I think we’ve cracked it! My overriden
my/minibuffer-choose-completion sets the variable to nil before
choose-completion, so we’re all good now. Completion in
region works as expected!
I know that the better practice is to advise this function instead of redefining it, but the problem is the inner let, and I only want to call this modified version when I am doing completion in region. Not sure how to do that with advices. I said I’m learning emacs lisp! Give me a break…
And that’s my completion system. Add these two external packages:
(require 'marginalia) (marginalia-mode t) (require 'orderless) (setq completion-styles '(orderless))
and you’re good to go bro. You’re good to go…
Try it out, and let me know what you think! And if anyone knows how to
implement this by advising
minibuffer-choose-completion, please let me know!