Fixed overlay when inserting fast enough so backend can't keep up

Overlay was updated temporarily with a wrong text on insert command,
so when the suggestion backend didn't keep up the suggestion was
wrong.

Refactored some code to have less repetition when setting overlay
properties.
This commit is contained in:
Adam Kruszewski 2023-05-07 19:48:45 +02:00
parent 446f88ff78
commit 71bff98bd4

View file

@ -66,7 +66,7 @@
'((t (:inherit 'corfu--candidate-overlay-face :underline t))) '((t (:inherit 'corfu--candidate-overlay-face :underline t)))
"Face used for the overlay when there is only one candidate.") "Face used for the overlay when there is only one candidate.")
(defun corfu--candiate-overlay-prepare (beg end) (defun corfu--candiate-overlay-prepare (position)
"Sets the default properties of the candidates overlay. "Sets the default properties of the candidates overlay.
The overlay can be dismissed with a mouse click." The overlay can be dismissed with a mouse click."
(when (not corfu--candidate-overlay-map) (when (not corfu--candidate-overlay-map)
@ -77,15 +77,23 @@
(delete-overlay corfu--candidate-overlay)))) (delete-overlay corfu--candidate-overlay))))
(if corfu--candidate-overlay (if corfu--candidate-overlay
(move-overlay corfu--candidate-overlay end end) (move-overlay corfu--candidate-overlay position position)
(progn (progn
(setq corfu--candidate-overlay (make-overlay end end nil)) (setq corfu--candidate-overlay (make-overlay position position nil))
;; priority of 1k is the value used by Corfu. ;; priority of 1k is the value used by Corfu.
(overlay-put corfu--candidate-overlay 'priority 1000)))) (overlay-put corfu--candidate-overlay 'priority 1000))))
(defun corfu--candidate-overlay-update (beg end prefix candidate how-many-candidates) (defun corfu--get-overlay-property (property)
"Returns the value of overlays' property"
(overlay-get corfu--candidate-overlay property))
(defun corfu--set-overlay-property (property value)
"Returns the value of overlays' property"
(overlay-put corfu--candidate-overlay property value))
(defun corfu--candidate-overlay-update (position prefix candidate how-many-candidates)
"Updates the candidate overlay with the first candidate found by Corfu." "Updates the candidate overlay with the first candidate found by Corfu."
(corfu--candiate-overlay-prepare beg end) (corfu--candiate-overlay-prepare position)
(unless (string-empty-p candidate) (unless (string-empty-p candidate)
(add-text-properties 0 1 '(cursor 1) candidate)) (add-text-properties 0 1 '(cursor 1) candidate))
@ -96,10 +104,11 @@
;; keep-up. We will need to use those stored values then to still ;; keep-up. We will need to use those stored values then to still
;; show the overlay with a meaningful suggestion ;; show the overlay with a meaningful suggestion
;; (i.e. the last one found) ;; (i.e. the last one found)
(overlay-put corfu--candidate-overlay 'corfu-candidate candidate) (corfu--set-overlay-property 'corfu-candidate candidate)
(overlay-put corfu--candidate-overlay 'corfu-prefix prefix) (corfu--set-overlay-property 'corfu-prefix prefix)
(corfu--set-overlay-property 'corfu-count how-many-candidates)
;; and here is the candidate string as it will be rendered by Emacs. ;; and here is the candidate string as it will be rendered by Emacs.
(overlay-put corfu--candidate-overlay 'after-string (corfu--set-overlay-property 'after-string
(propertize (propertize
candidate candidate
'face (if (= how-many-candidates 1) 'face (if (= how-many-candidates 1)
@ -116,7 +125,7 @@
;; would need to recreate the object on basically each keystroke ;; would need to recreate the object on basically each keystroke
;; and I don't like the perspective of it, would also flicker ;; and I don't like the perspective of it, would also flicker
;; for sure - so we keep the one overlay and we clear the contents. ;; for sure - so we keep the one overlay and we clear the contents.
(overlay-put corfu--candidate-overlay 'after-string ""))) (corfu--set-overlay-property 'after-string "")))
(defun corfu-show-candidate-overlay () (defun corfu-show-candidate-overlay ()
"Computes completion candidates just like Corfu and updats the candidate "Computes completion candidates just like Corfu and updats the candidate
@ -163,8 +172,7 @@
(string-prefix-p prefix candidate)) (string-prefix-p prefix candidate))
;; and finally we update the overlay. ;; and finally we update the overlay.
(corfu--candidate-overlay-update (corfu--candidate-overlay-update
beg end ;; we anchor the overlay to the end position as cursor is there.
end
prefix prefix
suffix suffix
how-many-candidates) how-many-candidates)
@ -181,6 +189,8 @@
;; We should not throw an error here, as Emacs will disable ;; We should not throw an error here, as Emacs will disable
;; the hook if it fails with an error. ;; the hook if it fails with an error.
(ignore-errors (ignore-errors
;; This condition look similar to one in the post-command hook but it does
;; differ significantly -- this one is in part reversed and less strict.
(let* ((is-insert-command (let* ((is-insert-command
(corfu--match-symbol-p corfu-auto-commands this-command)) (corfu--match-symbol-p corfu-auto-commands this-command))
(is-delete-command (is-delete-command
@ -188,10 +198,13 @@
(when (and (when (and
;; we are not in minibuffer. ;; we are not in minibuffer.
(not (minibuffer-window-active-p (selected-window))) (not (minibuffer-window-active-p (selected-window)))
;; corfu menu shown
(and (frame-live-p corfu--frame) (frame-visible-p corfu--frame))
(not corfu-auto) ;; don't work with corfu-auto
;; and the command is not one of insert or delete. ;; and the command is not one of insert or delete.
(not (or (not (or
is-insert-command is-insert-command
is-delete-command))) is-delete-command)))
(corfu-hide-candidate-overlay))))) (corfu-hide-candidate-overlay)))))
(defun corfu--candidate-overlay-post-command () (defun corfu--candidate-overlay-post-command ()
@ -212,6 +225,9 @@
(if (and (if (and
;; we are not in minibuffer, as it looks awkward. ;; we are not in minibuffer, as it looks awkward.
(not (minibuffer-window-active-p (selected-window))) (not (minibuffer-window-active-p (selected-window)))
(not corfu-auto) ;; don't work with corfu-auto
;; corfu menu needs to be hidden
(not (and (frame-live-p corfu--frame) (frame-visible-p corfu--frame)))
(not (and ;; do not update if the point have not moved. (not (and ;; do not update if the point have not moved.
corfu--candidate-last-point corfu--candidate-last-point
(= corfu--candidate-last-point (point)))) (= corfu--candidate-last-point (point))))
@ -236,45 +252,47 @@
;; of updating the overlay -- but using the previous auto suggestion candidate. ;; of updating the overlay -- but using the previous auto suggestion candidate.
(when corfu--candidate-overlay ;; need overlay active (when corfu--candidate-overlay ;; need overlay active
(let* ((candidate (let* ((candidate
(overlay-get corfu--candidate-overlay 'corfu-candidate)) (corfu--get-overlay-property 'corfu-candidate))
(prefix (prefix
(overlay-get corfu--candidate-overlay 'corfu-prefix)) (corfu--get-overlay-property 'corfu-prefix))
(count
(corfu--get-overlay-property 'corfu-count))
(previous-text (previous-text
(overlay-get corfu--candidate-overlay 'after-string))) (corfu--get-overlay-property 'after-string)))
;; We need to deal with the overlay and stored candidate differently ;; We need to deal with the overlay and stored candidate differently
;; when inserting and deleting (i.e. we need to shift one characte from or to ;; when inserting and deleting (i.e. we need to shift one characte from or to
;; prefix to/from candidate) ;; prefix to/from candidate)
(cond (cond
;; TODO: Delete character case should probably be moved to pre-command hook. ;; TODO: Delete character case should probably be moved to pre-command hook?
(is-delete-command (is-delete-command
(if (length> prefix 0) (if (length> prefix 0)
(progn
;; we still have some characters present in the prefix, ;; we still have some characters present in the prefix,
;; so we'll borrow one and move to the candidate. ;; so we'll borrow one and move to the candidate.
(overlay-put corfu--candidate-overlay 'corfu-candidate (corfu--candidate-overlay-update
(concat candidate (substring prefix (- (length prefix) 1)))) (point) ;; move to current cursor's position
(overlay-put corfu--candidate-overlay 'corfu-prefix (substring prefix 0 (- (length prefix) 1 ))
(substring prefix 0 (- (length prefix) 1 ))) (concat (substring prefix (- (length prefix) 1)) candidate)
(overlay-put corfu--candidate-overlay 'after-string count)
(concat candidate (substring prefix (- (length prefix) 1))))
(move-overlay corfu--candidate-overlay (point) (point)))
;; if the length of prefix is zero then we can only hide ;; if the length of prefix is zero then we can only hide
;; the overlay as we are removing past the current word ;; the overlay as we are removing past the current word
;; boundary. ;; boundary.
(corfu-hide-candidate-overlay))) (corfu-hide-candidate-overlay)))
;; Inserting character - we still update using historical data ;; Inserting character - we still update using historical data
;; in case the corfu backend would get interrupted; ;; in case the corfu backend would get interrupted;
;; Here we "borrow" a character from the candidate and append it to the prefix. ;; Here we "borrow" a character from the candidate and append it to the prefix.
(is-insert-command (is-insert-command
(when (not (string-empty-p previous-text)) (if (and
(overlay-put corfu--candidate-overlay 'corfu-candidate (not (string-empty-p previous-text))
(and (length> candidate 1) (substring candidate 1 (- (length candidate) 1)))) (length> candidate 1))
(overlay-put corfu--candidate-overlay 'corfu-prefix (corfu--candidate-overlay-update
(concat prefix (and (length> candidate 1) (substring candidate 0 1)))) (point) ;; move to current cursor's position
(overlay-put corfu--candidate-overlay 'after-string (concat prefix (substring candidate 0 1))
(substring previous-text 1))))) (substring candidate 1 (length candidate))
count)
(move-overlay corfu--candidate-overlay (point) (point)))) ;; no previous candidate or candidate is zero length,
;; probably we have reached the end of suggested word,
;; so let's hide the overlay.
(corfu-hide-candidate-overlay))))))
;; preserve the current position, show and update the overlay. ;; preserve the current position, show and update the overlay.
;; the corfu-show-candidate-overlay CAN be interrupted, that's why ;; the corfu-show-candidate-overlay CAN be interrupted, that's why
;; we did the shuffling above. ;; we did the shuffling above.
@ -289,10 +307,13 @@
:global t :global t
:group 'corfu :group 'corfu
(if corfu-candidate-overlay-mode (if corfu-candidate-overlay-mode
(progn (cond
((not corfu-auto)
(add-hook 'post-command-hook #'corfu--candidate-overlay-post-command) (add-hook 'post-command-hook #'corfu--candidate-overlay-post-command)
(add-hook 'pre-command-hook #'corfu--candidate-overlay-pre-command) (add-hook 'pre-command-hook #'corfu--candidate-overlay-pre-command)
(message "Enabled `corfu-candidate-overlay-mode'.")) (message "Enabled `corfu-candidate-overlay-mode'."))
(t
(message "`corfu-auto' enabled, `corfu-candidate-overlay-mode' requires `corfu-auto' to be set to `nil'.")))
(progn (progn
(remove-hook 'post-command-hook #'corfu--candidate-overlay-post-command) (remove-hook 'post-command-hook #'corfu--candidate-overlay-post-command)
(remove-hook 'pre-command-hook #'corfu--candidate-overlay-pre-command) (remove-hook 'pre-command-hook #'corfu--candidate-overlay-pre-command)