I will be going over the basic usages of Cape, namely how to add completion functions to completion-at-point-functions and how to use cape's built-in completion utilities (e.g. cape-company-to-capf and cape-capf-buster) to create backends with desired behavior. (Also see u/JDRiverRun’s informative comment about the advantages of using completion-at-point-functions over company.)
Cape is to corfu as company-backends are to company:
Cape provides a bunch of Completion At Point Extensions which can be used in combination with my Corfu completion UI or the default completion UI. The completion backends used by completion-at-point are so called completion-at-point-functions (Capfs). In principle, the Capfs provided by Cape can also be used by Company.
Consequently, cape is only used if you utilize the built-in completion-at-point, which is best complemented by corfu text-completion 1.
You can also see the list of built-in completion-at-point-functions in the README. Several of these completion-at-point-functions are quite niche but others, such as cape-file and cape-symbol have common use cases.
Note: I use straight.el for package management and general.el to set my keybindings. Both of these packages have integration with use-package which come in the form of the :straight and :general keywords, respectively.
Basic usage: keybinds
The most basic way to use cape is to bind its built-in completion-at-point-functions to their own keys. For instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(use-packagecape:general(:prefix"M-c"; Choose a particular completion function"p"'completion-at-point"t"'complete-tag; etags"d"'cape-dabbrev; basically `dabbrev-completion'"f"'cape-file"k"'cape-keyword"s"'cape-symbol"a"'cape-abbrev"i"'cape-ispell"l"'cape-line"w"'cape-dict"\\"'cape-tex"_"'cape-tex"^"'cape-tex"&"'cape-sgml"r"'cape-rfc1345))
Additionally, if having completion-at-point-functions readily available through keybinds is desirable, then one can use cape-interactive-capf to turn an already existing completion-at-point-function into a command (i.e. interactive function) that can be bound.
Adding backends to completion-at-point-functions
However, cape is powerful because these functions can be added to completion-at-point-functions, meaning you can configure when each functions is used and where. The simplest way to accomplish this is by adding backends to completion-at-point-functions in a hook. Here is a simple example:
There are a few additional things to keep in mind when adding backends to completion-function-at-point:
add-to-list prepends elements to a list, that is, place an element at the front of a list3.
add-to-list is almost always preferable to push because push adds an element to a list even if it already in the list, whereas add-to-list will not.
Elements earlier in dolist will be added to the list before later elements. This means that elements which should be deeper within completion-at-point-functions should be placed first. (Notice how cape-dabbrev is added after cape-symbol.)
Cape-company-to-capf and cape-super-capf
I think the killer feature of cape is cape-company-to-capf. This function is able to convert anycompany backend and convert it into a completion-at-point-function which corfu can use4. For this reason, I regard cape as quite an underrated package since it achieves almost full feature parity with company. Here is an example with company-yasnippet:
1
2
3
4
5
6
7
8
(defunkb/cape-capf-setup-lsp()"Replace the default `lsp-completion-at-point' with its
`cape-capf-buster' version. Also add `cape-file' and
`company-yasnippet' backends."(setf(elt(cl-member'lsp-completion-at-pointcompletion-at-point-functions)0)(cape-capf-buster#'lsp-completion-at-point))(add-to-list'completion-at-point-functions(cape-company-to-capf#'company-yasnippet))(add-to-list'completion-at-point-functions#'cape-dabbrevt))
Another useful function is cape-super-capf. This function combines multiple completion-at-point-functions into a single function. Effectively, this means candidates from multiple backends can appear jointly. For instance, one can combine cape-ispell and cape-dabbrev:
Finally, I have the following advice to make usage with pcomplete, what eshell uses for completion:
1
2
3
4
5
6
7
8
9
10
:config;; For pcomplete. For now these two advices are strongly recommended to;; achieve a sane Eshell experience. See;; https://github.com/minad/corfu#completing-with-corfu-in-the-shell-or-eshell;; Silence the pcomplete capf, no errors or messages!(advice-add'pcomplete-completions-at-point:around#'cape-wrap-silent);; Ensure that pcomplete does not write to the buffer and behaves as a pure;; `completion-at-point-function'.(advice-add'pcomplete-completions-at-point:around#'cape-wrap-purify)
My completion-at-point-functions
I have very hesitantly included my WIP code which leverages cape and completion-at-point utilities. I have not described in detail each of the following functions. I have, however, attempted to provide useful docstrings and comments.
I hesitate to publish this code because it was very haphazardly written and highly dependent on my configuration. The reason for this is the order in which the completion-at-point-functions are added: any peculiarities in another’s configuration may lead to undesirable results. As a result, do not directly copy-and-paste this code and expect proper functionality. Rather, I put it here as a reference for what can be done.
Here they are
Warning! This code may produce undesirable effects! Copy at your own risk.
(use-packagecape:hook((emacs-lisp-mode.kb/cape-capf-setup-elisp)(lsp-completion-mode.kb/cape-capf-setup-lsp)(org-mode.kb/cape-capf-setup-org)(eshell-mode.kb/cape-capf-setup-eshell)(git-commit-mode.kb/cape-capf-setup-git-commit)(LaTeX-mode.kb/cape-capf-setup-latex)(sh-mode.kb/cape-capf-setup-sh)):general(:prefix"M-c"; Particular completion function"p"'completion-at-point"t"'complete-tag; etags"d"'cape-dabbrev; or dabbrev-completion"f"'cape-file"k"'cape-keyword"s"'cape-symbol"a"'cape-abbrev"i"'cape-ispell"l"'cape-line"w"'cape-dict"\\"'cape-tex"_"'cape-tex"^"'cape-tex"&"'cape-sgml"r"'cape-rfc1345):custom(cape-dabbrev-min-length3):init;; Elisp(defunkb/cape-capf-ignore-keywords-elisp(cand)"Ignore keywords with forms that begin with \":\" (e.g.
:history)."(or(not(keywordpcand))(eq(char-after(carcompletion-in-region--data))?:)))(defunkb/cape-capf-setup-elisp()"Replace the default `elisp-completion-at-point'completion-at-point-function. Doing it this way will prevent
disrupting the addition of other capfs (e.g. merely setting the
variable entirely, or adding to list).
Additionally, add `cape-file' as early as possible to the list."(setf(elt(cl-member'elisp-completion-at-pointcompletion-at-point-functions)0)#'elisp-completion-at-point)(add-to-list'completion-at-point-functions#'cape-symbol);; I prefer this being early/first in the list(add-to-list'completion-at-point-functions#'cape-file)(require'company-yasnippet)(add-to-list'completion-at-point-functions(cape-company-to-capf#'company-yasnippet)));; LSP(defunkb/cape-capf-setup-lsp()"Replace the default `lsp-completion-at-point' with its
`cape-capf-buster' version. Also add `cape-file' and
`company-yasnippet' backends."(setf(elt(cl-member'lsp-completion-at-pointcompletion-at-point-functions)0)(cape-capf-buster#'lsp-completion-at-point));; TODO 2022-02-28: Maybe use `cape-wrap-predicate' to have candidates;; listed when I want?(add-to-list'completion-at-point-functions(cape-company-to-capf#'company-yasnippet))(add-to-list'completion-at-point-functions#'cape-dabbrevt));; Org(defunkb/cape-capf-setup-org()(require'org-roam)(if(org-roam-file-p)(org-roam--register-completion-functions-h)(let(result)(dolist(element(list(cape-super-capf#'cape-ispell#'cape-dabbrev)(cape-company-to-capf#'company-yasnippet))result)(add-to-list'completion-at-point-functionselement)))));; Eshell(defunkb/cape-capf-setup-eshell()(let((result))(dolist(element'(pcomplete-completions-at-pointcape-file)result)(add-to-list'completion-at-point-functionselement))));; Git-commit(defunkb/cape-capf-setup-git-commit()(general-define-key:keymaps'local:states'insert"<tab>"'completion-at-point); Keybinding for `completion-at-point'(let((result))(dolist(element'(cape-dabbrevcape-symbol)result)(add-to-list'completion-at-point-functionselement))));; LaTeX(defunkb/cape-capf-setup-latex()(require'company-auctex)(let((result))(dolist(element(list;; First add `company-yasnippet'(cape-company-to-capf#'company-yasnippet);; Then add `cape-tex'#'cape-tex;; Then add `company-auctex' in the order it adds its;; backends.(cape-company-to-capf#'company-auctex-bibs)(cape-company-to-capf#'company-auctex-labels)(cape-company-to-capf(apply-partially#'company--multi-backend-adapter'(company-auctex-macroscompany-auctex-symbolscompany-auctex-environments))))result)(add-to-list'completion-at-point-functionselement))));; Sh(defunkb/cape-capf-setup-sh()(require'company-shell)(add-to-list'completion-at-point-functions(cape-company-to-capf#'company-shell))):config;; For pcomplete. For now these two advices are strongly recommended to;; achieve a sane Eshell experience. See;; https://github.com/minad/corfu#completing-with-corfu-in-the-shell-or-eshell;; Silence the pcomplete capf, no errors or messages!(advice-add'pcomplete-completions-at-point:around#'cape-wrap-silent);; Ensure that pcomplete does not write to the buffer and behaves as a pure;; `completion-at-point-function'.(advice-add'pcomplete-completions-at-point:around#'cape-wrap-purify))
tags-completion-at-point-function is a default completion-at-point-function↩︎
See its docstring to learn how to append to a list ↩︎
This feature is currently listed as experimental but, for the most part, the results are as expected. If anything, rare edge cases are the only points of missing functionality. ↩︎
The Text Completion and Minibuffer UI series
This post is just one installation of the Text Completion and Minibuffer UI series. Below is a list of all the posts in this series: