Cape
Table of Contents
- What is
cape? - Basic usage: keybinds
- Adding backends to
completion-at-point-functions Cape-company-to-capfandcape-super-capfpcompletenicety- My
completion-at-point-functions - Changelog
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-pointare so calledcompletion-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:
add-to-listprepends elements to a list, that is, place an element at the front of a list[3].add-to-listis almost always preferable topushbecausepushadds an element to a list even if it already in the list, whereasadd-to-listwill not.- Elements earlier in
dolistwill be added to the list before later elements. This means that elements which should be deeper withincompletion-at-point-functionsshould be placed first. (Notice howcape-dabbrevis added aftercape-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 preventdisrupting the addition of other capfs (e.g. merely setting thevariable 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 §
-
- Fixed typo. Added link to u/JDRiverRun’s informative Reddit comment.