Skip to main content
Kristoffer Balintona
  1. Archive/

Corfu, Kind-icon, and Corfu-doc

··2041 words·10 mins

Synopsis #

I will be going over my personal Emacs' text completion (e.g. company-mode and its accessories) configuration, which includes the following packages:

  • Corfu by Minad — simpler alternative to company-mode
  • Kind-icon by jdtsmith (u/JDRiverRun)— add icons to corfu popup (analog to company-box-icons)
  • Corfu-doc by Galeo — add documentation popup for corfu candidates (analog to company-box-doc)

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.

Corfu #

What is corfu? How does it differ from company? #

Figure 1: The default corfu popup window. The GIF’s framerate is low, which makes corfu appear less performant here than in actuality.

Corfu is a text completion (e.g. completion-at-point, company-mode) package. In my opinion, since its release, corfu has not gotten the attention that it deserves. I prefer it to company for the following reasons:

  1. It is easier to configure since corfu’s internals rely on the built-in completion-at-point. This also means that, unlike company,1
    • any built-in invocation of completion-at-point or completion-in-region leverages corfu,
    • and any completion-style (e.g. orderless) can be used for filtering candidates.
  2. Corfu has been more performant (i.e. fewer stutters, smoother cycling of candidates) in my experience.
  3. Corfu can support any company backend via cape-company-to-capf, provided by the complementary cape package. Thus, packages like company-yasnippet can be used with corfu easily (see the next post in my Text completion and minibuffer UI series for more details and examples.)

Basic #

The following is a basic corfu configuration with my preferred keybinds:

