A stupid Emacs completion definer.
Paraphrased from these two reddit posts:
In-buffer completion in Emacs is handled via the completion-at-point
command
by default. It gathers the completion data from completion-at-point-functions
(capf
for short).
Company — a popular external package with
similar functionality — uses company-backends
to configure sources for
completion data. Because company is designed to work with capf
you can make
use of it by adding the company-capf
backend to company-backends
.
However, company dumps all of your completion backends into a single global
value of company-backends
. Then it searches through them until one works out
for your current mode. Sometimes this works, sometimes it doesn’t (there is
often a conflict between backends, or there are too many active backends for
snappy feedback). completion-at-point
does the same with capf
. compdef
sets backends as a local variable for that specific mode, so you’re always
pinging the right ones in the right order.
So by running the following:
(compdef
:modes #'org-mode
:company '(company-dabbrev company-capf)
:capf #'pcomplete-completions-at-point)
You set company-backends
and capf
buffer locally for org-mode
buffers (the
global values might still get used, if the buffer local ones don’t make the
completion gathering process stop).
You can now use completion-at-point
command in org-mode
, and
pcomplete-completions-at-point
will be used to get completion candidates. This
works because pcomplete
is already setup correctly for org-mode
, and
pcomplete-completions-at-point
is pcomplete’s compatabilty interface for
completion-at-point
.
If you invoke company-complete
(manually or via the timer) company uses
company-dabbrev
and company-capf
as backends. The latter uses uses whatever
is configured as capf
, which is pcomplete-completions-at-point
in this
example.
All of this will work without any interference from external completion configurations.
We keep reinventing the wheel on how to set local completion backends. Even the
official company
documentation (in this case company-yasnippet
) recommends
ugly workarounds like this:
(add-hook 'js-mode-hook
(lambda ()
(set (make-local-variable 'company-backends)
'((company-dabbrev-code company-yasnippet)))))
compdef
sets local completion backends for both completion-at-point
and
company
simultaneously, with some auto-magic thrown in for convenience
(setting backends for multiple modes at the same time, mixing-and-matching modes
and hooks, etc.). compdef
is intentionally stupid, simply setting the the
relevant variables to what you tell it to, and letting completion-at-point
and
company
handle the finesse of interpreting them. I’ve seen some really
powerful solutions to this problem, but they all seem to assume a certain
approach to configuring completions and are thus usually embedded in a starter
kit like Doom Emacs, Spacemacs… compdef
isn’t that clever. It just works.
… Did I mention the use-package
keywords?
(compdef
:modes '(emacs-lisp-mode lisp-interaction-mode ielm-mode)
:capf '(helm-lisp-completion-or-file-name-at-point
ggtags-completion-at-point
t))
;; Add org keyword completions.
(compdef
:modes #'org-mode
:company '(company-dabbrev company-capf)
:capf #'pcomplete-completions-at-point)
;; infer modes/hooks from package name
(use-package go-mode
:capf go-complete
:company company-go)
;; override
(use-package go-mode
;; mix-and-match hooks and modes freely
:compdef go-mode my-custom-hook my-custom-mode
:capf go-complete
:company company-go)
;; same as above
(use-package go-mode
:compdef (go-mode my-custom-hook my-custom-mode)
:capf go-complete
:company company-go)