From dd856e4523f9c547d4a4a9ee21ad806c7bbae737 Mon Sep 17 00:00:00 2001 From: Luigi Sartor Piucco Date: Sat, 24 Sep 2022 17:33:32 -0300 Subject: [PATCH] module: add :completion corfu This commit's primary goal is allowing use of [minad/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 [minad/cape](https://github.com/minad/cape) capfs for certain modes. Other optional or miscellaneous features include: - Support for displaying the completion's documentation on a secondary popup; - Support for terminal display if :os tty; - Support for icons if +icons; - Support for tab-and-go completion if +tng; --- modules/completion/corfu/README.org | 139 +++++++++++++++++++ modules/completion/corfu/config.el | 192 +++++++++++++++++++++++++++ modules/completion/corfu/packages.el | 11 ++ modules/tools/lsp/+lsp.el | 5 +- 4 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 modules/completion/corfu/README.org 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..f98c13675 --- /dev/null +++ b/modules/completion/corfu/README.org @@ -0,0 +1,139 @@ +#+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. + +** Maintainers +- [[doom-user:][@LuigiPiucco]] + +[[doom-contrib-maintainer:][Become a maintainer?]] + +** Module flags +- +icons :: + Display icons beside completion suggestions. +- +tng :: + Invoke completion on [[kbd:][TAB]]. When corfu is active, [[kbd:][TAB]] and [[kbd:][S-TAB]] will navigate + the completion candidates. Arrow keys and evil-style movement are still + supported. +- +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-completion]] 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 + +** Code completion +By default, completion gets triggered after typing 2 non-space consecutive +characters, or by means of the [[kbd:][C-SPC]] keybinding at any moment. While the popup +is visible, the following relevant keys are available: + +| Keybind | Description | +|----------+------------------------------------------------------| +| [[kbd:][]] | Go to next candidate | +| [[kbd:][]] | Go to previous candidate | +| [[kbd:][C-n]] | Go to next candidate | +| [[kbd:][C-p]] | 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-n]] | Go to next doc line | +| [[kbd:][C-S-p]] | 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 | +| [[kbd:][C-h]] | Toggle documentation (if available) | +| [[kbd:][s-]] | Export to minibuffer (if [[doom-module::completion vertico]]) | +| [[kbd:][s-j]] | (evil) Export to minibuffer (if [[doom-module::completion vertico]]) | +| [[kbd:][RET]] | Insert candidate | +| [[kbd:][C-SPC]] | (when completing) Insert separator (see below) | +| [[kbd:][C-SPC]] | Complete (unless [[doom-module::completion corfu +tng]]) | + +If you prefer a [[kbd:][TAB]]-centric completion style, enable the [[doom-module::completion corfu +tng]] +flag so that, instead, you trigger completion with [[kbd:][TAB]], getting the following +additional binds: + +| Keybind | Description | +|---------+--------------------------------------------| +| [[kbd:][TAB]] | Complete | +| [[kbd:][TAB]] | (when completing) Go to next candidate | +| [[kbd:][S-TAB]] | (when completing) Go to previous candidate | + +** Searching with multiple keywords +If the [[doom-module::completion corfu +orderless]] flag is enabled, users can +perform code completion with multiple search keywords by use of space as +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 restarts completion, so that +when typing sentences it doesn't try to complete the whole sentence instead of +just the word. + +** 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: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. Its default is nil, and it's + recommended to leave it at that. Otherwise, single matches on snippet keys + expand immediately. +- [[var:+corfu-completion-styles]] :: + Used to override [[var:completion-styles]] for corfu invocations, such that it + can have a value separate from, say, [[doom-package:consult]]. +- [[var:+corfu-icon-mapping]] :: + Configures icons used for each completion. See its documentation for details. + +* 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/config.el b/modules/completion/corfu/config.el new file mode 100644 index 000000000..66d2ef12b --- /dev/null +++ b/modules/completion/corfu/config.el @@ -0,0 +1,192 @@ +;;; completion/corfu/config.el -*- lexical-binding: t; -*- + +(defvar +corfu-completion-styles '(basic partial-completion flex) + "Completion styles for corfu to use. + +If the user enables +orderless, `orderless' is automatically appended to this +list before fowarding to `completion-styles'.") + +(defvar +corfu-icon-mapping + `((array ,(nerd-icons-codicon "nf-cod-symbol_array") :face font-lock-type-face) + (boolean ,(nerd-icons-codicon "nf-cod-symbol_boolean") :face font-lock-builtin-face) + (class ,(nerd-icons-codicon "nf-cod-symbol_class") :face font-lock-type-face) + (color ,(nerd-icons-codicon "nf-cod-symbol_color") :face success) + (command ,(nerd-icons-codicon "nf-cod-terminal") :face default) + (constant ,(nerd-icons-codicon "nf-cod-symbol_constant") :face font-lock-constant-face) + (constructor ,(nerd-icons-codicon "nf-cod-triangle_right") :face font-lock-function-name-face) + (enummember ,(nerd-icons-codicon "nf-cod-symbol_enum_member") :face font-lock-builtin-face) + (enum-member ,(nerd-icons-codicon "nf-cod-symbol_enum_member") :face font-lock-builtin-face) + (enum ,(nerd-icons-codicon "nf-cod-symbol_enum") :face font-lock-builtin-face) + (event ,(nerd-icons-codicon "nf-cod-symbol_event") :face font-lock-warning-face) + (field ,(nerd-icons-codicon "nf-cod-symbol_field") :face font-lock-variable-name-face) + (file ,(nerd-icons-codicon "nf-cod-symbol_file") :face font-lock-string-face) + (folder ,(nerd-icons-codicon "nf-cod-folder") :face font-lock-doc-face) + (interface ,(nerd-icons-codicon "nf-cod-symbol_interface") :face font-lock-type-face) + (keyword ,(nerd-icons-codicon "nf-cod-symbol_keyword") :face font-lock-keyword-face) + (macro ,(nerd-icons-codicon "nf-cod-symbol_misc") :face font-lock-keyword-face) + (magic ,(nerd-icons-codicon "nf-cod-wand") :face font-lock-builtin-face) + (method ,(nerd-icons-codicon "nf-cod-symbol_method") :face font-lock-function-name-face) + (function ,(nerd-icons-codicon "nf-cod-symbol_method") :face font-lock-function-name-face) + (module ,(nerd-icons-codicon "nf-cod-file_submodule") :face font-lock-preprocessor-face) + (numeric ,(nerd-icons-codicon "nf-cod-symbol_numeric") :face font-lock-builtin-face) + (operator ,(nerd-icons-codicon "nf-cod-symbol_operator") :face font-lock-comment-delimiter-face) + (param ,(nerd-icons-codicon "nf-cod-symbol_parameter") :face default) + (property ,(nerd-icons-codicon "nf-cod-symbol_property") :face font-lock-variable-name-face) + (reference ,(nerd-icons-codicon "nf-cod-references") :face font-lock-variable-name-face) + (snippet ,(nerd-icons-codicon "nf-cod-symbol_snippet") :face font-lock-string-face) + (string ,(nerd-icons-codicon "nf-cod-symbol_string") :face font-lock-string-face) + (struct ,(nerd-icons-codicon "nf-cod-symbol_structure") :face font-lock-variable-name-face) + (text ,(nerd-icons-codicon "nf-cod-text_size") :face font-lock-doc-face) + (typeparameter ,(nerd-icons-codicon "nf-cod-list_unordered") :face font-lock-type-face) + (type-parameter ,(nerd-icons-codicon "nf-cod-list_unordered") :face font-lock-type-face) + (unit ,(nerd-icons-codicon "nf-cod-symbol_ruler") :face font-lock-constant-face) + (value ,(nerd-icons-codicon "nf-cod-symbol_field") :face font-lock-builtin-face) + (variable ,(nerd-icons-codicon "nf-cod-symbol_variable") :face font-lock-variable-name-face) + (t ,(nerd-icons-codicon "nf-cod-code") :face font-lock-warning-face)) + "Mapping of completion kinds to icons. + +It should be a list of elements with the form (KIND ICON-TXT [:face FACE]). +KIND is a symbol determining what the completion is, and comes from calling the +`:company-kind' property of the completion. ICON-TXT is a string with the icon +to use, usually as a character from the `nerd-icons' symbol font. See that +package for how to get these. Note that it can be simple text if that is +preferred. FACE, if present, is applied to the icon, mainly for its color. The +special `t' symbol should be used for KIND to represent the default icon, and +must be present.") + +;; +;;; Packages +(use-package! corfu + :hook ((doom-first-buffer . global-corfu-mode) + (org-mode . corfu-mode)) + :init + ;; Auto-completion settings, must be set before calling `global-corfu-mode'. + ;; Due to lazy-loading, setting them in config.el works too. + (setq corfu-auto t + corfu-auto-delay 0.1 + corfu-auto-prefix 2 + corfu-excluded-modes '(erc-mode + circe-mode + help-mode + gud-mode + vterm-mode)) + :config + (setq corfu-cycle t + corfu-separator (when (modulep! +orderless) ?\s) + corfu-preselect t + 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) + ;; In the case of +tng, TAB should be smart regarding completion; + ;; However, it should otherwise behave like normal, whatever normal was. + tab-always-indent (if (modulep! +tng) 'complete tab-always-indent)) + + (when (modulep! +orderless) + (after! 'lsp-mode + (add-to-list 'completion-category-overrides + `(lsp-capf (styles ,@+corfu-completion-styles ,(when (modulep! +orderless) 'orderless))))) + (after! 'eglot + (add-to-list 'completion-category-overrides + `(eglot (styles ,@+corfu-completion-styles ,(when (modulep! +orderless) 'orderless)))))) + + ;; For the icons, we use a custom margin formatter, which simply reads the + ;; mapping in `+corfu-icon-mapping'. + (when (modulep! +icons) + (defun icon-margin-formatter (metadata) + (when-let ((kindfunc (or (plist-get completion-extra-properties :company-kind) + (assq 'company-kind metadata)))) + (lambda (cand) + (let* ((kind (funcall kindfunc cand)) + (icon-entry (assq (or kind t) +corfu-icon-mapping)) + (str (cadr icon-entry)) + (props (cddr icon-entry)) + (extra-face (plist-get props :face)) + (space (propertize " " 'display '(space :width 1))) + (str (concat " " str space))) + (when extra-face + (put-text-property 0 3 'face extra-face str)) + str)))) + (setq corfu-margin-formatters '(icon-margin-formatter))) + + ;; This is to decouple the use of `completion-styles' in corfu from other + ;; completion packages, such as vertico. That way, the user can leave the + ;; global value of the variable alone, say, to be used by the default + ;; front-end or consult. The vertico module also does something similar with + ;; `+vertico-company-completion-styles'. + (defadvice! +corfu--completion-styles (orig &rest args) + "Try default completion styles before orderless. + +Meant as :around advice for `corfu--recompute'." + :around #'corfu--recompute + (let ((completion-styles + (append +corfu-completion-styles (when (modulep! +orderless) + '(orderless)))) + completion-category-overrides completion-category-defaults) + (apply orig args))) + + (map! (:unless (modulep! +tng) + "C-SPC" #'completion-at-point) + (:map 'corfu-map + (:when (modulep! +orderless) + "C-SPC" #'corfu-insert-separator) + (:when (modulep! +tng) + [tab] #'corfu-next + [backtab] #'corfu-previous + "TAB" #'corfu-next + "S-TAB" #'corfu-previous))) + (after! evil-collection-corfu + (evil-collection-define-key 'insert 'corfu-map + (kbd "RET") #'corfu-insert + [return] #'corfu-insert)) + + (after! vertico + ;; Taken from corfu's README. + ;; TODO: extend this to other completion front-ends. + (defun corfu-move-to-minibuffer () + (interactive) + (let ((completion-extra-properties corfu--extra) + (completion-cycle-threshold completion-cycling)) + (apply #'consult-completion-in-region completion-in-region--data))) + (map! :map 'corfu-map "s-" #'corfu-move-to-minibuffer + (:when (modulep! :editor evil) "s-j" #'corfu-move-to-minibuffer)))) + +(defmacro +corfu--add-capf! (capf) + "Create sexp to add CAPF to the list of CAPFs." + `(add-to-list 'completion-at-point-functions ,capf)) +(use-package! cape + :after corfu + :config + (add-hook! prog-mode (+corfu--add-capf! #'cape-file)) + (add-hook! (org-mode markdown-mode) (+corfu--add-capf! #'cape-elisp-block)) + (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible)) + +(use-package! corfu-terminal + :when (not (display-graphic-p)) + :hook (corfu-mode . corfu-terminal-mode)) + +;; +;;; Extensions +(use-package! corfu-history + :after savehist + :hook (corfu-mode . corfu-history-mode) + :config + (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)) + (map! (:map 'corfu-map + "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-h" #'corfu-popupinfo-toggle) + (:map 'corfu-popupinfo-map + :when (modulep! :editor evil) + ;; 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))) diff --git a/modules/completion/corfu/packages.el b/modules/completion/corfu/packages.el new file mode 100644 index 000000000..7111813ae --- /dev/null +++ b/modules/completion/corfu/packages.el @@ -0,0 +1,11 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/corfu/packages.el + +(package! corfu :recipe (:files ("*.el" "extensions/*.el"))) +(package! cape) +(when (modulep! +icons) + (package! nerd-icons-completion)) +(when (modulep! +orderless) + (package! orderless)) +(when (modulep! :os tty) + (package! corfu-terminal)) 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)