Customize completion-at-point
This post is an overview of text completion in Emacs using completion-at-point
and how you can customize it. I will assume that you already know some Elisp and
are familiar with the basics about
variables
and
hooks.
All examples also assume you use a fairly recent Emacs version (v.26 or later).
Usage
The completion-at-point
command prompts the user with a list of completions
for buffer text. Usually it is bound to M-TAB which is sometimes also
bound to the wrapper command complete-symbol
.
For editable buffers TAB is regularly used for indentation but you can also
combine indentation and completion on TAB by configuring
tab-always-indent
:
;; First try to indent the current line, and if the line
;; was already indented, then try `completion-at-point'
(setq tab-always-indent 'complete)
Adding completions
The completion data gets gathered by running completion-at-point-functions
. As
discussed in a
previous
post you can add functions to hooks buffer locally. This is also what major
modes and language server clients like
eglot or
lsp-mode do to provide their completions.
If the local hook fails to return any candidates the global hook runs. You can
use this to add your own completions as a fallback. The functions of this hook
receive no arguments and should return completion data containing the start and
end positions of the text to be completed, a completion table and optionally
some extra information (see the docstring of completion-at-point-functions
for
more details).
Here is useful example that adds file path completion to the hook:
(autoload 'ffap-file-at-point "ffap")
(defun complete-path-at-point+ ()
"Return completion data for UNIX path at point."
(let ((fn (ffap-file-at-point))
(fap (thing-at-point 'filename)))
(when (and (or fn (equal "/" fap))
(save-excursion
(search-backward fap (line-beginning-position) t)))
(list (match-beginning 0)
(match-end 0)
#'completion-file-name-table :exclusive 'no))))
(add-hook 'completion-at-point-functions
#'complete-path-at-point+
'append)
Afterwards completion-at-point
will offer you file completions when your point
is at a path. This is especially nice with
selectrum which won’t exit file
completions after each path level so you can conveniently navigate to the path
like you would do with find-file
.
The function above looks for a file path at point. If it finds one it returns
the file path boundaries and the completion table. There will be more info about
completion tables in the next section. The no
value for :exclusive
is
added so that other completion-at-point-functions
are continued to be
queried if the completion table should fail.
Completion tables
Completion tables can have many different formats. A list of strings, an alist,
an obarray, a hash table or so called dynamic completion table which is a
function. Those table formats can also be used by completing-read
which is the
function Emacs commands typically use to prompt a user with a list of
completions.
There isn’t much to say about static tables so this section will look at the
more interesting dynamic ones. The last example used the already existing
completion-file-name-table
function. You need a dynamic table for file
completion because the returned file names will change based on the input path.
Dynamic tables are also useful if you want to configure some custom completion
behaviour which the table can control via its metadata. If you call the table
with the metadata
action argument it should return it, see (info "(elisp)
Programmed Completion")
for the full details.
The following example shows how you can add some metadata to completions. Emacs
comes with a little helper function complete-with-action
which you can use to
handle the rest of the API so you only need to care about handling the
metadata
action:
(let* ((alist '(("GNU" . "GNU is not Unix")
("Emacs" . "Eight Megabytes And Constantly Swapping")))
(annotf (lambda (str)
(format " (%s)" (cdr (assoc str alist))))))
(completing-read "Candidates: "
(lambda (str pred action)
(if (eq action 'metadata)
`(metadata
(annotation-function . ,annotf)
(cycle-sort-function . identity)
(display-sort-function . identity))
(complete-with-action action alist str pred)))))
The example disables the default sorting behaviour by using the identity
function. It also adds some annotations to describe the candidates in more
detail. Note that there is also completion-extra-properties
which can also be
used to configure certain completion properties.
There are many more use cases where dynamic tables can be useful see for example this stackexchange answer which constructs a completion table with automatic caching.
Closing words
This post only talked about completion-at-point
but there are many more useful
ways to complete text in Emacs. One external package worth mentioning in the
context of this post is the popular
company package which supports
auto completion.
Company also integrates with completion-at-point-functions
: The variable
company-backends
includes the company-capf
backend which provides you the
completion candidates of completion-at-point-functions
. There are also some
extra properties you can add to your completion-at-point-functions
which are
picked up by company. You can inspect the source of elisp-completion-at-point
or company-capf
itself for more details on this.