diff --git a/modules/completion/corfu/README.org b/modules/completion/corfu/README.org new file mode 100644 index 000000000..224d6959c --- /dev/null +++ b/modules/completion/corfu/README.org @@ -0,0 +1,182 @@ +#+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). +- +dabbrev :: + Enable and configure [[doom-package:dabbrev]] as a nigh on everywhere CAPF + fallback. +- +dict :: + Enable and configure dictionary completion for text modes and related regions + in programming modes. +- +emoji :: + Enable and configure emoji completion via the emoji input method. + +** Packages +- [[doom-package:corfu]] +- [[doom-package:cape]] +- [[doom-package:nerd-icons-completion]] if [[doom-module::completion corfu +icons]] +- [[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./ + +** 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. Snippets may also appear in the candidate list if available. + +* 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:][M-m]] | Export to minibuffer (if [[doom-module::completion vertico]]) | +| [[kbd:][M-S-j]] | (evil) Export to minibuffer (if [[doom-module::completion vertico]]) | +| [[kbd:][RET]] | Insert candidate | +| [[kbd:][SPC]] | (after wildcard) Reset completion | +| [[kbd:][DEL]] | Reset completion | +| [[kbd:][C-SPC]] | Complete (unless [[doom-module::completion corfu +tng]]) | +| [[kbd:][C-SPC]] | (when completing) Insert separator (see below) | + +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 or ~,~ 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. + +Furthermore, if you also have [[var:+orderless-wildcard-character]] set (by default +it's the comma key), then that character acts as a wildcard when typed +mid-completion. + +** 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:+orderless-wildcard-character]] :: + Used for fuzzy-matching corfu invocations as an escapable alternative to + ~corfu-separator~. Defaults to comma. +- [[var:+cape-buffer-scanning-size-limit:]] :: + Sets the maximum buffer size to be scanned by ~cape-dabbrev~ and + ~cape-lines~. Defaults to 1 MB. Set this if you are having performance + problems using ~cape-dabbrev~. + +** 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-hook 'completion-at-point-functions #'some-capf depth t)) +;; OR, but note the different call signature +(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 of ommited. Also +see ~add-hook!~'s documentation for additional ways to call it. ~add-hook~ only +accepts the quoted arguments form above. + +* Troubleshooting +[[doom-report:][Report an issue?]] + +If you have performance issues with ~cape-dabbrev~, the first thing I recommend +doing is looking at the list of buffers Dabbrev is scanning with: + +#+begin_src emacs-lisp +(dabbrev--select-buffers) ; => (# #> # ...) +(length (dabbrev--select-buffers)) ; => 37 +#+end_src + +and modifying ~dabbrev-ignored-buffer-regexps~ or ~dabbrev-ignored-buffer-modes~ +accordingly. + +* 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..215e5bd41 --- /dev/null +++ b/modules/completion/corfu/autoload.el @@ -0,0 +1,18 @@ +;;; completion/corfu/autoload/commands.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))) + +;;;###autoload +(defun +corfu-insert-wildcard-separator () + ;; I had to rename this command so that it doesn't start with "corfu-". + ;; Otherwise, it does not insert the completion when +tng is enabled. + (interactive) + (setq this-command #'corfu-insert-separator) + (call-interactively #'corfu-insert-separator)) diff --git a/modules/completion/corfu/config.el b/modules/completion/corfu/config.el new file mode 100644 index 000000000..73013c51c --- /dev/null +++ b/modules/completion/corfu/config.el @@ -0,0 +1,291 @@ +;;; completion/corfu/config.el -*- lexical-binding: t; -*- + +(defvar +cape-buffer-scanning-size-limit (* 1 1024 1024) ; 1 MB + "Size limit for a buffer to be scanned by `cape-line' or `cape-dabbrev'. + +As an exception, `cape-line' will also scan buffers with the same +major mode regardless of size.") + +;; +;;; Packages +(use-package! corfu + :hook (doom-first-buffer . global-corfu-mode) + :hook (org-mode . corfu-mode) + :init + ;; Auto-completion settings, must be set before calling `global-corfu-mode'. + ;; Due to lazy-loading, overriding these in config.el works too. + (setq corfu-auto t + corfu-auto-delay 0.1 + corfu-auto-prefix 2 + corfu-separator ?\s + corfu-excluded-modes '(erc-mode + circe-mode + help-mode + gud-mode + vterm-mode)) + ;; `:g' is needed here to prevent `global-map' from overriding this with + ;; `set-mark-command'. + (map! :unless (modulep! +tng) :gi "C-SPC" #'completion-at-point) + :config + (setq corfu-cycle t + corfu-preselect (if (modulep! :completion corfu +tng) 'prompt t) + corfu-count 16 + corfu-max-width 120 + corfu-preview-current 'insert + corfu-on-exact-match nil + corfu-quit-at-boundary 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)) + + (defun corfu-disable-in-minibuffer-p () + (or (bound-and-true-p mct--active) + (bound-and-true-p vertico--input) + (and (featurep 'helm-core) (helm--alive-p)) + (eq (current-local-map) read-passwd-map))) + + (defun corfu-enable-in-minibuffer () + "Enable Corfu in the minibuffer if `completion-at-point' is bound." + (unless (corfu-disable-in-minibuffer-p) + (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup + corfu-popupinfo-delay nil) + (corfu-mode +1))) + + (add-hook 'minibuffer-setup-hook #'corfu-enable-in-minibuffer) + + (defun corfu-visible-p () + (or (and (frame-live-p corfu--frame) + (frame-visible-p corfu--frame)) + (and (featurep 'corfu-terminal) + (popon-live-p corfu-terminal--popon)))) + + ;; 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 (corfu-visible-p) + (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) + ;; Pending a PR I (@LemonBreezes) am making to expose `anzu--update-timer'. + (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))))) + + ;; Allow completion after `:' in Lispy. + (add-to-list 'corfu-auto-commands #'lispy-colon) + + (when (modulep! +orderless) + (after! orderless + (setq orderless-component-separator #'orderless-escapable-split-on-space))) + + (add-hook! 'evil-insert-state-exit-hook + (defun +corfu-quit-on-evil-insert-state-exit-h () + ;; This predicate a workaround for unexpected calls to `corfu-quit' in + ;; :company-doc-buffer buffers. This was specifically happening when using + ;; `yasnippet-capf' and `company-yasnippet'. + (when (eq (current-buffer) (window-buffer (selected-window))) + (corfu-quit)))) + + (when (modulep! +icons) + (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter)) + + (defun +corfu--reset-or-passthrough (cmd) + (when (and (modulep! +tng) + (> corfu--index -1) + (eq corfu-preview-current 'insert)) + cmd)) + (defun +corfu--backward-toggle-escape-sep (cmd) + (save-excursion + (backward-char 1) + (if (looking-back "\\\\" -1) + #'corfu-reset + (lambda () + (interactive) + (save-excursion + (backward-char 1) + (insert-char ?\\)))))) + (defun +corfu--insert-separator-or-toggle-escape (cmd) + (if (char-equal (char-before) corfu-separator) + (+corfu--backward-toggle-escape-sep cmd) + cmd)) + (let ((mi-del '(menu-item "corfu-reset-or-passthrough" corfu-reset + :filter +corfu--maybe-reset-backspace-filter)) + (mi-c-spc '(menu-item "corfu-insert-separator-or-toggle-escape" corfu-insert-separator + :filter +corfu--insert-separator-or-toggle-escape))) + (map! :map corfu-map + [return] #'corfu-insert + "RET" #'corfu-insert + (:when (modulep! +orderless) + :gi "C-SPC" mi-c-spc) + (:when (modulep! +tng) + [tab] #'corfu-next + [backtab] #'corfu-previous + "TAB" #'corfu-next + "S-TAB" #'corfu-previous + [backspace] mi-del + "DEL" mi-del))) + + (after! vertico + (map! :map corfu-map + "M-m" #'corfu-move-to-minibuffer + (:when (modulep! :editor evil) "M-J" #'corfu-move-to-minibuffer)))) + +(use-package! cape + :commands + cape-abbrev + cape-dabbrev + cape-dict + cape-elisp-block + cape-elisp-symbol + cape-emoji + cape-file + cape-history + cape-keyword + cape-line + cape-rfc1345 + cape-sgml + cape-tex + cape-company-to-capf + cape-capf-super + cape-capf-buster + cape-capf-accept-all + cape-capf-debug + cape-capf-silent + cape-capf-purify + cape-capf-nonexclusive + cape-capf-noninterruptable + cape-capf-properties + cape-capf-predicate + cape-capf-prefix-length + cape-capf-inside-comment + cape-capf-inside-string + cape-capf-inside-faces + cape-capf-interactive + cape-wrap-buster + cape-wrap-accept-all + cape-wrap-debug + cape-wrap-silent + cape-wrap-purify + cape-wrap-nonexclusive + cape-wrap-noninterruptable + cape-wrap-properties + cape-wrap-predicate + cape-wrap-prefix-length + cape-wrap-inside-comment + cape-wrap-inside-string + cape-wrap-inside-faces + cape-interactive + :init + (add-hook! prog-mode + (add-hook 'completion-at-point-functions #'cape-file -10 t)) + (add-hook! (org-mode markdown-mode) + (add-hook 'completion-at-point-functions #'cape-elisp-block 0 t)) + + ;; Enable Dabbrev completion basically everywhere as a fallback. + (when (modulep! +dabbrev) + ;; Set up `cape-dabbrev' options. + (defun +dabbrev-friend-buffer-p (other-buffer) + (< (buffer-size other-buffer) +cape-buffer-scanning-size-limit)) + (setq cape-dabbrev-check-other-buffers t + dabbrev-friend-buffer-function #'+dabbrev-friend-buffer-p + dabbrev-ignored-buffer-regexps + '("\\.\\(?:pdf\\|jpe?g\\|png\\|svg\\|eps\\)\\'" + "^ " + "\\(TAGS\\|tags\\|ETAGS\\|etags\\|GTAGS\\|GRTAGS\\|GPATH\\)\\(<[0-9]+>\\)?") + dabbrev-upcase-means-case-search t) + (add-hook! (prog-mode text-mode conf-mode comint-mode minibuffer-setup + eshell-mode) + (add-hook 'completion-at-point-functions #'cape-dabbrev 20 t))) + (when (modulep! +line) + ;; Set up `cape-line' options. + (defun +cape-line-buffers () + (cl-loop for buf in (buffer-list) + if (or (eq major-mode (buffer-local-value 'major-mode buf)) + (< (buffer-size buf) +cape-buffer-scanning-size-limit)) + collect buf)) + (setq cape-line-buffer-function #'+cape-line-buffers) + (add-hook! (text-mode comint-mode minibuffer-setup) + (add-hook 'completion-at-point-functions #'cape-line 20 t))) + + ;; Complete emojis :). + (when (and (modulep! +emoji) (> emacs-major-version 28)) + (add-hook! (prog-mode conf-mode) + (add-hook 'completion-at-point-functions + (cape-capf-inside-faces + (cape-capf-prefix-length #'cape-emoji 1) + ;; Only call inside comments and docstrings. + 'tree-sitter-hl-face:doc 'font-lock-doc-face + 'font-lock-comment-face 'tree-sitter-hl-face:comment) + 10 t)) + (add-hook! text-mode + (add-hook 'completion-at-point-functions + (cape-capf-prefix-length #'cape-emoji 1) 10 t))) + + ;; Enable dictionary-based autocompletion. + (when (modulep! +dict) + (add-hook! text-mode + (add-hook 'completion-at-point-functions #'cape-dict 40 t)) + (add-hook! (prog-mode conf-mode) + (add-hook 'completion-at-point-functions + (cape-capf-inside-faces + ;; Only call inside comments and docstrings. + #'cape-dict 'tree-sitter-hl-face:doc 'font-lock-doc-face + 'font-lock-comment-face 'tree-sitter-hl-face:comment) + 40 t))) + + ;; Make these capfs composable. + (advice-add #'comint-completion-at-point :around #'cape-wrap-nonexclusive) + (advice-add #'eglot-completion-at-point :around #'cape-wrap-nonexclusive) + (advice-add #'lsp-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)) + + (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible)) + +(use-package! yasnippet-capf + :when (modulep! :editor snippets) + :defer t + :init + (add-hook! yas-minor-mode + (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)) + +;; +;;; 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)) + (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! :when (modulep! :editor evil) + :map corfu-popupinfo-map + ;; 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..0107b7278 --- /dev/null +++ b/modules/completion/corfu/packages.el @@ -0,0 +1,14 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/corfu/packages.el + +(package! corfu :recipe (:files ("*.el" "extensions/*.el")) :pin "457042d486e7542b9a6a832e47e6833d217ffd47") +(package! cape :pin "abacb231157e0c90e29bdda6d15b4b448e48ffbd") +(when (modulep! +icons) + (package! nerd-icons-completion :pin "c2db8557a3c1a9588d111f8c8e91cae96ee85010") + (package! nerd-icons-corfu :pin "7077bb76fefc15aed967476406a19dc5c2500b3c")) +(when (modulep! +orderless) + (package! orderless :pin "89eb3775daa53cfb52ad03015410c23f28c72d30")) +(when (modulep! :os tty) + (package! corfu-terminal :pin "501548c3d51f926c687e8cd838c5865ec45d03cc")) +(when (modulep! :editor snippets) + (package! yasnippet-capf :pin "a0a6b1c2bb6decdad5cf9b74202f0042f494a6ab")) diff --git a/modules/completion/vertico/config.el b/modules/completion/vertico/config.el index a4efa7c4e..ac4bbad05 100644 --- a/modules/completion/vertico/config.el +++ b/modules/completion/vertico/config.el @@ -98,6 +98,7 @@ orderless." ;; Flex matching ((string-prefix-p "~" pattern) `(orderless-flex . ,(substring pattern 1))) ((string-suffix-p "~" pattern) `(orderless-flex . ,(substring pattern 0 -1))))) + ;; TODO: Find a way to deduplicate this code from the corfu module. (add-to-list 'completion-styles-alist '(+vertico-basic-remote @@ -110,7 +111,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/completion/vertico/packages.el b/modules/completion/vertico/packages.el index 68f1b73e9..947c8b7a5 100644 --- a/modules/completion/vertico/packages.el +++ b/modules/completion/vertico/packages.el @@ -6,7 +6,7 @@ :files ("*.el" "extensions/*.el")) :pin "a28370d07f35c5387c7a9ec2e5b67f0d4598058d") -(package! orderless :pin "e6784026717a8a6a7dcd0bf31fd3414f148c542e") +(package! orderless :pin "89eb3775daa53cfb52ad03015410c23f28c72d30") (package! consult :pin "fe49dedd71802ff97be7b89f1ec4bd61b98c2b13") (package! consult-dir :pin "ed8f0874d26f10f5c5b181ab9f2cf4107df8a0eb") 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)