Skip to content

Adding custom completions to Company mode in Spacemacs

Inspired by A Beginner's Guide to Extending Emacs, I decided to implement my own in Spacemacs. This post navigates through the odessey of getting the capf to work with company-mode auto-completion in Spacemacs.

Writing a custom capf

My Python code often contains variables with names input_prefix and output_prefix. It would be super convenient to, say, type just input and have it expanded to input_prefix. This is quite simple to implement (as adequately described in aforementioned Beginner's Guide to Extending Emacs).

elisp
;; just define a thing
(define-thing-chars py-path-var "\\(input\\|output\\)")

;; our custom completion func
(defun my-python-prefix-capf ()
  (let* ((bounds (bounds-of-thing-at-point 'py-path-var))
         (start (car bounds))
         (end (cdr bounds)))
    (if start
        (list start end (list "input_prefix" "output_prefix") '()))))

;; and make it available to the completion framework
(add-hook 'completion-at-point-functions 'my-python-prefix-capf)

Evaluate all three of these, and then M-x completion-at-point will complete your text.

Integrating into Spacemacs's Company auto-completion

People on the interwebs say that Company interops with completion functions. This should be simple, this idea couldn't be described more succintly than karthinks:

Add the CAPF to the list of active completion-at-point-functions, then add company-capf to your company backends.

Our code goes into dotspacemacs/user-config

elisp
(defun dotspacemacs/user-config ()
  (define-thing-chars py-path-var "\\(input\\|output\\)")
  (defun my-python-prefix-capf ()
    (let* ((bounds (bounds-of-thing-at-point 'py-path-var))
           (start (car bounds))
           (end (cdr bounds)))
      (if start
          (list start end (list "input_prefix" "output_prefix") '()))))
  (add-hook 'python-mode-hook
            (lambda ()
              (add-hook 'completion-at-point-functions  #'my-python-prefix-capf 0 t))))

and we configure the auto-completion layer as described in Spacemacs docs

elisp
   dotspacemacs-configuration-layers
   '(
     (auto-completion
      :variables
      spacemacs-default-company-backends '(company-capf))
     (python
      :variables
      python-backend 'anaconda))

But it doesn't work auto-magically. Apparently, the custom capf needs to come first in the list for both backends to work together.

elisp
  (add-hook 'python-mode-hook
            (lambda ()
              (add-hook 'completion-at-point-functions  #'my-python-prefix-capf 0 t)
              (add-hook 'company-mode-hook (lambda () (setq-local company-backends '((company-capf company-anaconda)))))))

With this, you can use completions from both the custom capf and the anaconda python backend. You can view your company config with M-x company-diag

Using more in-built completion funcs

We can combine with in-built company backends, like company-files. Company does recommend some conventions on grouping backends, but does not automatically switches to the next group when one does not provide any completions. This can be done manually via M-x company-other-backend. I prefer to put all of them in a single group. This will have it's own disadvantages but I'll cross that bridge when I get there, I guess?

elisp
  (add-hook 'python-mode-hook
            (lambda ()
              (add-hook 'completion-at-point-functions  #'my-python-prefix-capf 0 t)
              (add-hook 'company-mode-hook (lambda () (setq-local company-backends '((company-capf company-anaconda company-files)))))))