(use-package corfu
  :general
  (:keymaps 'corfu-map
            :states 'insert
            "C-n" #'corfu-next
            "C-p" #'corfu-previous
            "<escape>" #'corfu-quit
            "<return>" #'corfu-insert
            "M-d" #'corfu-show-documentation
            "M-l" #'corfu-show-location)
  :config
  (corfu-global-mode))

These keybinds have C-n and C-p move through the candidates popup, <return> choose the current candidate, and <escape> close the corfu popup. Moreover, I have corfu’s documentation command (corfu-show-documentation; shows the available documentation for the current candidate, if any) bound to M-d, and corfu’s location command (corfu-show-location) to go to the location of the current candidate to M-l.

Corfu offers a few variables to configure. You can take a look at each docstring to see its function. Here are my preferences:

:custom
(corfu-auto nil)        ; Only use `corfu' when calling `completion-at-point' or
                                        ; `indent-for-tab-command'
(corfu-auto-prefix 2)
(corfu-auto-delay 0.25)

(corfu-min-width 80)
(corfu-max-width corfu-min-width)       ; Always have the same width
(corfu-count 14)
(corfu-scroll-margin 4)
(corfu-cycle nil)

;; `nil' means to ignore `corfu-separator' behavior, that is, use the older
;; `corfu-quit-at-boundary' = nil behavior. Set this to separator if using
;; `corfu-auto' = `t' workflow (in that case, make sure you also set up
;; `corfu-separator' and a keybind for `corfu-insert-separator', which my
;; configuration already has pre-prepared). Necessary for manual corfu usage with
;; orderless, otherwise first component is ignored, unless `corfu-separator'
;; is inserted.
(corfu-quit-at-boundary nil)
(corfu-preselect-first t)        ; Preselect first candidate?

;; Other
;; NOTE 2022-02-05: In my actual configuration, I have this variable set to nil
;; since I use `corfu-doc', whose configuration comes later. But if you don't
;; use `corfu-doc', this might be helpful to you.
(corfu-echo-documentation t)            ; Show documentation in echo area?

Additionally, the following two variables not under corfu but related to completion-at-point will be useful to set:

;; Works with `indent-for-tab-command'. Make sure tab doesn't indent when you
;; want to perform completion
(tab-always-indent 'complete)
(completion-cycle-threshold nil)      ; Always show all candidates in popup menu

Working with the recent changes to corfu #

On February 7, 2022, corfu introduced an important change2, particularly the interaction between corfu and orderless. You can read more on their README, but, essentially, orderless now introduces the corfu-insert-separator command that inserts the corfu-separator character into the buffer. This character is what delimits orderless components (see this Reddit comment for a more lengthy description of this behavior.) A corfu workflow in which corfu-auto is set to t leverages this change, for without it users could not realistically use corfu with a multi-component completion-style like orderless.

I do not use this workflow3, but if this behavior is desirable, you can set corfu-separator to your orderless separator character to properly delimit orderless components. I personally use the regular space character. You can make the following modifications to your configuration:

:general
;; NOTE 2022-02-28: `general-override-mode-map' is necessary to override local
;; binds to SPC in evil-mode's insert mode. May not be necessary if you don't use `evil'
(:keymaps 'corfu-map
          :states 'insert
          "H-SPC" #'corfu-insert-separator ; I have a hyper key so this is an alternative keybind I use sometimes
          "SPC" #'corfu-insert-separator)
:custom
(corfu-quit-at-boundary 'separator)     ; a non-nil value is necessary
(corfu-separator ?\s)                   ; Use space
(corfu-quit-no-match 'separator) ; Don't quit if there is `corfu-separator' inserted
(corfu-preview-current 'insert)  ; Preview current candidate?
:config
;; NOTE 2022-03-01: This allows for a more evil-esque way to have
;; `corfu-insert-separator' work with space in insert mode without resorting to
;; overriding keybindings with `general-override-mode-map'. See
;; https://github.com/minad/corfu/issues/12#issuecomment-869037519
;; Alternatively, add advice without `general.el':
;; (advice-add 'corfu--setup :after 'evil-normalize-keymaps)
;; (advice-add 'corfu--teardown :after 'evil-normalize-keymaps)
(general-add-advice '(corfu--setup corfu--teardown) :after 'evil-normalize-keymaps)
(evil-make-overriding-map corfu-map)

Further configuration in minibuffers and with lsp #

Corfu’s README provides a way to be able to use corfu completion in the minibuffer:

;; Enable Corfu more generally for every minibuffer, as long as no other
;; completion UI is active. If you use Mct or Vertico as your main minibuffer
;; completion UI. From
;; https://github.com/minad/corfu#completing-with-corfu-in-the-minibuffer
(defun corfu-enable-always-in-minibuffer ()
  "Enable Corfu in the minibuffer if Vertico/Mct are not active."
  (unless (or (bound-and-true-p mct--active) ; Useful if I ever use MCT
              (bound-and-true-p vertico--input))
    (setq-local corfu-auto nil)       ; Ensure auto completion is disabled
    (corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)

This means that in commands like eval-expression, corfu is able to be used (via <tab>) and provide completion.

Figure 2: Using corfu in the minibuffer prompt for eval-expression.

Additionally, for lsp-mode buffers, I have the following lines (this is entirely optional and preferential):

:hook (lsp-completion-mode . kb/corfu-setup-lsp) ; Use corfu for lsp completion
:custom
(lsp-completion-provider :none) ; Use corfu instead the default for lsp completions
:config
;; Setup lsp to use corfu for lsp completion
(defun kb/corfu-setup-lsp ()
  "Use orderless completion style with lsp-capf instead of the
  default lsp-passthrough."
  (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
        '(orderless)))

End product #

Putting it together, we end with my actual configuration:

(use-package corfu
  :hook (lsp-completion-mode . kb/corfu-setup-lsp) ; Use corfu for lsp completion
  :general
  (:keymaps 'corfu-map
            :states 'insert
            "C-n" #'corfu-next
            "C-p" #'corfu-previous
            "<escape>" #'corfu-quit
            "<return>" #'corfu-insert
            "H-SPC" #'corfu-insert-separator
            ;; "SPC" #'corfu-insert-separator ; Use when `corfu-quit-at-boundary' is non-nil
            "M-d" #'corfu-show-documentation
            "C-g" #'corfu-quit
            "M-l" #'corfu-show-location)
  :custom
  ;; Works with `indent-for-tab-command'. Make sure tab doesn't indent when you
  ;; want to perform completion
  (tab-always-indent 'complete)
  (completion-cycle-threshold nil)      ; Always show candidates in menu

  (corfu-auto nil)
  (corfu-auto-prefix 2)
  (corfu-auto-delay 0.25)

  (corfu-min-width 80)
  (corfu-max-width corfu-min-width)     ; Always have the same width
  (corfu-count 14)
  (corfu-scroll-margin 4)
  (corfu-cycle nil)

  ;; `nil' means to ignore `corfu-separator' behavior, that is, use the older
  ;; `corfu-quit-at-boundary' = nil behavior. Set this to separator if using
  ;; `corfu-auto' = `t' workflow (in that case, make sure you also set up
  ;; `corfu-separator' and a keybind for `corfu-insert-separator', which my
  ;; configuration already has pre-prepared). Necessary for manual corfu usage with
  ;; orderless, otherwise first component is ignored, unless `corfu-separator'
  ;; is inserted.
  (corfu-quit-at-boundary nil)
  (corfu-separator ?\s)            ; Use space
  (corfu-quit-no-match 'separator) ; Don't quit if there is `corfu-separator' inserted
  (corfu-preview-current 'insert)  ; Preview first candidate. Insert on input if only one
  (corfu-preselect-first t)        ; Preselect first candidate?

  ;; Other
  (corfu-echo-documentation nil)        ; Already use corfu-doc
  (lsp-completion-provider :none)       ; Use corfu instead for lsp completions
  :init
  (corfu-global-mode)
  :config
  ;; NOTE 2022-03-01: This allows for a more evil-esque way to have
  ;; `corfu-insert-separator' work with space in insert mode without resorting to
  ;; overriding keybindings with `general-override-mode-map'. See
  ;; https://github.com/minad/corfu/issues/12#issuecomment-869037519
  ;; Alternatively, add advice without `general.el':
  ;; (advice-add 'corfu--setup :after 'evil-normalize-keymaps)
  ;; (advice-add 'corfu--teardown :after 'evil-normalize-keymaps)
  (general-add-advice '(corfu--setup corfu--teardown) :after 'evil-normalize-keymaps)
  (evil-make-overriding-map corfu-map)

  ;; Enable Corfu more generally for every minibuffer, as long as no other
  ;; completion UI is active. If you use Mct or Vertico as your main minibuffer
  ;; completion UI. From
  ;; https://github.com/minad/corfu#completing-with-corfu-in-the-minibuffer
  (defun corfu-enable-always-in-minibuffer ()
    "Enable Corfu in the minibuffer if Vertico/Mct are not active."
    (unless (or (bound-and-true-p mct--active) ; Useful if I ever use MCT
                (bound-and-true-p vertico--input))
      (setq-local corfu-auto nil)       ; Ensure auto completion is disabled
      (corfu-mode 1)))
  (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)

  ;; Setup lsp to use corfu for lsp completion
  (defun kb/corfu-setup-lsp ()
    "Use orderless completion style with lsp-capf instead of the
default lsp-passthrough."
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(orderless))))

Kind-icon #

Kind-icon is essentially company-box-icons for corfu. It adds icons to the left margin of the corfu popup that represent the ‘function’ (e.g. variable, method, file) of that candidate.

Figure 3: Using corfu-doc in java-mode with completion candidates provided by lsp-mode.

The following is my configuration:

(use-package kind-icon
  :after corfu
  :custom
  (kind-icon-use-icons t)
  (kind-icon-default-face 'corfu-default) ; Have background color be the same as `corfu' face background
  (kind-icon-blend-background nil)  ; Use midpoint color between foreground and background colors ("blended")?
  (kind-icon-blend-frac 0.08)

  ;; NOTE 2022-02-05: `kind-icon' depends `svg-lib' which creates a cache
  ;; directory that defaults to the `user-emacs-directory'. Here, I change that
  ;; directory to a location appropriate to `no-littering' conventions, a
  ;; package which moves directories of other packages to sane locations.
  (svg-lib-icons-dir (no-littering-expand-var-file-name "svg-lib/cache/")) ; Change cache dir
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter) ; Enable `kind-icon'

  ;; Add hook to reset cache so the icon colors match my theme
  ;; NOTE 2022-02-05: This is a hook which resets the cache whenever I switch
  ;; the theme using my custom defined command for switching themes. If I don't
  ;; do this, then the backgound color will remain the same, meaning it will not
  ;; match the background color corresponding to the current theme. Important
  ;; since I have a light theme and dark theme I switch between. This has no
  ;; function unless you use something similar
  (add-hook 'kb/themes-hooks #'(lambda () (interactive) (kind-icon-reset-cache))))

Corfu-doc #

Corfu-doc is basically company-quickhelp for corfu. It shows the documentation of the selected candidate in an adjacent popup window.

Figure 4: Using corfu-doc in a corfu popup. Called from a java file with completion candidates provided by lsp-mode.

Here is a sample configuration4:

(use-package corfu-doc
  ;; NOTE 2022-02-05: At the time of writing, `corfu-doc' is not yet on melpa
  :straight (corfu-doc :type git :host github :repo "galeo/corfu-doc")
  :after corfu
  :hook (corfu-mode . corfu-doc-mode)
  :general (:keymaps 'corfu-map
                     ;; This is a manual toggle for the documentation popup.
                     [remap corfu-show-documentation] #'corfu-doc-toggle ; Remap the default doc command
                     ;; Scroll in the documentation window
                     "M-n" #'corfu-doc-scroll-up
                     "M-p" #'corfu-doc-scroll-down)
  :custom
  (corfu-doc-delay 0.5)
  (corfu-doc-max-width 70)
  (corfu-doc-max-height 20)

  ;; NOTE 2022-02-05: I've also set this in the `corfu' use-package to be
  ;; extra-safe that this is set when corfu-doc is loaded. I do not want
  ;; documentation shown in both the echo area and in the `corfu-doc' popup.
  (corfu-echo-documentation nil))

From my experience, corfu-doc is perfect for most. However, it should be noted that for those who have a high repeat rate5, rapidly scrolling through candidates causes stuttering and/or lag. This is why I find setting a keybind for corfu-doc-toggle to be useful.


Changelog #

  • [2022-02-28 Mon]
    • Added link to Reddit comment in Working with the recent changes to corfu section. Also update description of new corfu behavior.
    • Added configuration for using corfu in the minibuffer.
    • Listed more benefits to corfu, provided by u/JDRiverRun.
  • [2022-03-01 Tue]
    • Added link to relevant corfu GitHub Issue.
    • Changed corfu configuration to avoid setting keybinds in general-override-mode-map, suggested by a comment to this point.
    • Added a note and GIF to corfu-doc section.
  • [2022-03-11 Fri]
    • Update to include new compatibility with corfu-insert-separator and corfu-quit-at-boundary functionality.

Text completion and minibuffer UI series #

  1. Vertico, Marginalia, All-the-icons-completion, and Orderless
  2. Corfu, Kind-icon, and Corfu-doc (this post!)
  3. Cape

  1. Kudos to u/JDRiverRun, the current maintainer of kind-icon for providing a few benefits I didn’t originally list. ↩︎

  2. This change was initially motivated by jdtsmith (u/JDRiverRun) and is described in this GitHub issue↩︎

  3. See this commit. Also see this GitHub issue which reimplemented the old corfu-quit-at-boundary functionality alongside the then new corfu-insert-separator functionality. ↩︎

  4. This is not exactly my configuration, but is quite close to it. ↩︎

  5. I personally use a repeat rate of 37 ms with a delay rate of 225, set by xset r rate 225 37↩︎