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