Cape

Table of Contents

What is cape? §

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:

(use-package cape
: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:

(defun kb/cape-capf-setup-git-commit ()
(let ((result))
(dolist (element '(cape-symbol cape-dabbrev) result)
(add-to-list 'completion-at-point-functions element))))

I then add this to the appropriate hook:

:hook (git-commit-mode . kb/cape-capf-setup-git-commit)

Consequently, when making commits to git, via magit, for instance, completion-at-point-functions looks like this[2]:

'(cape-symbol
cape-dabbrev
tags-completion-at-point-function)

There are a few additional things to keep in mind when adding backends to completion-function-at-point:

  1. add-to-list prepends elements to a list, that is, place an element at the front of a list[3].
  2. 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.
  3. 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 any company backend and convert it into a completion-at-point-function which corfu can use[4]. 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:

(defun kb/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-point completion-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-dabbrev t))

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:

(defun kb/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-functions element)))
))

For other cape transformers, see the appropriate section of the README.

pcomplete nicety §

Finally, I have the following advice to make usage with pcomplete, what eshell uses for completion:

: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-package cape
: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-length 3)
:init
;; Elisp
(defun kb/cape-capf-ignore-keywords-elisp (cand)
"Ignore keywords with forms that begin with \":\" (e.g.
:history)."
(or (not (keywordp cand))
(eq (char-after (car completion-in-region--data)) ?:)))
(defun kb/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-point completion-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
(defun kb/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-point completion-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-dabbrev t))
;; Org
(defun kb/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-functions element)))
))
;; Eshell
(defun kb/cape-capf-setup-eshell ()
(let ((result))
(dolist (element '(pcomplete-completions-at-point cape-file) result)
(add-to-list 'completion-at-point-functions element))
))
;; Git-commit
(defun kb/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-dabbrev cape-symbol) result)
(add-to-list 'completion-at-point-functions element))))
;; LaTeX
(defun kb/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-macros company-auctex-symbols company-auctex-environments))))
result)
(add-to-list 'completion-at-point-functions element))))
;; Sh
(defun kb/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))

Changelog §

Footnotes

  1. See Corfu, Kind-icon, and Corfu-doc to see the basic usage of corfu and several accessory packages.

  2. tags-completion-at-point-function is a default completion-at-point-function

  3. See its docstring to learn how to append to a list

  4. 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.