branch: externals/company commit 24c804393eaebe2e6919c1d9f7fbebfbc76d6eae Merge: 6c7731d4ec 566c273678 Author: Dmitry Gutov <dmi...@gutov.dev> Commit: GitHub <nore...@github.com>
Merge pull request #1471 from company-mode/async_no_cache_flicker No-flicker with no-cache+async and while-no-input in company-capf --- NEWS.md | 8 ++++++++ company-capf.el | 18 ++++++++++++++--- company.el | 59 ++++++++++++++++++++++++++++++++---------------------- test/capf-tests.el | 27 ++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/NEWS.md b/NEWS.md index 7a5085ff2f..093f2bb3d7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,14 @@ # Next +* Improved behavior when user types new character while completion is being + computed: better performance, less blinking (in the rare cases when it still + happened). The improvement extends to native async backends and to + `company-capf`. +* As such `company-capf` now interrupts computation on new user + input. Completion tables that are incompatible with this behavior should get + updated: bind `inhibit-quit` to non-nil around their sensitive sections, or + simply around the whole implementation (as a fallback). * `company-elisp` has been removed. It's not needed since Emacs 24.4, with all of its features having been incorporated into the built-in Elisp completion. * `company-files` shows shorter completions. Previously, the popup spanned diff --git a/company-capf.el b/company-capf.el index 0173a611cc..2543f3d8d2 100644 --- a/company-capf.el +++ b/company-capf.el @@ -1,6 +1,6 @@ ;;; company-capf.el --- company-mode completion-at-point-functions backend -*- lexical-binding: t -*- -;; Copyright (C) 2013-2023 Free Software Foundation, Inc. +;; Copyright (C) 2013-2024 Free Software Foundation, Inc. ;; Author: Stefan Monnier <monn...@iro.umontreal.ca> @@ -189,9 +189,10 @@ so we can't just use the preceding variable instead.") table pred)))) (company-capf--save-current-data res meta) (when res - (let* ((candidates (completion-all-completions input table pred + (let* ((candidates (company-capf--candidates-1 input table pred (length input) - meta)) + meta + non-essential)) (sortfun (cdr (assq 'display-sort-function meta))) (last (last candidates)) (base-size (and (numberp (cdr last)) (cdr last)))) @@ -207,6 +208,17 @@ so we can't just use the preceding variable instead.") candidates)) candidates))))) +(defun company-capf--candidates-1 (input table pred len meta interrupt-on-input) + (if (not interrupt-on-input) + (completion-all-completions input table pred len meta) + (let (res) + (and (while-no-input + (setq res + (completion-all-completions input table pred len meta)) + nil) + (throw 'interrupted 'new-input)) + res))) + (defun company--capf-post-completion (arg) (let* ((res company-capf--current-completion-data) (exit-function (plist-get (nthcdr 4 res) :exit-function)) diff --git a/company.el b/company.el index 9b518347c3..fb564f1d25 100644 --- a/company.el +++ b/company.el @@ -1411,7 +1411,10 @@ be recomputed when this value changes." (defvar-local company-selection-changed nil) (defvar-local company--manual-action nil - "Non-nil, if manual completion took place.") + "Non-nil if manual completion was performed by the user.") + +(defvar-local company--manual-now nil + "Non-nil if manual completion is being performed now.") (defvar-local company--manual-prefix nil) @@ -1591,12 +1594,11 @@ update if FORCE-UPDATE." 'snippet)))) (defun company--fetch-candidates (prefix) - (let* ((non-essential (not (company-explicit-action-p))) + (let* ((non-essential (not company--manual-now)) (inhibit-redisplay t) - (c (if (or company-selection-changed - ;; FIXME: This is not ideal, but we have not managed to deal - ;; with these situations in a better way yet. - (company-require-match-p)) + ;; At least we need "fresh" completions if the current command will + ;; rely on the result (e.g. insert common, or finish completion). + (c (if company--manual-now (company-call-backend 'candidates prefix) (company-call-backend-raw 'candidates prefix)))) (if (not (eq (car c) :async)) @@ -1617,9 +1619,11 @@ update if FORCE-UPDATE." (while (member (car unread-command-events) '(company-foo (t . company-foo))) (pop unread-command-events)) - (prog1 - (and (consp res) res) - (setq res 'exited)))))) + (let ((res-was res)) + (setq res 'exited) + (if (eq 'none res-was) + (throw 'interrupted 'new-input) + res-was)))))) (defun company--sneaky-refresh () (when company-candidates (company-call-frontends 'unhide)) @@ -2102,8 +2106,10 @@ doesn't cause any immediate changes to the buffer text." (company-assert-enabled) (setq company--manual-action t) (unwind-protect - (let ((company-minimum-prefix-length 0)) - (or company-candidates + (let ((company-minimum-prefix-length 0) + (company--manual-now t)) + (or (and company-candidates + (= company-point (point))) (company-auto-begin))) (unless company-candidates (setq company--manual-action nil)))) @@ -2201,13 +2207,16 @@ For more details see `company-insertion-on-trigger' and (setq company-candidates-cache nil)) (let* ((new-prefix (company-call-backend 'prefix)) (ignore-case (company-call-backend 'ignore-case)) - (c (when (and (company--good-prefix-p new-prefix - (company--prefix-min-length)) - (setq new-prefix (company--prefix-str new-prefix)) - (= (- (point) (length new-prefix)) - (- company-point (length company-prefix)))) - (company-calculate-candidates new-prefix ignore-case)))) + (c (catch 'interrupted + (when (and (company--good-prefix-p new-prefix + (company--prefix-min-length)) + (setq new-prefix (company--prefix-str new-prefix)) + (= (- (point) (length new-prefix)) + (- company-point (length company-prefix)))) + (company-calculate-candidates new-prefix ignore-case))))) (cond + ((eq c 'new-input) ; Keep the old completions, company-point, prefix. + t) ((and company-abort-on-unique-match (company--unique-match-p c new-prefix ignore-case)) ;; Handle it like completion was aborted, to differentiate from user @@ -2216,7 +2225,8 @@ For more details see `company-insertion-on-trigger' and (company-cancel 'unique)) ((consp c) ;; incremental match - (setq company-prefix new-prefix) + (setq company-prefix new-prefix + company-point (point)) (company-update-candidates c) c) ((and (characterp last-command-event) @@ -2250,9 +2260,14 @@ For more details see `company-insertion-on-trigger' and ;; Keep this undocumented, esp. while only 1 backend needs it. (company-call-backend 'set-min-prefix min-prefix) (setq company-prefix (company--prefix-str prefix) + company-point (point) company-backend backend - c (company-calculate-candidates company-prefix ignore-case)) + c (catch 'interrupted + (company-calculate-candidates company-prefix ignore-case))) (cond + ((or (null c) (eq c 'new-input)) + (when company--manual-action + (message "No completion found"))) ((and company-abort-on-unique-match (company--unique-match-p c company-prefix ignore-case) (if company--manual-action @@ -2262,9 +2277,6 @@ For more details see `company-insertion-on-trigger' and t)) ;; ...abort and run the hooks, e.g. to clear the cache. (company-cancel 'unique)) - ((null c) - (when company--manual-action - (message "No completion found"))) (t ;; We got completions! (when company--manual-action (setq company--manual-prefix prefix)) @@ -2282,8 +2294,7 @@ For more details see `company-insertion-on-trigger' and (company--begin-new))) (if (not company-candidates) (setq company-backend nil) - (setq company-point (point) - company--point-max (point-max)) + (setq company--point-max (point-max)) (company-ensure-emulation-alist) (company-enable-overriding-keymap company-active-map) (company-call-frontends 'update))) diff --git a/test/capf-tests.el b/test/capf-tests.el index 37efa8d541..bf7997ade0 100644 --- a/test/capf-tests.el +++ b/test/capf-tests.el @@ -1,6 +1,6 @@ ;;; capf-tests.el --- company tests for the company-capf backend -*- lexical-binding: t; -*- -;; Copyright (C) 2018-2019, 2021-2023 Free Software Foundation, Inc. +;; Copyright (C) 2018-2019, 2021-2024 Free Software Foundation, Inc. ;; Author: João Távora <joaotav...@gmail.com> ;; Keywords: @@ -141,5 +141,30 @@ 0 14 (face (company-tooltip-common company-tooltip)); "with-current-b" 14 19 (face company-tooltip))))))) ; "uffer" +(ert-deftest company-capf-interrupted-on-input () + (should + (eq + (catch 'interrupted + (with-temp-buffer + (let ((completion-at-point-functions + (list (lambda () + (list 1 1 obarray :company-use-while-no-input t)))) + (unread-command-events '(?a)) + (non-essential t)) + (company-capf 'candidates "a") + (error "Not reachable")))) + 'new-input))) + +(ert-deftest company-capf-uninterrupted () + (should + (equal + (with-temp-buffer + (let ((completion-at-point-functions + (list (lambda () + (list 1 1 '("abcd" "ae" "be") t)))) + (unread-command-events '(?a))) + (company-capf 'candidates "b"))) + '("be")))) + (provide 'capf-tests) ;;; capf-tests.el ends here