From 6949451b00eccbf03a6e070afafca85bec5daac5 Mon Sep 17 00:00:00 2001 From: Luigi Sartor Piucco Date: Sat, 24 Sep 2022 17:33:32 -0300 Subject: [PATCH 1/8] module: add :completion corfu This commit's primary goal is allowing use of [Corfu](https://github.com/minad/corfu) as an alternative to [Company](https://github.com/company-mode/company-mode). It introduces a module under `:completion` for this purpose, plus some conditionals on other relevant modules to toggle functionality like lsp back-ends and [Cape](https://github.com/minad/cape) capfs for certain modes. Other optional or miscellaneous features include: - Corfu is enabled in the minibuffer if `completion-at-point` is bound; - Support for displaying the completion's documentation on a secondary popup; - Support for terminal display if :os tty; - Support for icons if +icons; --- modules/completion/corfu/README.org | 229 ++++++++++++++++++++++ modules/completion/corfu/autoload.el | 10 + modules/completion/corfu/config.el | 92 +++++++++ modules/completion/corfu/packages.el | 11 ++ modules/config/default/+emacs-bindings.el | 2 +- modules/config/default/+evil-bindings.el | 47 ++++- modules/config/default/config.el | 33 ++++ modules/tools/lsp/+lsp.el | 5 +- templates/init.example.el | 1 + 9 files changed, 422 insertions(+), 8 deletions(-) create mode 100644 modules/completion/corfu/README.org create mode 100644 modules/completion/corfu/autoload.el create mode 100644 modules/completion/corfu/config.el create mode 100644 modules/completion/corfu/packages.el diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org new file mode 100644 index 000000000..82b910f43 --- /dev/null +++ b/modules/completion/corfu/README.org @@ -0,0 +1,229 @@ +#+title: :completion corfu +#+subtitle: Complete with cap(f), cape and a flying feather +#+created: September 9, 2022 +#+since: 3.0.0 (#7002) + +* Description :unfold: +This module provides code completion, powered by [[doom-package:corfu]]. + +It is recommended to enable either this or [[doom-module::completion company]] in +case you desire pre-configured auto-completion. Corfu is much lighter weight and +focused, plus it's built on native Emacs functionality, whereas Company is heavy +and highly non-native, but has some extra features and more maturity. + +If you choose Corfu, we also highly recomend reading [[https://github.com/minad/corfu][its README]] and [[https://github.com/minad/cape][cape's +README]], as the backend is very configurable and provides many power-user +utilities for fine-tuning. Only some of common behaviors are documented here. + +** Maintainers +- [[doom-user:][@LuigiPiucco]] + +[[doom-contrib-maintainer:][Become a maintainer?]] + +** Module flags +- +icons :: + Display icons beside completion suggestions. +- +orderless :: + Pull in [[doom-package:orderless]] if necessary and apply multi-component + completion (still needed if [[doom-module::completion vertico]] is active). + +** Packages +- [[doom-package:corfu]] +- [[doom-package:cape]] +- [[doom-package:nerd-icons-corfu]] if [[doom-module::completion corfu +icons]] +- [[doom-package:orderless]] if [[doom-module::completion corfu +orderless]] +- [[doom-package:corfu-terminal]] if [[doom-module::os tty]] + +** Hacks +/No hacks documented for this module./ + +** TODO Changelog +# This section will be machine generated. Don't edit it by hand. +/This module does not have a changelog yet./ + +* Installation +Enable this module in your ~doom!~ block. + +This module has no direct requirements, but some languages may have their own +requirements to fulfill before you get code completion in them (and some +languages may lack code completion support altogether). Run ~$ doom doctor~ to +find out if you're missing any dependencies. Note that Corfu may have support +for completions in languages that have no development intelligence, since it +supports generic, context insensitive candidates such as file names or recurring +words. + +* TODO Usage +#+begin_quote + 🔨 /This module's usage documentation is incomplete./ [[doom-contrib-module:][Complete it?]] +#+end_quote + +By default, completion gets triggered after typing 2 non-space consecutive +characters, by means of [[kbd:][C-SPC]] at any moment or [[kbd:][TAB]] on a line with proper +indentation. Many styles of completion are documented below, which can be +composed to suit the user. The following keybindings are generally available: + +| Keybind | Description | +|---------+--------------------------------------------| +| [[kbd:][C-n]] | Go to next candidate | +| [[kbd:][C-p]] | Go to previous candidate | +| [[kbd:][C-S-n]] | Go to next doc line | +| [[kbd:][C-S-p]] | Go to previous doc line | +| [[kbd:][C-S-s]] | Export to minibuffer | +| [[kbd:][TAB]] | (when not completing) Indent or complete | +| [[kbd:][C-SPC]] | (when not completing) Complete | +| [[kbd:][C-u]] | (evil) Go to next candidate page | +| [[kbd:][C-d]] | (evil) Go to previous candidate page | +| [[kbd:][C-h]] | (evil) Toggle documentation (if available) | +| [[kbd:][M-t]] | (emacs) (when not completing) Complete | + +Bindings in the following sections are additive, and unless otherwise noted, are +enabled by default with configurable behavior. Additionally, for users of evil, +[[kdb:][C-SPC]] is smart regarding your state. In normal-like states, enter insert then +start corfu; in visual-like states, perform [[help:evil-change][evil-change]] (which leaves you in +insert state) then start corfu; in insert-like states, start corfu immediatelly. + +** Commit preview on type +When the completion popup is visible, by default the current candidate is +previewed into the buffer, and further input commits that candidate as previewed +(note it does not perform candidate exit actions, such as expanding snippets). + +The feature is in line with other common editors, but if you prefer the preview +to be only visual or for there to be no preview, configure +[[var:corfu-preview-current]]. + +#+begin_src emacs-lisp +;; Non-inserting preview +(setq corfu-preview-current t) +;; No preview +(setq corfu-preview-current nil) +#+end_src + +** Commit on [[kbd:][RET]] with pass-through +A lot of people like to use [[kbd:][RET]] to commit, so here we bind it to Corfu's +insertion function. Note that Corfu allows "no candidate" to be selected, and in +that case, we have a custom binding to quit completion and pass-through. To make +it less obtrusive by default, the popup starts in this unselected state. See +[[var:corfu-preselect]] to alter the initial behavior; it can start with the first +one selected, for instance. Then, you have to move one candidate backwards to +pass-through The exact action of [[kbd:][RET]] can be changed via +[[var:+corfu-want-ret-to-confirm]]. + +| Keybind | Description | +|---------+-----------------------| +| [[kbd:][RET]] | Insert candidate DWIM | + +** Cycle directionally +If you'd rather think in directions rather than next/previous, arrow keys and vi +movements to control the selection and documentation view are bound by default. +You may unbind them by setting to nil, see ~map!~'s documentation. + +| Keybind | Description | +|----------+---------------------------------| +| [[kbd:][]] | Go to next candidate | +| [[kbd:][]] | Go to previous candidate | +| [[kbd:][C-j]] | (evil) Go to next candidate | +| [[kbd:][C-k]] | (evil) Go to previous candidate | +| [[kbd:][C-]] | Go to next doc line | +| [[kbd:][C-]] | Go to previous doc line | +| [[kbd:][C-S-j]] | (evil) Go to next doc line | +| [[kbd:][C-S-k]] | (evil) Go to previous doc line | + +** Cycle with [[kbd:][TAB]] +[[kbd:][TAB]]-based cycling alternatives are also bound according to the table below: + +| Keybind | Description | +|---------+--------------------------| +| [[kbd:][TAB]] | Go to next candidate | +| [[kbd:][S-TAB]] | Go to previous candidate | + +** Searching with multiple keywords (~+orderless~) +If the [[doom-module::completion corfu +orderless]] flag is enabled, users can +perform code completion with multiple search keywords by use of space as the +separator. More information can be found [[https://github.com/oantolin/orderless#company][here]]. Pressing [[kdb:][C-SPC]] again while +completing inserts a space as separator. This allows searching with +space-separated terms; each piece will match individually and in any order, with +smart casing. Pressing just [[kbd:][SPC]] acts as normal and quits completion, so that +when typing sentences it doesn't try to complete the whole sentence instead of +just the word. + +| Keybind | Description | +|---------+-------------------------------------------------| +| [[kbd:][C-SPC]] | (evil) (when completing) Insert separator DWIM | +| [[kbd:][M-SPC]] | (emacs) (when completing) Insert separator DWIM | +| [[kbd:][SPC]] | (when completing) Quit autocompletion | +| [[kbd:][SPC]] | (when completing with separators) Self-insert | + +** Exporting to the minibuffer (requires [[doom-module::completion vertico]]) +When using the [[doom-module::completion vertico]] module, which pulls in the +[[doom-package:consult]] package, the entries shown in the completion popup can be +exported to a consult minibuffer, giving access to all the manipulations the +Vertico suite allows. For instance, one could use this to export with +[[doom-package:embark]] via [[kbd:][C-c C-l]] and get a buffer with all candidates. + +* Configuration +A few variables may be set to change behavior of this module: + +- [[var:completion-at-point-functions]] :: + This is not a module/package variable, but a builtin Emacs one. Even so, it's + very important to how Corfu works, so we document it here. It contains a list + of functions that are called in turn to generate completion candidates. The + regular (non-lexical) value should contain few entries and they should + generally be context aware, so as to predict what you need. Additional + functions can be added as you get into more and more specific contexts. Also, + there may be cases where you know beforehand the kind of candidate needed, and + want to enable only that one. For this, the variable may be lexically bound to + the correct value, or you may call the CAPF interactively if a single function + is all you need. +- [[var:corfu-auto-delay]] :: + Number of seconds till completion occurs automatically. Defaults to 0.1. +- [[var:corfu-auto-prefix]] :: + Number of characters till auto-completion starts to happen. Defaults to 2. +- [[var:corfu-on-exact-match]] :: + Configures behavior for exact matches. +- [[var:corfu-preselect]] :: + Configures startup selection, choosing between the first candidate or the + prompt. +- [[var:corfu-preview-current]] :: + Configures current candidate preview. +- [[var:+corfu-want-ret-to-confirm]] :: + Enables commiting with [[RET]] when the popup is visible. Default is ~t~, may be set to + ~'minibuffer~ if you want to commit both the completion and the minibuffer when + active. When ~nil~, it is always passed-through. + +** Adding CAPFs to a mode +To add other CAPFs on a mode-per-mode basis, put either of the following in your +~config.el~: + +#+begin_src emacs-lisp +(add-hook! some-mode (add-to-list 'completion-at-point-functions #'some-capf)) +;; OR, but note the different call signature +(add-hook 'some-mode-hook (lambda () (add-to-list 'completion-at-point-functions #'some-capf))) +#+end_src + +~DEPTH~ above is an integer between -100, 100, and defaults to 0 if nil. Also +see ~add-hook!~'s documentation for additional ways to call it. ~add-hook~ only +accepts the quoted arguments form above. + +** Adding CAPFs to a key +To add other CAPFs to keys, adapt the snippet below into your ~config.el~: + +#+begin_src emacs-lisp +(map! :map some-mode-map + "C-x e" #'cape-emoji) +#+end_src + +It's okay to add to the mode directly because ~completion-at-point~ works +regardless of Corfu (the latter is an enhanced UI for the former). Just note not +all CAPFs are interactive to be called this way, in which case you can use +[[doom-package:cape]]'s adapter to enable this. + +* Troubleshooting +[[doom-report:][Report an issue?]] + +* Frequently asked questions +/This module has no FAQs yet./ [[doom-suggest-faq:][Ask one?]] + +* TODO Appendix +#+begin_quote + 🔨 This module has no appendix yet. [[doom-contrib-module:][Write one?]] +#+end_quote diff --git a/modules/completion/corfu/autoload.el b/modules/completion/corfu/autoload.el new file mode 100644 index 000000000..c34cb8232 --- /dev/null +++ b/modules/completion/corfu/autoload.el @@ -0,0 +1,10 @@ +;;; completion/corfu/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +corfu-move-to-minibuffer () + ;; Taken from corfu's README. + ;; TODO: extend this to other completion front-ends. + (interactive) + (let ((completion-extra-properties corfu--extra) + (completion-cycle-threshold completion-cycling)) + (apply #'consult-completion-in-region completion-in-region--data))) diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el new file mode 100644 index 000000000..997af183d --- /dev/null +++ b/modules/completion/corfu/config.el @@ -0,0 +1,92 @@ +;;; completion/corfu/config.el -*- lexical-binding: t; -*- + +(defvar +corfu-want-ret-to-confirm t + "Configure how the user expects RET to behave. +Possible values are: +- t (default): Insert candidate if one is selected, pass-through otherwise; +- `minibuffer': Insert candidate if one is selected, pass-through otherwise, + and immediatelly exit if in the minibuffer; +- nil: Pass-through without inserting.") + +;; +;;; Packages +(use-package! corfu + :hook (doom-first-input . global-corfu-mode) + :init + (add-hook! 'minibuffer-setup-hook + (defun +corfu-enable-in-minibuffer () + "Enable Corfu in the minibuffer if `completion-at-point' is bound." + (when (where-is-internal #'completion-at-point (list (current-local-map))) + (setq-local corfu-echo-delay nil) + (corfu-mode +1)))) + :config + (setq corfu-auto t + corfu-auto-delay 0.1 + corfu-auto-prefix 2 + global-corfu-modes '((not + erc-mode + circe-mode + help-mode + gud-mode + vterm-mode) + t) + corfu-cycle t + corfu-separator (when (modulep! +orderless) ?\s) + corfu-preselect 'prompt + corfu-count 16 + corfu-max-width 120 + corfu-preview-current 'insert + corfu-on-exact-match nil + corfu-quit-at-boundary (if (modulep! +orderless) 'separator t) + corfu-quit-no-match (if (modulep! +orderless) 'separator t) + tab-always-indent 'complete) + (add-to-list 'completion-category-overrides `(lsp-capf (styles ,@completion-styles))) + (add-to-list 'corfu-auto-commands #'lispy-colon) + (add-to-list 'corfu-continue-commands #'+corfu-move-to-minibuffer) + (add-hook 'evil-insert-state-exit-hook #'corfu-quit)) + +(use-package! cape + :defer t + :init + (add-hook! prog-mode + (defun +corfu-add-cape-file-h () + (add-to-list 'completion-at-point-functions #'cape-file))) + (add-hook! (org-mode markdown-mode) + (defun +corfu-add-cape-elisp-block-h () + (add-hook 'completion-at-point-functions #'cape-elisp-block 0 t))) + + ;; Make these capfs composable. + (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible) + (advice-add #'lsp-completion-at-point :around #'cape-wrap-nonexclusive) + (advice-add #'comint-completion-at-point :around #'cape-wrap-nonexclusive) + (advice-add #'eglot-completion-at-point :around #'cape-wrap-nonexclusive) + (advice-add #'pcomplete-completions-at-point :around #'cape-wrap-nonexclusive) + ;; From the `cape' readme. Without this, Eshell autocompletion is broken on + ;; Emacs28. + (when (< emacs-major-version 29) + (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent) + (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify))) + +(use-package! corfu-terminal + :when (not (display-graphic-p)) + :hook ((corfu-mode . corfu-terminal-mode))) + +;; +;;; Extensions + +(use-package! corfu-history + :hook ((corfu-mode . corfu-history-mode)) + :config + (after! savehist (add-to-list 'savehist-additional-variables 'corfu-history))) + +(use-package! corfu-popupinfo + :hook ((corfu-mode . corfu-popupinfo-mode)) + :config + (setq corfu-popupinfo-delay '(0.5 . 1.0))) + +(use-package! nerd-icons-corfu + :when (modulep! +icons) + :defer t + :init + (after! corfu + (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))) diff --git a/modules/completion/corfu/packages.el b/modules/completion/corfu/packages.el new file mode 100644 index 000000000..fba829248 --- /dev/null +++ b/modules/completion/corfu/packages.el @@ -0,0 +1,11 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/corfu/packages.el + +(package! corfu :pin "c1e7b6190b00158e67347b4db0a8f7964e5d2f8b") +(package! cape :pin "a397a0c92de38277b7f835fa999fac400a764908") +(when (modulep! +icons) + (package! nerd-icons-corfu :pin "7077bb76fefc15aed967476406a19dc5c2500b3c")) +(when (modulep! +orderless) + (package! orderless :pin "b24748093b00b37c3a572c4909f61c08fa27504f")) +(when (modulep! :os tty) + (package! corfu-terminal :pin "501548c3d51f926c687e8cd838c5865ec45d03cc")) diff --git a/modules/config/default/+emacs-bindings.el b/modules/config/default/+emacs-bindings.el index 7aa4dfc41..7c63e68e2 100644 --- a/modules/config/default/+emacs-bindings.el +++ b/modules/config/default/+emacs-bindings.el @@ -511,7 +511,7 @@ "C-x C-b" #'ibuffer "C-x K" #'doom/kill-this-buffer-in-all-windows - ;;; company-mode + ;;; completion (in-buffer) (:when (modulep! :completion company) "C-;" #'+company/complete (:after company diff --git a/modules/config/default/+evil-bindings.el b/modules/config/default/+evil-bindings.el index fd2487410..b07109ebe 100644 --- a/modules/config/default/+evil-bindings.el +++ b/modules/config/default/+evil-bindings.el @@ -43,7 +43,10 @@ #'yas-expand (and (bound-and-true-p company-mode) (modulep! :completion company +tng)) - #'company-indent-or-complete-common) + #'company-indent-or-complete-common + (and (bound-and-true-p corfu-mode) + (modulep! :completion corfu)) + #'completion-at-point) :m [tab] (cmds! (and (modulep! :editor snippets) (evil-visual-state-p) (or (eq evil-visual-selection 'line) @@ -127,7 +130,7 @@ ;; ;;; Module keybinds -;;; :completion +;;; :completion (in-buffer) (map! (:when (modulep! :completion company) :i "C-@" (cmds! (not (minibufferp)) #'company-complete-common) :i "C-SPC" (cmds! (not (minibufferp)) #'company-complete-common) @@ -156,7 +159,38 @@ "C-s" #'company-filter-candidates [escape] #'company-search-abort))) - (:when (modulep! :completion ivy) + (:when (modulep! :completion corfu) + (:after corfu + (:map corfu-mode-map + :i "C-SPC" #'completion-at-point + :n "C-SPC" (cmd! (call-interactively #'evil-insert-state) + (call-interactively #'completion-at-point)) + :v "C-SPC" (cmd! (call-interactively #'evil-change) + (call-interactively #'completion-at-point))) + (:map corfu-map + :i "C-SPC" #'corfu-insert-separator + "C-k" #'corfu-previous + "C-j" #'corfu-next + "C-u" (cmd! (let (corfu-cycle) + (funcall-interactively #'corfu-next (- corfu-count)))) + "C-d" (cmd! (let (corfu-cycle) + (funcall-interactively #'corfu-next corfu-count))))) + (:after corfu-popupinfo + :map corfu-popupinfo-map + "C-h" #'corfu-popupinfo-toggle + ;; Reversed because popupinfo assumes opposite of what feels intuitive + ;; with evil. + "C-S-k" #'corfu-popupinfo-scroll-down + "C-S-j" #'corfu-popupinfo-scroll-up + "C-" #'corfu-popupinfo-scroll-down + "C-" #'corfu-popupinfo-scroll-up + "C-S-p" #'corfu-popupinfo-scroll-down + "C-S-n" #'corfu-popupinfo-scroll-up + "C-S-u" (cmd!! #'corfu-popupinfo-scroll-down nil corfu-popupinfo-min-height) + "C-S-d" (cmd!! #'corfu-popupinfo-scroll-up nil corfu-popupinfo-min-height)))) + +;;; :completion (separate) +(map! (:when (modulep! :completion ivy) (:after ivy :map ivy-minibuffer-map "C-SPC" #'ivy-call-and-recenter ; preview file @@ -169,7 +203,8 @@ [C-return] #'+ivy/git-grep-other-window-action)) (:when (modulep! :completion helm) - (:after helm :map helm-map + (:after helm + :map helm-map [remap next-line] #'helm-next-line [remap previous-line] #'helm-previous-line [left] #'left-char @@ -387,9 +422,9 @@ :desc "Incoming call hierarchy" "y" #'lsp-treemacs-call-hierarchy :desc "Outgoing call hierarchy" "Y" (cmd!! #'lsp-treemacs-call-hierarchy t) :desc "References tree" "R" (cmd!! #'lsp-treemacs-references t) - :desc "Symbols" "S" #'lsp-treemacs-symbols) + :desc "Symbols" "S" #'lsp-treemacs-symbols :desc "LSP" "l" #'+default/lsp-command-map - :desc "LSP Rename" "r" #'lsp-rename) + :desc "LSP Rename" "r" #'lsp-rename)) (:when (modulep! :tools lsp +eglot) :desc "LSP Execute code action" "a" #'eglot-code-actions :desc "LSP Rename" "r" #'eglot-rename diff --git a/modules/config/default/config.el b/modules/config/default/config.el index e67adf1e3..8316284d8 100644 --- a/modules/config/default/config.el +++ b/modules/config/default/config.el @@ -458,6 +458,39 @@ Continues comments if executed from a commented line. Consults '(evil-ex-completion-map))) "C-s" command)) + (map! :when (modulep! :completion corfu) + :after corfu + (:map corfu-map + "C-S-s" #'+corfu-move-to-minibuffer + "C-p" #'corfu-previous + "C-n" #'corfu-next + "S-TAB" #'corfu-previous + [backtab] #'corfu-previous + "TAB" #'corfu-next + [tab] #'corfu-next)) + (let ((cmds-ret + `(menu-item "Insert completion DWIM" corfu-insert + :filter ,(lambda (cmd) + (interactive) + (cond ((null +corfu-want-ret-to-confirm) + (corfu-quit) + nil) + ((eq +corfu-want-ret-to-confirm 'minibuffer) + (funcall-interactively cmd) + nil) + ((and (or (not (minibufferp nil t)) + (eq +corfu-want-ret-to-confirm t)) + (>= corfu--index 0)) + cmd) + ((or (not (minibufferp nil t)) + (eq +corfu-want-ret-to-confirm t)) + nil) + (t cmd)))))) + (map! :when (modulep! :completion corfu) + :map corfu-map + :gi [return] cmds-ret + :gi "RET" cmds-ret)) + ;; Smarter C-a/C-e for both Emacs and Evil. C-a will jump to indentation. ;; Pressing it again will send you to the true bol. Same goes for C-e, except ;; it will ignore comments+trailing whitespace before jumping to eol. diff --git a/modules/tools/lsp/+lsp.el b/modules/tools/lsp/+lsp.el index 5ae4a417d..96361aae4 100644 --- a/modules/tools/lsp/+lsp.el +++ b/modules/tools/lsp/+lsp.el @@ -138,8 +138,11 @@ server getting expensively restarted when reverting buffers." " ")) (add-to-list 'global-mode-string '(t (:eval lsp-modeline-icon)) - 'append)))))) + 'append))))) + (when (modulep! :completion corfu) + (setq lsp-completion-provider :none) + (add-hook 'lsp-mode-hook #'lsp-completion-mode))) (use-package! lsp-ui :hook (lsp-mode . lsp-ui-mode) diff --git a/templates/init.example.el b/templates/init.example.el index 7a5e4e6c7..cef45ecb3 100644 --- a/templates/init.example.el +++ b/templates/init.example.el @@ -22,6 +22,7 @@ :completion company ; the ultimate code completion backend + ;;(corfu +orderless) ; complete with cap(f), cape and a flying feather! ;;helm ; the *other* search engine for love and life ;;ido ; the other *other* search engine... ;;ivy ; a search engine for love and life From 968a8975308c772d0298021884758792eab641e8 Mon Sep 17 00:00:00 2001 From: Luigi Sartor Piucco Date: Wed, 26 Jul 2023 19:57:21 -0300 Subject: [PATCH 2/8] feat(corfu): add snippets Yasnippet is now properly integrated! A previosly-unset default has now been given to `corfu-on-exact-match`. With snippets, it causes immediate expansion upon single match by default, so we set it to nil and recommend against changing it in the README. --- modules/completion/corfu/README.org | 3 ++- modules/completion/corfu/config.el | 8 ++++++++ modules/completion/corfu/packages.el | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org index 82b910f43..74c8043d5 100644 --- a/modules/completion/corfu/README.org +++ b/modules/completion/corfu/README.org @@ -33,6 +33,7 @@ utilities for fine-tuning. Only some of common behaviors are documented here. - [[doom-package:nerd-icons-corfu]] if [[doom-module::completion corfu +icons]] - [[doom-package:orderless]] if [[doom-module::completion corfu +orderless]] - [[doom-package:corfu-terminal]] if [[doom-module::os tty]] +- [[doom-package:yasnippet-capf]] if [[doom-module::editor snippets]] ** Hacks /No hacks documented for this module./ @@ -50,7 +51,7 @@ languages may lack code completion support altogether). Run ~$ doom doctor~ to find out if you're missing any dependencies. Note that Corfu may have support for completions in languages that have no development intelligence, since it supports generic, context insensitive candidates such as file names or recurring -words. +words. Snippets may also appear in the candidate list if available. * TODO Usage #+begin_quote diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el index 997af183d..6ad3fe349 100644 --- a/modules/completion/corfu/config.el +++ b/modules/completion/corfu/config.el @@ -67,6 +67,14 @@ Possible values are: (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent) (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify))) +(use-package! yasnippet-capf + :when (modulep! :editor snippets) + :defer t + :init + (add-hook! 'yas-minor-mode-hook + (defun +corfu-add-yasnippet-capf-h () + (add-hook 'completion-at-point-functions #'yasnippet-capf 30 t)))) + (use-package! corfu-terminal :when (not (display-graphic-p)) :hook ((corfu-mode . corfu-terminal-mode))) diff --git a/modules/completion/corfu/packages.el b/modules/completion/corfu/packages.el index fba829248..cf45a992f 100644 --- a/modules/completion/corfu/packages.el +++ b/modules/completion/corfu/packages.el @@ -9,3 +9,5 @@ (package! orderless :pin "b24748093b00b37c3a572c4909f61c08fa27504f")) (when (modulep! :os tty) (package! corfu-terminal :pin "501548c3d51f926c687e8cd838c5865ec45d03cc")) +(when (modulep! :editor snippets) + (package! yasnippet-capf :pin "9043f8275176a8f198ce8e81fadab1870fa165bb")) From 365a95de76e14939b7296c636192319a675844cf Mon Sep 17 00:00:00 2001 From: StrawberryTea Date: Mon, 30 Oct 2023 14:36:19 -0400 Subject: [PATCH 3/8] feat(corfu): more CAPFs and ergonomy changes Add CAPFs from cape: - `cape-dabbrev`; - `cape-elisp-block`; - `cape-file`; Fix some CAPFs via cape: - Make non-exclusive, purified and silent `pcomplete-completions-at-point`; - Make non-exclusive and non-interruptable `lsp-completion-at-point`; - Make non-exclusive `eglot-completion-at-point`; - Make non-exclusive `comint-completion-at-point`; Fix and improve keybindings: - Smart `DEL`; Add depth to CAPFs, allowing ordering to be adjustable. --- modules/completion/corfu/README.org | 21 +++++++++++++++++++-- modules/completion/corfu/config.el | 22 +++++++++++++++++++++- modules/config/default/config.el | 10 +++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org index 74c8043d5..252423567 100644 --- a/modules/completion/corfu/README.org +++ b/modules/completion/corfu/README.org @@ -26,6 +26,9 @@ utilities for fine-tuning. Only some of common behaviors are documented here. - +orderless :: Pull in [[doom-package:orderless]] if necessary and apply multi-component completion (still needed if [[doom-module::completion vertico]] is active). +- +dabbrev :: + Enable and configure [[doom-package:dabbrev]] as a close-to-universal CAPF + fallback. ** Packages - [[doom-package:corfu]] @@ -190,15 +193,18 @@ A few variables may be set to change behavior of this module: Enables commiting with [[RET]] when the popup is visible. Default is ~t~, may be set to ~'minibuffer~ if you want to commit both the completion and the minibuffer when active. When ~nil~, it is always passed-through. +- [[var:+corfu-buffer-scanning-size-limit]] :: + Sets the maximum buffer size to be scanned by ~cape-dabbrev~. Defaults to 1 MB. + Set this if you are having performance problems using the CAPF. ** Adding CAPFs to a mode To add other CAPFs on a mode-per-mode basis, put either of the following in your ~config.el~: #+begin_src emacs-lisp -(add-hook! some-mode (add-to-list 'completion-at-point-functions #'some-capf)) +(add-hook! some-mode (add-hook 'completion-at-point-functions #'some-capf depth t)) ;; OR, but note the different call signature -(add-hook 'some-mode-hook (lambda () (add-to-list 'completion-at-point-functions #'some-capf))) +(add-hook 'some-mode-hook (lambda () (add-hook 'completion-at-point-functions #'some-capf depth t))) #+end_src ~DEPTH~ above is an integer between -100, 100, and defaults to 0 if nil. Also @@ -221,6 +227,17 @@ all CAPFs are interactive to be called this way, in which case you can use * Troubleshooting [[doom-report:][Report an issue?]] +If you have performance issues with ~cape-dabbrev~, the first thing I recommend +doing is to look at the list of buffers Dabbrev is scanning: + +#+begin_src emacs-lisp +(dabbrev--select-buffers) ; => (# #> # ...) +(length (dabbrev--select-buffers)) ; => 37 +#+end_src + +... and modify ~dabbrev-ignored-buffer-regexps~ or ~dabbrev-ignored-buffer-modes~ +accordingly. + * Frequently asked questions /This module has no FAQs yet./ [[doom-suggest-faq:][Ask one?]] diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el index 6ad3fe349..ae2e8409a 100644 --- a/modules/completion/corfu/config.el +++ b/modules/completion/corfu/config.el @@ -8,6 +8,9 @@ Possible values are: and immediatelly exit if in the minibuffer; - nil: Pass-through without inserting.") +(defvar +corfu-buffer-scanning-size-limit (* 1 1024 1024) ; 1 MB + "Size limit for a buffer to be scanned by `cape-dabbrev'.") + ;; ;;; Packages (use-package! corfu @@ -50,10 +53,27 @@ Possible values are: :init (add-hook! prog-mode (defun +corfu-add-cape-file-h () - (add-to-list 'completion-at-point-functions #'cape-file))) + (add-hook 'completion-at-point-functions #'cape-file -10 t))) (add-hook! (org-mode markdown-mode) (defun +corfu-add-cape-elisp-block-h () (add-hook 'completion-at-point-functions #'cape-elisp-block 0 t))) + ;; Enable Dabbrev completion basically everywhere as a fallback. + (when (modulep! +dabbrev) + (setq cape-dabbrev-check-other-buffers t) + ;; Set up `cape-dabbrev' options. + (defun +dabbrev-friend-buffer-p (other-buffer) + (< (buffer-size other-buffer) +corfu-buffer-scanning-size-limit)) + (add-hook! (prog-mode text-mode conf-mode comint-mode minibuffer-setup + eshell-mode) + (defun +corfu-add-cape-dabbrev-h () + (add-hook 'completion-at-point-functions #'cape-dabbrev 20 t))) + (after! dabbrev + (setq dabbrev-friend-buffer-function #'+dabbrev-friend-buffer-p + dabbrev-ignored-buffer-regexps + '("^ " + "\\(TAGS\\|tags\\|ETAGS\\|etags\\|GTAGS\\|GRTAGS\\|GPATH\\)\\(<[0-9]+>\\)?") + dabbrev-upcase-means-case-search t) + (add-to-list 'dabbrev-ignored-buffer-modes 'pdf-view-mode))) ;; Make these capfs composable. (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible) diff --git a/modules/config/default/config.el b/modules/config/default/config.el index 8316284d8..e3becc12a 100644 --- a/modules/config/default/config.el +++ b/modules/config/default/config.el @@ -468,7 +468,13 @@ Continues comments if executed from a commented line. Consults [backtab] #'corfu-previous "TAB" #'corfu-next [tab] #'corfu-next)) - (let ((cmds-ret + (let ((cmds-del + `(menu-item "Reset completion" corfu-reset + :filter ,(lambda (cmd) + (when (and (>= corfu--index 0) + (eq corfu-preview-current 'insert)) + cmd)))) + (cmds-ret `(menu-item "Insert completion DWIM" corfu-insert :filter ,(lambda (cmd) (interactive) @@ -488,6 +494,8 @@ Continues comments if executed from a commented line. Consults (t cmd)))))) (map! :when (modulep! :completion corfu) :map corfu-map + [backspace] cmds-del + "DEL" cmds-del :gi [return] cmds-ret :gi "RET" cmds-ret)) From 0588b42b46265450128e438cef82f7906d8722e5 Mon Sep 17 00:00:00 2001 From: Luigi Sartor Piucco Date: Sun, 29 Oct 2023 14:31:13 -0300 Subject: [PATCH 4/8] feat(corfu,vertico): use equal orderless config This removes the old `&` separator for Vertico (does anyone use that instead of just space?) in favor of escapable space and unifies orderless config with Corfu. Also implements smart separator insert/escape/reset on `C-SPC` Co-authored-by: Liam Hupfer --- modules/completion/corfu/README.org | 5 ++++- modules/completion/corfu/autoload.el | 12 ++++++++++++ modules/completion/corfu/config.el | 13 +++++++++++++ modules/completion/vertico/config.el | 2 +- modules/config/default/config.el | 1 + 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org index 252423567..9f0f7093c 100644 --- a/modules/completion/corfu/README.org +++ b/modules/completion/corfu/README.org @@ -148,7 +148,10 @@ completing inserts a space as separator. This allows searching with space-separated terms; each piece will match individually and in any order, with smart casing. Pressing just [[kbd:][SPC]] acts as normal and quits completion, so that when typing sentences it doesn't try to complete the whole sentence instead of -just the word. +just the word. Pressing [[kdb:][C-SPC]] with point after a separator escapes it with a +backslash, including the space in the search term, and pressing it with an +already escaped separator before point deletes it. Thus, you can cycle back if +you accidentaly press more than needed. | Keybind | Description | |---------+-------------------------------------------------| diff --git a/modules/completion/corfu/autoload.el b/modules/completion/corfu/autoload.el index c34cb8232..5003d3b7c 100644 --- a/modules/completion/corfu/autoload.el +++ b/modules/completion/corfu/autoload.el @@ -8,3 +8,15 @@ (let ((completion-extra-properties corfu--extra) (completion-cycle-threshold completion-cycling)) (apply #'consult-completion-in-region completion-in-region--data))) + +;;;###autoload +(defun +corfu-smart-sep-toggle-escape () + "Insert `corfu-separator' or toggle escape if it's already there." + (interactive) + (cond ((and (char-equal (char-before) corfu-separator) + (char-equal (char-before (1- (point))) ?\\)) + (save-excursion (delete-char -2))) + ((char-equal (char-before) corfu-separator) + (save-excursion (backward-char 1) + (insert-char ?\\))) + (t (call-interactively #'corfu-insert-separator)))) diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el index ae2e8409a..bfa302546 100644 --- a/modules/completion/corfu/config.el +++ b/modules/completion/corfu/config.el @@ -46,6 +46,7 @@ Possible values are: (add-to-list 'completion-category-overrides `(lsp-capf (styles ,@completion-styles))) (add-to-list 'corfu-auto-commands #'lispy-colon) (add-to-list 'corfu-continue-commands #'+corfu-move-to-minibuffer) + (add-to-list 'corfu-continue-commands #'+corfu-smart-sep-toggle-escape) (add-hook 'evil-insert-state-exit-hook #'corfu-quit)) (use-package! cape @@ -118,3 +119,15 @@ Possible values are: :init (after! corfu (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))) + +;; If vertico is not enabled, orderless will be installed but not configured. +;; That may break smart separator behavior, so we conditionally configure it. +(use-package! orderless + :when (and (not (modulep! :completion vertico)) + (modulep! +orderless)) + :config + (setq completion-styles '(orderless basic) + completion-category-defaults nil + completion-category-overrides '((file (styles orderless partial-completion))) + orderless-component-separator #'orderless-escapable-split-on-space) + (set-face-attribute 'completions-first-difference nil :inherit nil)) diff --git a/modules/completion/vertico/config.el b/modules/completion/vertico/config.el index 0d52ab950..3f2bf3f30 100644 --- a/modules/completion/vertico/config.el +++ b/modules/completion/vertico/config.el @@ -107,7 +107,7 @@ orderless." ;; find-file etc. completion-category-overrides '((file (styles +vertico-basic-remote orderless partial-completion))) orderless-style-dispatchers '(+vertico-orderless-dispatch) - orderless-component-separator "[ &]") + orderless-component-separator #'orderless-escapable-split-on-space) ;; ...otherwise find-file gets different highlighting than other commands (set-face-attribute 'completions-first-difference nil :inherit nil)) diff --git a/modules/config/default/config.el b/modules/config/default/config.el index e3becc12a..21aa7268d 100644 --- a/modules/config/default/config.el +++ b/modules/config/default/config.el @@ -461,6 +461,7 @@ Continues comments if executed from a commented line. Consults (map! :when (modulep! :completion corfu) :after corfu (:map corfu-map + [remap corfu-insert-separator] #'+corfu-smart-sep-toggle-escape "C-S-s" #'+corfu-move-to-minibuffer "C-p" #'corfu-previous "C-n" #'corfu-next From 763464afdb9c46d5f60d4565c110d6d1a05fbf99 Mon Sep 17 00:00:00 2001 From: Luigi Sartor Piucco Date: Sun, 4 Feb 2024 17:17:13 -0300 Subject: [PATCH 5/8] feat(corfu): general move-to-minibuffer impl We previously implemented only consult/vertico as a target for export, now we have all of them. It was necessary to use case-by-case conditions, unfortunately, because other UIs have subtle quirks that prevent a single generalized approach to work. Ivy is almost compliant, but it needs beg and end to not be markers. Helm doesn't replace `completion-in-region-function`, it expects to go around the default `completion--in-region`. It's supposed to add the advice by itself, but it's very unreliable, so we do the wrapping manually. Ido doesn't implement `completion-in-region` and its `completing-read` is retricted to a list of strings as table, so we use default `completion--in-region` with no bells or whistles. --- modules/completion/corfu/README.org | 11 +++++------ modules/completion/corfu/autoload.el | 27 ++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org index 9f0f7093c..7413f53a1 100644 --- a/modules/completion/corfu/README.org +++ b/modules/completion/corfu/README.org @@ -160,12 +160,11 @@ you accidentaly press more than needed. | [[kbd:][SPC]] | (when completing) Quit autocompletion | | [[kbd:][SPC]] | (when completing with separators) Self-insert | -** Exporting to the minibuffer (requires [[doom-module::completion vertico]]) -When using the [[doom-module::completion vertico]] module, which pulls in the -[[doom-package:consult]] package, the entries shown in the completion popup can be -exported to a consult minibuffer, giving access to all the manipulations the -Vertico suite allows. For instance, one could use this to export with -[[doom-package:embark]] via [[kbd:][C-c C-l]] and get a buffer with all candidates. +** Exporting to the minibuffer +The entries shown in the completion popup can be exported to a ~completing-read~ +minibuffer, giving access to all the manipulations that suite allows. Using +Vertico for instance, one could use this to export with [[doom-package:embark]] via +[[kbd:][C-c C-l]] and get a buffer with all candidates. * Configuration A few variables may be set to change behavior of this module: diff --git a/modules/completion/corfu/autoload.el b/modules/completion/corfu/autoload.el index 5003d3b7c..43005c6c1 100644 --- a/modules/completion/corfu/autoload.el +++ b/modules/completion/corfu/autoload.el @@ -2,12 +2,29 @@ ;;;###autoload (defun +corfu-move-to-minibuffer () - ;; Taken from corfu's README. - ;; TODO: extend this to other completion front-ends. + "Move the current list of candidates to your choice of minibuffer completion UI." (interactive) - (let ((completion-extra-properties corfu--extra) - (completion-cycle-threshold completion-cycling)) - (apply #'consult-completion-in-region completion-in-region--data))) + (pcase completion-in-region--data + (`(,beg ,end ,table ,pred ,extras) + (let ((completion-extra-properties extras) + completion-cycle-threshold completion-cycling) + (cond ((and (modulep! :completion vertico) + (fboundp #'consult-completion-in-region)) + (consult-completion-in-region beg end table pred)) + ((and (modulep! :completion ivy) + (fboundp #'ivy-completion-in-region)) + (ivy-completion-in-region (marker-position beg) (marker-position end) table pred)) + ;; Important: `completion-in-region-function' is set to corfu at + ;; this moment, so `completion-in-region' (single -) doesn't work + ;; below. + ((modulep! :completion helm) + ;; Helm is special and wants to _wrap_ `completion--in-region' + ;; instead of replacing it in `completion-in-region-function'. + ;; But because the advice is too unreliable we "fake" the wrapping. + (helm--completion-in-region #'completion--in-region beg end table pred)) + ((modulep! :completion ido) + (completion--in-region beg end table pred)) + (t (error "No minibuffer completion UI available for moving to!"))))))) ;;;###autoload (defun +corfu-smart-sep-toggle-escape () From 4192c811130c2c2e73eeb83a9f24a9ba40f8b02f Mon Sep 17 00:00:00 2001 From: StrawberryTea Date: Wed, 28 Feb 2024 17:42:47 -0600 Subject: [PATCH 6/8] feat(corfu): make minibuffer completion optional --- modules/completion/corfu/README.org | 3 +++ modules/completion/corfu/config.el | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org index 7413f53a1..48d591c91 100644 --- a/modules/completion/corfu/README.org +++ b/modules/completion/corfu/README.org @@ -198,6 +198,9 @@ A few variables may be set to change behavior of this module: - [[var:+corfu-buffer-scanning-size-limit]] :: Sets the maximum buffer size to be scanned by ~cape-dabbrev~. Defaults to 1 MB. Set this if you are having performance problems using the CAPF. +- [[var:+corfu-want-minibuffer-completion]] :: + Whether to enable Corfu in the minibuffer. See its documentation for + additional tweaks. ** Adding CAPFs to a mode To add other CAPFs on a mode-per-mode basis, put either of the following in your diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el index bfa302546..8e9006765 100644 --- a/modules/completion/corfu/config.el +++ b/modules/completion/corfu/config.el @@ -11,6 +11,11 @@ Possible values are: (defvar +corfu-buffer-scanning-size-limit (* 1 1024 1024) ; 1 MB "Size limit for a buffer to be scanned by `cape-dabbrev'.") +(defvar +corfu-want-minibuffer-completion t + "Whether to enable Corfu in the minibuffer. +Setting this to `aggressive' will enable Corfu in more commands which +use the minibuffer such as `query-replace'.") + ;; ;;; Packages (use-package! corfu @@ -18,8 +23,20 @@ Possible values are: :init (add-hook! 'minibuffer-setup-hook (defun +corfu-enable-in-minibuffer () - "Enable Corfu in the minibuffer if `completion-at-point' is bound." - (when (where-is-internal #'completion-at-point (list (current-local-map))) + "Enable Corfu in the minibuffer." + (when (pcase +corfu-want-minibuffer-completion + ('aggressive + (not (or (bound-and-true-p mct--active) + (bound-and-true-p vertico--input) + (eq (current-local-map) read-passwd-map) + (and (featurep 'helm-core) (helm--alive-p)) + (and (featurep 'ido) (ido-active)) + (where-is-internal 'minibuffer-complete + (list (current-local-map))) + (memq #'ivy--queue-exhibit post-command-hook)))) + ('nil nil) + (_ (where-is-internal #'completion-at-point + (list (current-local-map))))) (setq-local corfu-echo-delay nil) (corfu-mode +1)))) :config From bfb9aabe27fbb89f9e3f31b306c0bc2a103f15fd Mon Sep 17 00:00:00 2001 From: StrawberryTea Date: Sat, 17 Feb 2024 10:47:35 -0600 Subject: [PATCH 7/8] feat(corfu): update minibuffer hints manually We need this advice to ensure that visual hints are updated before exiting. --- modules/completion/corfu/config.el | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el index 8e9006765..defdc8aaa 100644 --- a/modules/completion/corfu/config.el +++ b/modules/completion/corfu/config.el @@ -64,7 +64,26 @@ use the minibuffer such as `query-replace'.") (add-to-list 'corfu-auto-commands #'lispy-colon) (add-to-list 'corfu-continue-commands #'+corfu-move-to-minibuffer) (add-to-list 'corfu-continue-commands #'+corfu-smart-sep-toggle-escape) - (add-hook 'evil-insert-state-exit-hook #'corfu-quit)) + (add-hook 'evil-insert-state-exit-hook #'corfu-quit) + + ;; If you want to update the visual hints after completing minibuffer commands + ;; with Corfu and exiting, you have to do it manually. + (defadvice! +corfu--insert-before-exit-minibuffer-a () + :before #'exit-minibuffer + (when (or (and (frame-live-p corfu--frame) + (frame-visible-p corfu--frame)) + (and (featurep 'corfu-terminal) + (popon-live-p corfu-terminal--popon))) + (when (member isearch-lazy-highlight-timer timer-idle-list) + (apply (timer--function isearch-lazy-highlight-timer) + (timer--args isearch-lazy-highlight-timer))) + (when (member (bound-and-true-p anzu--update-timer) timer-idle-list) + (apply (timer--function anzu--update-timer) + (timer--args anzu--update-timer))) + (when (member (bound-and-true-p evil--ex-search-update-timer) + timer-idle-list) + (apply (timer--function evil--ex-search-update-timer) + (timer--args evil--ex-search-update-timer)))))) (use-package! cape :defer t From f9c0243211a4654347c7bb83daf5e80416739227 Mon Sep 17 00:00:00 2001 From: Luigi Sartor Piucco Date: Mon, 4 Mar 2024 14:17:56 -0300 Subject: [PATCH 8/8] docs(corfu): add @LemonBreezes as co-maintainer Co-authored-by: StrawberryTea --- modules/completion/corfu/README.org | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org index 48d591c91..e10d1b17d 100644 --- a/modules/completion/corfu/README.org +++ b/modules/completion/corfu/README.org @@ -17,6 +17,7 @@ utilities for fine-tuning. Only some of common behaviors are documented here. ** Maintainers - [[doom-user:][@LuigiPiucco]] +- [[doom-user:][@LemonBreezes]] [[doom-contrib-maintainer:][Become a maintainer?]]