Skip to content
Daniel Mendler edited this page May 17, 2023 · 96 revisions

Corfu video overview

See this video for a nice overview of Corfu and a bit about Cape, as well as a comparison with other tools.

Configuring Corfu for LSP clients

Configuring Corfu for LSP-mode

Lsp-mode is an Emacs package which provides an LSP client for Emacs. It provides completions for a wide array of languages and types. It does this by consulting external LSP server processes. LSP servers look at the text in the region and send a list of suggested completions.

Example configuration with flex

The main thing you need to tune is the completion style. LSP servers do not know about or respect the “completion style” in Emacs (they have their own “styles”). For example, with text foo they may provide completions foobar, frodo, tofoo, atafondillo, etc. So here the local Emacs style you configure matters. If you have setup a “prefix-only” Emacs style like basic, all those completions the LSP server provided will be filtered; only foobar in the list above will be shown. If you want to use a builtin completion style it is recommended to either use substring or flex. The substring style is slightly limited in that it matches substrings only, so you will get the candidates foobar and tofoo. The flex style matches all of the candidates.

(use-package lsp-mode
  :custom
  (lsp-completion-provider :none) ;; we use Corfu!
  :init
  (defun my/lsp-mode-setup-completion ()
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(flex))) ;; Configure flex
  :hook
  (lsp-completion-mode . my/lsp-mode-setup-completion))

Basic example configuration with orderless

An example of a much more flexible and powerful Emacs completion style is Orderless, which can find text anywhere in the string. You can even use spaces in the buffer to add additional regex or normal searches. And the beauty is with Corfu and Orderless, this filtering and selection happens instantly in Emacs, without having to re-contact the LSP server! This can be much faster for slow servers.

(use-package orderless
  :init
  ;; Tune the global completion style settings to your liking!
  ;; This affects the minibuffer and non-lsp completion at point.
  (setq completion-styles '(orderless partial-completion basic)
        completion-category-defaults nil
        completion-category-overrides nil))

(use-package lsp-mode
  :custom
  (lsp-completion-provider :none) ;; we use Corfu!
  :init
  (defun my/lsp-mode-setup-completion ()
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(orderless))) ;; Configure orderless
  :hook
  (lsp-completion-mode . my/lsp-mode-setup-completion))

Advanced example configuration with orderless

To make sure every single completion candidate comes through, we use Orderless’ “dispatcher” capability to setup the orderless-flex style for its 1st search term (the string you are completing). You can skip this if you prefer to filter a bit what the LSP servers has provided when Corfu first pops up (I mean, did I really type foo if I wanted atafondillo?).

;; Optional cape package.
;; See the Cape README for more tweaks!
(use-package cape)

(use-package orderless
  :init
  ;; Tune the global completion style settings to your liking!
  ;; This affects the minibuffer and non-lsp completion at point.
  (setq completion-styles '(orderless partial-completion basic)
        completion-category-defaults nil
        completion-category-overrides nil))

