November 28, 2022 As is noted in the corfu-doc repository, corfu-doc has been deprecated by the built-in corfu-popupinfocorfu extension. corfu-popupinfo’s functionality is roughly identical to corfu-doc’s, though its interface and code is naturally more idiomatic to corfu.
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?
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:
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.
Corfu has been more performant (i.e. fewer stutters, smoother cycling of candidates) in my experience.
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:
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-autonil); Only use `corfu' when calling `completion-at-point' or; `indent-for-tab-command'(corfu-auto-prefix2)(corfu-auto-delay0.25)(corfu-min-width80)(corfu-max-widthcorfu-min-width); Always have the same width(corfu-count14)(corfu-scroll-margin4)(corfu-cyclenil);; `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-boundarynil)(corfu-preselect-firstt); 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-documentationt); Show documentation in echo area?
Additionally, the following two variables not under corfu but related to completion-at-point will be useful to set:
1
2
3
4
;; Works with `indent-for-tab-command'. Make sure tab doesn't indent when you;; want to perform completion(tab-always-indent'complete)(completion-cycle-thresholdnil); 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--setupcorfu--teardown):after'evil-normalize-keymaps)(evil-make-overriding-mapcorfu-map)
Further configuration in minibuffers and with lsp
Corfu’s README provides a way to be able to use corfu completion in the minibuffer:
1
2
3
4
5
6
7
8
9
10
11
;; 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(defuncorfu-enable-always-in-minibuffer()"Enable Corfu in the minibuffer if Vertico/Mct are not active."(unless(or(bound-and-true-pmct--active); Useful if I ever use MCT(bound-and-true-pvertico--input))(setq-localcorfu-autonil); Ensure auto completion is disabled(corfu-mode1)))(add-hook'minibuffer-setup-hook#'corfu-enable-always-in-minibuffer1)
This means that in commands like eval-expression, corfu is able to be used (via <tab>) and provide completion.
Additionally, for lsp-mode buffers, I have the following lines (this is entirely optional and preferential):
1
2
3
4
5
6
7
8
9
10
: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(defunkb/corfu-setup-lsp()"Use orderless completion style with lsp-capf instead of the
default lsp-passthrough."(setf(alist-get'styles(alist-get'lsp-capfcompletion-category-defaults))'(orderless)))
End product
Putting it together, we end with my actual configuration:
(use-packagecorfu: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-thresholdnil); Always show candidates in menu(corfu-autonil)(corfu-auto-prefix2)(corfu-auto-delay0.25)(corfu-min-width80)(corfu-max-widthcorfu-min-width); Always have the same width(corfu-count14)(corfu-scroll-margin4)(corfu-cyclenil);; `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-boundarynil)(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-firstt); Preselect first candidate?;; Other(corfu-echo-documentationnil); 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--setupcorfu--teardown):after'evil-normalize-keymaps)(evil-make-overriding-mapcorfu-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(defuncorfu-enable-always-in-minibuffer()"Enable Corfu in the minibuffer if Vertico/Mct are not active."(unless(or(bound-and-true-pmct--active); Useful if I ever use MCT(bound-and-true-pvertico--input))(setq-localcorfu-autonil); Ensure auto completion is disabled(corfu-mode1)))(add-hook'minibuffer-setup-hook#'corfu-enable-always-in-minibuffer1);; Setup lsp to use corfu for lsp completion(defunkb/corfu-setup-lsp()"Use orderless completion style with lsp-capf instead of the
default lsp-passthrough."(setf(alist-get'styles(alist-get'lsp-capfcompletion-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.
(use-packagekind-icon:aftercorfu:custom(kind-icon-use-iconst)(kind-icon-default-face'corfu-default); Have background color be the same as `corfu' face background(kind-icon-blend-backgroundnil); Use midpoint color between foreground and background colors ("blended")?(kind-icon-blend-frac0.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.
(use-packagecorfu-doc;; NOTE 2022-02-05: At the time of writing, `corfu-doc' is not yet on melpa:straight(corfu-doc:typegit:hostgithub:repo"galeo/corfu-doc"):aftercorfu:hook(corfu-mode.corfu-doc-mode):general(:keymaps'corfu-map;; This is a manual toggle for the documentation popup.[remapcorfu-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-delay0.5)(corfu-doc-max-width70)(corfu-doc-max-height20);; 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-documentationnil))
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.
Added configuration for using corfu in the minibuffer.
Listed more benefits to corfu, provided by u/JDRiverRun.
March 01, 2022
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.
March 11, 2022
Update to include new compatibility with corfu-insert-separator and corfu-quit-at-boundary functionality.
Kudos to u/JDRiverRun, the current maintainer of kind-icon for providing a few benefits I didn’t originally list. ↩︎
This change was initially motivated by jdtsmith (u/JDRiverRun) and is described in this GitHub issue. ↩︎
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. ↩︎
This is not exactly my configuration, but is quite close to it. ↩︎
I personally use a repeat rate of 37 ms with a delay rate of 225, set by xset r rate 225 37. ↩︎