branch: externals/corfu commit 435787cb60d5de71c5cf63578075f07ded3bdc9c Author: Daniel Mendler <m...@daniel-mendler.de> Commit: Daniel Mendler <m...@daniel-mendler.de>
Implement auto completion --- README.org | 52 ++++++++++++++++++++++------------------------------ corfu.el | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/README.org b/README.org index adf274f..48491a3 100644 --- a/README.org +++ b/README.org @@ -16,14 +16,13 @@ overlay. The current candidates are shown in a popup below or above the point. Corfu can be considered the minimalistic ~completion-in-region~ counterpart of the [[https://github.com/minad/vertico][Vertico]] minibuffer UI. -Corfu is a minimal package (~600 lines of code without whitespace and comments). -In contrast to the featureful and complex Company package, Corfu concentrates on -the completion UI and does not include custom completion backends. Completions -are either provided by commands like ~dabbrev-completion~ or by pluggable -backends (~completion-at-point-functions~, Capfs). Many programming language -major modes implement a Capf. Furthermore the language server packages, [[https://github.com/joaotavora/eglot][Eglot]] -and [[https://github.com/emacs-lsp/lsp-mode][Lsp-mode]], both use Capfs which talk to the LSP server to retrieve the -completions. +Corfu is a minimal package. In contrast to the featureful and complex Company +package, Corfu concentrates on the completion UI and does not include custom +completion backends. Completions are either provided by commands like +~dabbrev-completion~ or by pluggable backends (~completion-at-point-functions~, +Capfs). Many programming language major modes implement a Capf. Furthermore the +language server packages, [[https://github.com/joaotavora/eglot][Eglot]] and [[https://github.com/emacs-lsp/lsp-mode][Lsp-mode]], both use Capfs which talk to the +LSP server to retrieve the completions. *NOTE*: Corfu uses child frames to show the popup; on non-graphical displays it will fall back to the default setting of the ~completion-in-region-function~. @@ -32,6 +31,7 @@ will fall back to the default setting of the ~completion-in-region-function~. * Features +- Timer-based auto-completions - Popup display with scrollbar indicator and arrow key navigation - The popup must be summoned explicitly by pressing =TAB= - The current candidate is inserted with =TAB= and selected with =RET= @@ -44,9 +44,6 @@ will fall back to the default setting of the ~completion-in-region-function~. - Jumping to location/documentation of current candidate (Company extension) - Support for ~annotation-function~ and ~affixation-function~ -Notable non-feature: Timer-based auto-completions are not supported. There exists -an experimental implementation in the branch [[https://github.com/minad/corfu/tree/auto][auto]]. - * Configuration Corfu is available from [[http://elpa.gnu.org/packages/corfu.html][GNU ELPA]], such that it can be installed directly via @@ -60,6 +57,13 @@ Orderless is not a necessity. Here is an example configuration: #+begin_src emacs-lisp ;; Configure corfu (use-package corfu + ;; Optional customizations + ;; :custom + ;; (corfu-cycle t) ;; Enable cycling for `corfu-next/previous' + ;; (corfu-auto t) ;; Enable auto completion + ;; (corfu-quit-at-boundary t) ;; Automatically quit at word boundary + ;; (corfu-quit-no-match t) ;; Automatically quit if there is no match + ;; Optionally use TAB for cycling, default is `corfu-complete'. ;; :bind (:map corfu-map ;; ("TAB" . corfu-next) @@ -70,17 +74,10 @@ Orderless is not a necessity. Here is an example configuration: ;; (shell-mode . corfu-mode) ;; (eshell-mode . corfu-mode)) - ;; Recommended: Enable Corfu globally. ;; This is recommended since dabbrev can be used globally (M-/). :init - (corfu-global-mode) - - :config - - ;; Optionally enable cycling for `corfu-next' and `corfu-previous'. - ;; (setq corfu-cycle t) - ) + (corfu-global-mode)) ;; Optionally use the `orderless' completion style. ;; Enable `partial-completion' for files to allow path expansion. @@ -139,7 +136,6 @@ counterpart of Corfu. Corfu works in most scenarios. However there are a few known technical caveats. -- No support for idle completions (See branch =auto= for a possible implementation). - Corfu falls back to the default ~completion-in-region-function~ on non-graphical displays, since is displayed using child frames. - The abort handling could be improved, for example the input could be undone. @@ -151,16 +147,12 @@ Corfu works in most scenarios. However there are a few known technical caveats. * Alternative packages -The main alternative is the featureful [[https://github.com/company-mode/company-mode][Company]] package. In particular, Company -supports auto completion as you type. As of now, auto completion is not -supported by Corfu. Furthermore Company has support for multiple completion -backends. - -The Emacs builtin Icomplete is technically comparable to Corfu and Vertico. -Icomplete implements both ~completion-in-region~ and minibuffer completion in a -single package. Corfu and Vertico are two separate packages in order to optimize -the UI for the two distinct use cases, also leading to code bases which are -easier to understand. +The main alternative is the featureful and complex [[https://github.com/company-mode/company-mode][Company]] package. The Emacs +builtin Icomplete is technically comparable to Corfu and Vertico. Icomplete +implements both ~completion-in-region~ and minibuffer completion in a single +package. Corfu and Vertico are two separate packages in order to optimize the UI +for the two distinct use cases, also leading to code bases which are easier to +understand. * Contributions diff --git a/corfu.el b/corfu.el index a39b716..d1ede4a 100644 --- a/corfu.el +++ b/corfu.el @@ -80,6 +80,18 @@ filter string with spaces is allowed." "Width of the bar in units of the character width." :type 'float) +(defcustom corfu-auto-prefix 3 + "Minimum length of prefix for auto completion." + :type 'integer) + +(defcustom corfu-auto-delay 0.2 + "Delay for auto completion." + :type 'float) + +(defcustom corfu-auto nil + "Enable auto completion." + :type 'boolean) + (defgroup corfu-faces nil "Faces used by Corfu." :group 'corfu @@ -135,6 +147,9 @@ filter string with spaces is allowed." map) "Corfu keymap used when popup is shown.") +(defvar corfu--auto-timer nil + "Auto completion timer.") + (defvar-local corfu--candidates nil "List of candidates.") @@ -162,6 +177,10 @@ filter string with spaces is allowed." (defvar corfu--frame nil "Popup frame.") +(defvar corfu--auto-commands + "\\`\\(.*self-insert-command\\)\\'" + "Commands which initiate auto completion.") + (defvar corfu--continue-commands ;; nil is undefined command "\\`\\(nil\\|completion-at-point\\|corfu-.*\\|scroll-other-window.*\\)\\'" @@ -705,10 +724,10 @@ filter string with spaces is allowed." ;; XXX Warning this can result in an endless loop when `completion-in-region-function' ;; is set *globally* to `corfu--completion-in-region'. This should never happen. (apply (default-value 'completion-in-region-function) args) - ;; Prevent restarting the completion. This can happen for example if C-M-/ + ;; Restart the completion. This can happen for example if C-M-/ ;; (`dabbrev-completion') is pressed while the Corfu popup is already open. (when (and completion-in-region-mode (not completion-cycling)) - (user-error "Completion is already in progress")) + (corfu-quit)) (let ((completion-show-inline-help) (completion-auto-help) ;; XXX Disable original predicate check, keep completion alive when @@ -721,12 +740,49 @@ filter string with spaces is allowed." (prog1 (apply #'completion--in-region args) (corfu--setup))))) +(defun corfu--auto-complete (buffer) + "Initiate auto completion after delay in BUFFER." + (setq corfu--auto-timer nil) + (when (and (not completion-in-region-mode) + (eq (current-buffer) buffer)) + (pcase (run-hook-wrapped 'completion-at-point-functions + #'completion--capf-wrapper 'all) + ((and `(,fun ,beg ,end ,table . ,plist) (guard (>= (- end beg) corfu-auto-prefix))) + (let ((completion-extra-properties plist) + (completion-in-region-mode-predicate + (if corfu-quit-at-boundary + (lambda () + (when-let (newbeg (car-safe (funcall fun))) + (= newbeg beg))) + (lambda () t)))) + (setq completion-in-region--data `(,(copy-marker beg) ,(copy-marker end t) + ,table ,(plist-get plist :predicate))) + (completion-in-region-mode 1) + (corfu--setup) + (corfu--post-command)))))) + +(defun corfu--auto-post-command () + "Post command hook which initiates auto completion." + (when corfu--auto-timer + (cancel-timer corfu--auto-timer) + (setq corfu--auto-timer nil)) + (when (and (not completion-in-region-mode) + (display-graphic-p) + (symbolp this-command) + (string-match-p corfu--auto-commands (symbol-name this-command))) + (setq corfu--auto-timer (run-with-idle-timer corfu-auto-delay nil + #'corfu--auto-complete + (current-buffer))))) + ;;;###autoload (define-minor-mode corfu-mode "Completion Overlay Region FUnction" :global nil (if corfu-mode - (setq-local completion-in-region-function #'corfu--completion-in-region) + (progn + (and corfu-auto (add-hook 'post-command-hook #'corfu--auto-post-command nil 'local)) + (setq-local completion-in-region-function #'corfu--completion-in-region)) + (remove-hook 'post-command-hook #'corfu--auto-post-command 'local) (kill-local-variable 'completion-in-region-function))) ;;;###autoload