(use-package lsp-mode
  :custom
  (lsp-completion-provider :none) ;; we use Corfu!

  :init
  (defun my/orderless-dispatch-flex-first (_pattern index _total)
    (and (eq index 0) 'orderless-flex))

  (defun my/lsp-mode-setup-completion ()
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(orderless)))

  ;; Optionally configure the first word as flex filtered.
  (add-hook 'orderless-style-dispatchers #'my/orderless-dispatch-flex-first nil 'local)

  ;; Optionally configure the cape-capf-buster.
  (setq-local completion-at-point-functions (list (cape-capf-buster #'lsp-completion-at-point)))

  :hook
  (lsp-completion-mode . my/lsp-mode-setup-completion))

The one additional feature we have used here is Cape’s cape-capf-buster. This also isn’t entirely necessary, but it gives some nicer behavior when you alter the original text during completion (e.g. while completing get, you delete back to ge).

See below for examples of grouping results from other completion sources using Cape.

Configuring Corfu for Eglot

Eglot is an Emacs packages which provide completions for a wide array of languages and types. It does this by consulting external LSP language server processes. LSP servers look at the text in the region and send a list of suggested completions.

Continuously update the candidates

The LSP server assumes that the candidates are retrieved on every change to the buffer and we can rely on the LSP server to do the heavy lifting and provide a continuously update list of completions. The problem is that LSP servers are unaware of Emacs completion-styles, therefore the candidates from the language server are only post-filtered by the completion style.

Corfu retrieves the candidate completion table once at the beginning of a completion session and doesn’t reload it while the word is being typed. This is advantageous for most completion-at-point-functions since it opens up caching opportunities. Eglot completion function doesn’t refresh the completion table itself. This is problematic, as language servers often only provide limited list of completions. Therefore, when using auto completion and typing slowly, possible completion candidates may be missing, because the initial list was missing the intended string.

This behavior can be changed with the cache buster from Cape, which ensures that the completion table is refreshed such that the candidates are always obtained again from the server.

(advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)

Retrieving the candidates on every key press can be seen as a disadvantage, since it decreases performance. This matters in particular if your language server returns many candidates (or even a complete set of candidates) right away on the first invocation of completion. Depending on your language server you may or may not want the cape-wrap-buster. See also the next section about requesting more candidates from the Lsp server by default.

Corfu supports the Orderless completion style. As soon as you press M-SPC, a space is inserted and Orderless filtering starts. The candidates are not updated again from the server. Instead the existing candidates are only post-filtered with Orderless. This feature works always, with or without cape-wrap-buster. Note that this feature is Orderless-specific. See the next section.

Filter list of all possible completions with completion-style like Orderless

Instead of relying on the LSP server to continuously filter the list of possible completions, we can use a completion style like Orderless to filter that list.

Eglot adjusts the completion styles by default. It sets the style for the eglot category to flex in completion-category-defaults. The flex style is not used to do any “flex” or “fuzzy” matching, Eglot uses it mainly to provide highlighting. In LSP interactions the completion candidate filtering logic is determined by the LSP server outside of Emacs’s control. If you want to use Eglot in combination with Orderless or another completion style you should override this setting.

;; Option 1: Specify explicitly to use Orderless for Eglot
(setq completion-category-overrides '((eglot (styles orderless))))

;; Option 2: Undo the Eglot modification of completion-category-defaults
(with-eval-after-load 'eglot
   (setq completion-category-defaults nil))

;; Enable cache busting, depending on if your server returns
;; sufficiently many candidates in the first place.
(advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)

Depending on if Eglot returns all or sufficiently many candidates in the first place you may or may not need the cape-wrap-buster. Servers usually limit the completion candidates to a small number, to avoid sending large amounts of data over the JSONRPC protocol. If the server does not guarantee returning all valid completions matching the prefix, you will miss potentially valid completions. In case of doubt, use cape-wrap-buster.

As alternative you can increase the number of returned candidates as described in the next section. This may lead to higher GC pressure.

Requesting more completions from your LSP server with eglot-workspace-configuration

If you use the Orderless completion style and your LSP server doesn’t give all completions from Data.Vector. or java.util.list. that exist, you may wish to request more completions from the server to filter them with Orderless.

WARNING: Your LSP server likely limits completions due to performance concerns and raising the limit could cause performance issues. Keep this in mind for at least a few weeks after changing this setting.

You’ll have to consult your LSP server for it’s configuration details, but Elisp alists mapping to JSON can be added to eglot-workspace-configuration. Here is an example for haskell-language-server to request 200 completion candidates:

(setq-default eglot-workspace-configuration
      '((haskell (maxCompletions . 200))))

Making a Cape Super Capf for Eglot

When we’re in eglot-mode we want more than the eglot-completion-at-point function provides. In the following example, we use Cape’s cape-super-capf function to compose our own completion at point using eglot’s eglot-completion-at-point, tempel’s tempel-expand and Cape’s cape-file.

(defun my/eglot-capf ()
  (setq-local completion-at-point-functions
              (list (cape-super-capf
                     #'eglot-completion-at-point
                     #'tempel-expand
                     #'cape-file))))

(add-hook 'eglot-managed-mode-hook #'my/eglot-capf)

Configuring Corfu for EXWM

If you use Corfu in the minibuffer below an X11 window, the Corfu child-frame is displayed beneath the X11 window, rendering candidates invisible. In order to keep all candidates visible in this scenario use the following code:

(advice-add #'corfu--make-frame :around
            (defun +corfu--make-frame-a (oldfun &rest args)
              (cl-letf (((symbol-function #'frame-parent)
                         (lambda (frame)
                           (or (frame-parameter frame 'parent-frame)
                               exwm-workspace--current))))
                (apply oldfun args))
              (when exwm--connection
                (set-frame-parameter corfu--frame 'parent-frame nil))))

(advice-add #'corfu--popup-redirect-focus :override
            (defun +corfu--popup-redirect-focus-a ()
              (redirect-frame-focus corfu--frame
                                    (or (frame-parent corfu--frame)
                                        exwm-workspace--current))))

If you additionally use corfu-doc, then the same situation above also applies to child frames created by corfu-doc. To fix analogously, use the following code. Note that corfu-doc has been deprecated in favor of the corfu-popupinfo extension, which is part of Corfu itself.

(advice-add #'corfu-doc--make-frame :around
            (defun +corfu-doc--make-frame-a (oldfun &rest args)
              (cl-letf (((symbol-function #'frame-parent)
                         (lambda (frame)
                           (or (frame-parameter frame 'parent-frame)
                               exwm-workspace--current))))
                (apply oldfun args))
              (when exwm--connection
                (set-frame-parameter corfu-doc--frame 'parent-frame nil))))

(advice-add #'corfu-doc--redirect-focus :override
            (defun +corfu-doc--redirect-focus ()
              (redirect-frame-focus corfu-doc--frame
                                    (or (frame-parent corfu-doc--frame)
                                        exwm-workspace--current))))

Transfer completion to the minibuffer

If you want to transfer the Corfu popup completion to the minibuffer during the process you can use the function corfu-move-to-minibuffer which is defined in terms of consult-completion-in-region. Maybe this is sometimes useful?

(defun corfu-move-to-minibuffer ()
  (interactive)
  (let ((completion-extra-properties corfu--extra)
        completion-cycle-threshold completion-cycling)
    (apply #'consult-completion-in-region completion-in-region--data)))
(define-key corfu-map "\M-m" #'corfu-move-to-minibuffer)

Using Cape to tweak and combine Capfs

Corfu works with traditional Emacs completion backends called completion at point functions (Capfs). Cape provides some additional simple backends as Capfs, which can be used directly. It also allows you to tweak them, and even composite them. This examples shows how to set up emacs-lisp mode with a “super-capf” composed of two different sources: the normal elisp-completion-at-point completions and dabbrev candidates from cape-dabbrev. This is great while hacking on emacs-lisp files, when the symbols aren’t yet known to Emacs, for example. Corfu will pop-up with both symbols known to emacs, and (as a backup) additional matching symbols it finds in the file. I put a minimum length of 5 characters to avoid small word pollution.

It also adds cape-file to the list of completion at point functions, to try as a “back-up”, e.g. in strings and comments. It also shows how to use another cool cape feature to add a custom predicate. The predicate is a function which gets the candidate, and return t if it should be kept. This one drops keywords from the emacs-lisp candidate list, unless the completion text starts with a `:’. There are lots of ways to composite together and tweak CAPFs, to supplement your mode’s completions.

(defun my/ignore-elisp-keywords (cand)
  (or (not (keywordp cand))
      (eq (char-after (car completion-in-region--data)) ?:)))

(defun my/setup-elisp ()
  (setq-local completion-at-point-functions
              `(,(cape-super-capf
                  (cape-capf-predicate
                   #'elisp-completion-at-point
                   #'my/ignore-elisp-keywords)
                  #'cape-dabbrev)
                cape-file)
              cape-dabbrev-min-length 5))
(add-hook 'emacs-lisp-mode-hook #'my/setup-elisp)

Here’s an example of cape working to combine emacs-lisp and dabbrev completion candidates, with corfu, kind-icons, and orderless completion style in-buffer:

Auto trigger commands

If Corfu is not triggering completion with corfu-auto it can be the case that the command was not called with self-insert-command or one of the other commands registered in the list corfu-auto-commands. You can fix this by adding the commands you are missing to the list.

(add-to-list 'corfu-auto-commands 'some-special-insert-command)

Using together with prescient.el

prescient.el is a sorting and filtering package. An integration package is available for Corfu: corfu-prescient (available on MELPA). The mode provided by the package configures the filtering settings that Corfu uses (completion-styles and family) buffer locally and configures Corfu’s sorting globally.

;; After installing the package
(corfu-prescient-mode 1)

TAB-and-Go completion

In TAB-and-Go style, if a candidate is selected, then further input commits that candidate unless it is the first one. In order that the first candidate is also committed on further input, add this to your configuration:

(dolist (c (list (cons "SPC" " ")
                 (cons "." ".")
                 (cons "," ",")
                 (cons ":" ":")
                 (cons ")" ")")
                 (cons "}" "}")
                 (cons "]" "]")))
  (define-key corfu-map (kbd (car c)) `(lambda ()
                                         (interactive)
                                         (corfu-insert)
                                         (insert ,(cdr c)))))

This list can be modified to accommodate anyone’s needs. Note that the inclusion of space assumes that the completion style is “basic”. Also note that the inclusion of “.” disables regex search within the candidate list. A simpler version only including space is this:

(define-key corfu-map (kbd "SPC") (lambda ()
                                    (interactive)
                                    (corfu-insert)
                                    (insert " ")))
Clone this wiki locally