Completion system
… vertico, consult, posframe, orderless, marginalia, projectile … corfu, cape, eglot, kind-icon … Ohhh my!
History
For many years there were two different school of thoughts when it comes to completion system used in Emacs:
-
the one that comes bundled with Emacs with limited functionality:
-
the one that is built around custom APIs implemented in different frameworks:
Until last year! when new kid of the block arrived, composable packages that are fully compliant with default Emacs' completion system:
The power of composability
- What have Linux (or Unix), Ethereum and Lisp (or functional programming languages in general) in common?
- Composability! in Linux you can pipe multiple commands to get the result you are looking for, in Ethereum you can compose multiple smart contracts to achieve different functionality (e.g. DeFi) and in functional programming languages you call multiple functions to transform the data.
1. Incremental completion and selection
Let's have a close look at the screenshot below, this is my main Go To command that I use to switch to different files/projects/buffers/etc.
All that completion functionality that you see is a mix of half dozen packages that work together:
- Posframe - to display the overlay frame
- Vertico - for vertical completion minibuffer
- Consult - enhances default Emacs commands with Vertico functionality
- Orderless - provides filtering and sorting
- Marginalia - for nice attributes and description next to each candidate
- Projectile - for project management
2. Completion at point (or auto-completion)
Again, a bunch of packages are used:
Let's get a bit technical
Emacs's completion system is beautiful, powerful, yet very simple and at very low-level it is based on two main functions that take a list of candidates and display an interactive selection child frame.
- completing-read function for minibuffer completion
- completion-in-region function for at-point (in buffer) completion
1. completing-read function
(completing-read
"Complete a foo: "
'(("foobar1" 1) ("barfoo" 2) ("foobaz" 3) ("foobar2" 4))
nil t "fo")
In the example above we have a static list but it can also be dynamic list created at runtime (e.g. a list of file names).
(completing-read
"Complete a file: "
(completion-table-dynamic
(lambda (_)
(directory-files ".")))
nil t "ema")
Then, on top of it, we have higher level specific commands (e.g. read-file-name or read-color) and so on.
(read-color "Color: ")
2. completion-in-region function
The little sister of the above completing-read works the same way but instead of activating the minibuffer it displays a child frame at point.
(completion-in-region
(point)
(point)
'(("foobar1" 1) ("barfoo" 2) ("foobaz" 3) ("foobar2" 4)))
or dynamically generating the list of candidates
(completion-in-region
(point)
(point)
(completion-table-dynamic
(lambda (_)
(directory-files "."))))
Then we have a higher level command completion-at-point where we can create a hook function (of CAPFs - Completion At Point Function) and hook it into completion-at-point-functions variable.
(defun capf ()
(let ((beg (point)) (end (point)))
(list beg
end
(completion-table-dynamic
(lambda (_)
(directory-files "."))))))
(setq-local completion-at-point-functions '(capf))
;; eval the above sexps then call 'completion-at-point command to trigger auto-completion
This is all folks, we can hook into Emacs' completion system functions and have all flexibility in the world to built any kind of completion functionality we want.
Take away: you got the idea, any time you need to select an item from a list, we can leverage the completing-read or completion-in-region functions.