À la Mode - corfu, cape, and completion-preview

# Foreword

Over the last few years there has been an ever greater push towards making smaller Emacs packages that are more tightly integrated with the core Emacs ecosystem. In large part this is due to the massive ongoing efforts of the Emacs maintainers and the community whose enhancements make these packages easier to write and integrate without needing to reinvent the wheel.

Some of the changes that have happened include:

Where it makes sense I’ve made some small efforts to migrate to these newer leaner, packages suites.

So far I’ve completed my move from the ‘ivy’ suite to the ‘vertico’ suite. This was relatively easy but was certainly not painless. My largest issue with this migration was when trying to replace my usage of swiper with consult-line. Even with the some helpful patches I fell short of a complete migration. Thankfully not everything needs to be black or white, I can use ‘swiper’ where it makes sense and the ‘vertico’ suite the rest of the time.

# corfu, cape, and completion-preview-mode

Given the friction I experienced with my migration to the ‘vertico’ suite I’ve been a bit apprehensive about tackling the rest of these migrations . However here I stand now, without a job and with more free time than ever, thinking about my Emacs configuration.

Sure enough I decided to tackle a migration from the excellent ‘company’ package to a combination of ‘corfu’, and ‘cape’. Note that I wasn’t having any issues with ‘company’; I just wanted to see what new ideas had appeared in the in-buffer completion ecosystem since had I started using ‘company’.

When I first started using Emacs 10 years ago ‘auto-complete’ was the elder statesman of completion at point. At the time the new kid on the block was ‘company’. I wanted to be hip so started using ‘company’ and was generally happy with it. I tweaked it a bit to my liking with ‘company-box’, and ’esh-autosuggestion’ but generally it worked well enough that I didn’t think about it much over that time.

## corfu

Setting up ‘corfu’ to my liking was relatively easy:

(use-package corfu
  :custom
  ;; Make the popup appear quicker
  (corfu-popupinfo-delay '(0.5 . 0.5))
  ;; Always have the same width
  (corfu-min-width 80)
  (corfu-max-width corfu-min-width)
  (corfu-count 14)
  (corfu-scroll-margin 4)
  ;; Have Corfu wrap around when going up
  (corfu-cycle t)
  (corfu-preselect-first t)
  :bind (:map corfu-map
              ;; Match `corfu-quick-complete' keybinding to `avy-goto-line'
              ("s-j" . corfu-quick-complete))
  :init
  ;; Enable Corfu
  (global-corfu-mode t)
  ;; Enable Corfu history mode to act like `prescient'
  (corfu-history-mode t)
  ;; Allow Corfu to show help text next to suggested completion
  (corfu-popupinfo-mode t))

## cape

Setting up new backends via ‘cape’ was equally easy:

(use-package cape
  :demand t
  :init
  ;; Add `completion-at-point-functions', used by `completion-at-point'.
  (setq-default completion-at-point-functions
                (append (default-value 'completion-at-point-functions)
                        (list #'cape-dabbrev #'cape-file #'cape-abbrev))))

### cape + hydra

On top of a simple ‘cape’ configuration I realized that sometimes I wanted to call a specific completion function. For that I added a simple hydra which I bind globally to M-i.

(defhydra my/cape
  (:color blue :hint nil)
  "
^Complete^
^--------^
_i_ Completion at Point
_d_abbrev
_f_ile
_h_istory
_p_complete
_e_moji
"
  ("i" completion-at-point)
  ("p" (lambda () (interactive) (let ((completion-at-point-functions '(pcomplete-completions-at-point t))) (completion-at-point))))
  ("d" cape-dabbrev)
  ("f" cape-file)
  ("h" cape-history)
  ("e" cape-emoji))

### cape + eshell

I’ve always found eshell’s completion more difficult than other mode’s this is probably because of its usage of pcomplete and how it overrides any globally defined completion-at-point-functions. In a sort of arms race I “fix” this by once again overriding completion-at-point-functions. If I’d like to see the pcomplete completions I now explicitly demand them via my hydra, M-i p.

(use-package em-cmpl
  :ensure nil
  :config
  (bind-key "C-M-i" nil eshell-cmpl-mode-map)
  (defun my/em-cmpl-mode-hook ()
    (setq completion-at-point-functions
          (list #'cape-history #'cape-file #'cape-dabbrev)))
  (add-hook 'eshell-cmpl-mode-hook #my/em-cmpl-mode-hook))

## completion-preview-mode

The last bit that needed replacing was my usage of ’esh-autosuggestion’. Essentially this package would display the top completion candidate and let you select it.

Thankfully the excellent builtin completion-preview package was added in Emacs 30.1 by Eshel Yaron which gives me just this.

(use-package completion-preview
  :ensure nil
  :bind (:map completion-preview-active-mode-map
              ("M-f" . #'completion-preview-insert-word)
              ("C-M-f" . #'completion-preview-insert-sexp))
  :custom
  (completion-preview-minimum-symbol-length 2)
  :init
  (global-completion-preview-mode))

### completion-preview-mode enhancements

One last thing I’d like to touch on are some of the new additions to completion-preview. Since using other packages I grew used to partially completing the “top” candidate with M-f. With careful code review and help from Eshel I’ve added two new commands to completion-preview: completion-preview-insert-word completion-preview-insert-sexp. If you use the latest version of Emacs master you should have access to these functions as well. I hope they help.

# Future work

Maybe going forward I’ll migrate from ‘projectile’ to the builtin project.el or from ‘straight’ to ’elpaca’ but as they say “If it ain’t broke, don’t fix it, unless it sounds like a good learning experience”.