Fixed error when no further candidate was found
When corfu--candidate was ended up as nil, the overlay still held previous value but was not updated as the error was raised in the update function. Additionally when the backend is not keeping up we use the previous candidate to update what the user see using all of the information we had (i.e. prefix and the candidate), so we borrow a character from one and append/prepend to the other when necessary. This gives a little better experience when using slow backend such as lsp.
This commit is contained in:
parent
cfd74e2f6b
commit
446f88ff78
1 changed files with 145 additions and 66 deletions
|
@ -49,7 +49,7 @@
|
||||||
"Keymap to dismiss the Corfu candidate overlay.")
|
"Keymap to dismiss the Corfu candidate overlay.")
|
||||||
|
|
||||||
(defcustom corfu-overlay-auto-commands
|
(defcustom corfu-overlay-auto-commands
|
||||||
'("delete-backward-char\\'")
|
'("delete-backward-char\\'" "backward-delete-char-untabify")
|
||||||
"Additional commands apart from corfu-auto-commands which initiate
|
"Additional commands apart from corfu-auto-commands which initiate
|
||||||
completion candidate overlay."
|
completion candidate overlay."
|
||||||
:type '(repeat (choice regexp symbol))
|
:type '(repeat (choice regexp symbol))
|
||||||
|
@ -91,6 +91,14 @@
|
||||||
(add-text-properties 0 1 '(cursor 1) candidate))
|
(add-text-properties 0 1 '(cursor 1) candidate))
|
||||||
|
|
||||||
(overlay-put corfu--candidate-overlay 'window (selected-window))
|
(overlay-put corfu--candidate-overlay 'window (selected-window))
|
||||||
|
;; we store the whole candidate and prefix as a property to use when
|
||||||
|
;; deleting characters in quick succession so the backend will not
|
||||||
|
;; keep-up. We will need to use those stored values then to still
|
||||||
|
;; show the overlay with a meaningful suggestion
|
||||||
|
;; (i.e. the last one found)
|
||||||
|
(overlay-put corfu--candidate-overlay 'corfu-candidate candidate)
|
||||||
|
(overlay-put corfu--candidate-overlay 'corfu-prefix prefix)
|
||||||
|
;; and here is the candidate string as it will be rendered by Emacs.
|
||||||
(overlay-put corfu--candidate-overlay 'after-string
|
(overlay-put corfu--candidate-overlay 'after-string
|
||||||
(propertize
|
(propertize
|
||||||
candidate
|
candidate
|
||||||
|
@ -135,76 +143,145 @@
|
||||||
|
|
||||||
(when (>= (- end beg) corfu-auto-prefix) ;; adhere to auto prefix length settings.
|
(when (>= (- end beg) corfu-auto-prefix) ;; adhere to auto prefix length settings.
|
||||||
(corfu--update)
|
(corfu--update)
|
||||||
|
;; unfortunately corfu--candidates CAN and ARE nil sometimes,
|
||||||
|
;; if so we just short-circuit and hide the overlay.
|
||||||
|
;; TODO: Refactor the below to have just one ``if'' condition. Low priority.
|
||||||
|
(if corfu--candidates
|
||||||
(let* ((candidate (car corfu--candidates))
|
(let* ((candidate (car corfu--candidates))
|
||||||
(how-many-candidates (length corfu--candidates))
|
(how-many-candidates (length corfu--candidates))
|
||||||
(len (- end beg))
|
(len (- end beg))
|
||||||
(prefix (buffer-substring-no-properties beg end))
|
(prefix (buffer-substring-no-properties beg end))
|
||||||
(suffix (substring candidate len)))
|
(suffix (substring candidate len)))
|
||||||
|
|
||||||
(if (and
|
(if (and
|
||||||
;; need candidate
|
;; need candidate, it should be present if we got here, but safety-first
|
||||||
candidate
|
candidate
|
||||||
;; the prefix can't be empty (in case of corfu-auto-prefix equal 0)
|
;; the prefix can't be empty (in case of corfu-auto-prefix equal 0)
|
||||||
(not (string-empty-p prefix))
|
(not (string-empty-p prefix))
|
||||||
;; prefix need to match the candidate as there are „fuzzy”
|
;; prefix need to match the candidate as there are „fuzzy”
|
||||||
;; found candidates, esp. when using templates and the user
|
;; found candidates, esp. when using templates so the user
|
||||||
;; could see strage results at the first character.
|
;; could see strage results when typing the first character.
|
||||||
(string-prefix-p prefix candidate))
|
(string-prefix-p prefix candidate))
|
||||||
|
;; and finally we update the overlay.
|
||||||
(corfu--candidate-overlay-update
|
(corfu--candidate-overlay-update
|
||||||
beg
|
beg
|
||||||
end
|
end
|
||||||
prefix
|
prefix
|
||||||
suffix
|
suffix
|
||||||
how-many-candidates)
|
how-many-candidates)
|
||||||
;; otherwise we hide the overlay.
|
;; hide if the above ``if-condition'' is not met.
|
||||||
(corfu-hide-candidate-overlay))))))))))
|
(corfu-hide-candidate-overlay)))
|
||||||
|
;; hide if there is no corfu--candidates at all (equals nil).
|
||||||
|
(corfu-hide-candidate-overlay)))))))))
|
||||||
|
|
||||||
(defun corfu--candidate-overlay-post-command ()
|
(defun corfu--candidate-overlay-pre-command ()
|
||||||
"Post command hook updating the candidate overlay when user inserts character
|
"Pre command hook to hide the overlay if the command is not insert or delete.
|
||||||
and the cursor is at the end of word."
|
Otherwise the overlay can influence movement commands (i.e. the cursor is
|
||||||
|
considered to be located at the end of the overlay, so line movement will
|
||||||
|
jump to character far removed from the perceived cursor location)."
|
||||||
|
;; We should not throw an error here, as Emacs will disable
|
||||||
|
;; the hook if it fails with an error.
|
||||||
|
(ignore-errors
|
||||||
(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
|
||||||
(corfu--match-symbol-p corfu-overlay-auto-commands this-command)))
|
(corfu--match-symbol-p corfu-overlay-auto-commands this-command)))
|
||||||
|
(when (and
|
||||||
|
;; we are not in minibuffer.
|
||||||
|
(not (minibuffer-window-active-p (selected-window)))
|
||||||
|
;; and the command is not one of insert or delete.
|
||||||
|
(not (or
|
||||||
|
is-insert-command
|
||||||
|
is-delete-command)))
|
||||||
|
(corfu-hide-candidate-overlay)))))
|
||||||
|
|
||||||
|
(defun corfu--candidate-overlay-post-command ()
|
||||||
|
"Post command hook updating the candidate overlay when user inserts character
|
||||||
|
and the cursor is at the end of word."
|
||||||
|
;; We should not throw an error here, as Emacs will disable
|
||||||
|
;; the hook if it fails with an error (and auto suggestion backends
|
||||||
|
;; can and do throw errors sometimes, corfu even have a readme section
|
||||||
|
;; dedicated to debugging those; but a timer corfu menu is using is much
|
||||||
|
;; more forgiving than how Emacs handle post and pre command hooks).
|
||||||
|
(ignore-errors
|
||||||
|
(let* ((is-insert-command
|
||||||
|
(corfu--match-symbol-p corfu-auto-commands this-command))
|
||||||
|
(is-delete-command
|
||||||
|
(corfu--match-symbol-p corfu-overlay-auto-commands this-command)))
|
||||||
|
;; short-circuit conditions -- the earlier we return if don't need to do
|
||||||
|
;; anything the better.
|
||||||
(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 (and ;; do not update it 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))))
|
||||||
(or ;; do not update if it is not one of the insert or delete commands.
|
(or ;; do not update if it is not one of the insert or delete commands.
|
||||||
is-insert-command
|
is-insert-command
|
||||||
is-delete-command))
|
is-delete-command))
|
||||||
|
;; now we check additional short-circuit conditions, but those operate on
|
||||||
|
;; next character.
|
||||||
(let ((next-char (char-after)))
|
(let ((next-char (char-after)))
|
||||||
(when (or ;; do not update if we are not at the end of the word.
|
(when (or ;; do not update if we are not at the end of the word.
|
||||||
(not next-char) ;; end of file
|
(not next-char) ;; end of file
|
||||||
;; one of whitespace, quoting character, punctuation,
|
;; one of whitespace, quoting character, punctuation,
|
||||||
;; closing bracket, etc is next.
|
;; closing bracket, etc is next.
|
||||||
;; When those characters follow next completion won't trigger
|
;; When those characters follow next completion won't trigger
|
||||||
;; eitherway: ' = * - + / ~ _ (have not investigated further)
|
;; either-way: ' = * - + / ~ _ (have not investigated further)
|
||||||
(memq next-char '(?\s ?\t ?\r ?\n
|
(memq next-char '(?\s ?\t ?\r ?\n
|
||||||
?\" ?\` ?\) ?\] ?\>
|
?\" ?\` ?\) ?\] ?\>
|
||||||
?\. ?\, ?\: ?\;)))
|
?\. ?\, ?\: ?\;)))
|
||||||
;; When the completion backend is SLOW, i.e. like every LSP client,
|
;; When the completion backend is SLOW, i.e. like every LSP client,
|
||||||
;; then the overlay will not update and will interfere with the typing.
|
;; then the overlay will often not update and will interfere with the typing.
|
||||||
;; That's why we move preemptively when inserting and deleting the first
|
;; That's why we operate on stored prefix and candidate giving an illusion
|
||||||
;; character (look awkward when typing a different word than the completion
|
;; of updating the overlay -- but using the previous auto suggestion candidate.
|
||||||
;; but still looks better than flickering).
|
(when corfu--candidate-overlay ;; need overlay active
|
||||||
;; When deleting -- we just move the overlay so it will show
|
(let* ((candidate
|
||||||
;; the „lagging” candidate.
|
(overlay-get corfu--candidate-overlay 'corfu-candidate))
|
||||||
(when (and is-insert-command corfu--candidate-overlay)
|
(prefix
|
||||||
(let ((previous-text (overlay-get corfu--candidate-overlay 'after-string)))
|
(overlay-get corfu--candidate-overlay 'corfu-prefix))
|
||||||
(when (not (string-empty-p previous-text))
|
(previous-text
|
||||||
|
(overlay-get corfu--candidate-overlay 'after-string)))
|
||||||
|
;; 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
|
||||||
|
;; prefix to/from candidate)
|
||||||
|
(cond
|
||||||
|
;; TODO: Delete character case should probably be moved to pre-command hook.
|
||||||
|
(is-delete-command
|
||||||
|
(if (length> prefix 0)
|
||||||
|
(progn
|
||||||
|
;; we still have some characters present in the prefix,
|
||||||
|
;; so we'll borrow one and move to the candidate.
|
||||||
|
(overlay-put corfu--candidate-overlay 'corfu-candidate
|
||||||
|
(concat candidate (substring prefix (- (length prefix) 1))))
|
||||||
|
(overlay-put corfu--candidate-overlay 'corfu-prefix
|
||||||
|
(substring prefix 0 (- (length prefix) 1 )))
|
||||||
(overlay-put corfu--candidate-overlay 'after-string
|
(overlay-put corfu--candidate-overlay 'after-string
|
||||||
(substring previous-text 1)))
|
(concat candidate (substring prefix (- (length prefix) 1))))
|
||||||
(move-overlay corfu--candidate-overlay (point) (point))))
|
(move-overlay corfu--candidate-overlay (point) (point)))
|
||||||
|
;; if the length of prefix is zero then we can only hide
|
||||||
|
;; the overlay as we are removing past the current word
|
||||||
|
;; boundary.
|
||||||
|
(corfu-hide-candidate-overlay)))
|
||||||
|
;; Inserting character - we still update using historical data
|
||||||
|
;; in case the corfu backend would get interrupted;
|
||||||
|
;; Here we "borrow" a character from the candidate and append it to the prefix.
|
||||||
|
(is-insert-command
|
||||||
|
(when (not (string-empty-p previous-text))
|
||||||
|
(overlay-put corfu--candidate-overlay 'corfu-candidate
|
||||||
|
(and (length> candidate 1) (substring candidate 1 (- (length candidate) 1))))
|
||||||
|
(overlay-put corfu--candidate-overlay 'corfu-prefix
|
||||||
|
(concat prefix (and (length> candidate 1) (substring candidate 0 1))))
|
||||||
|
(overlay-put corfu--candidate-overlay 'after-string
|
||||||
|
(substring previous-text 1)))))
|
||||||
|
|
||||||
|
(move-overlay corfu--candidate-overlay (point) (point))))
|
||||||
;; 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
|
||||||
|
;; we did the shuffling above.
|
||||||
(setq corfu--candidate-last-point (point))
|
(setq corfu--candidate-last-point (point))
|
||||||
(corfu-show-candidate-overlay)))
|
(corfu-show-candidate-overlay)))
|
||||||
;; or hide the overlay if the conditions where not met.
|
;; or hide the overlay if the conditions to show the overlay where not met.
|
||||||
(corfu-hide-candidate-overlay))))
|
(corfu-hide-candidate-overlay)))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(define-minor-mode corfu-candidate-overlay-mode
|
(define-minor-mode corfu-candidate-overlay-mode
|
||||||
|
@ -214,9 +291,11 @@
|
||||||
(if corfu-candidate-overlay-mode
|
(if corfu-candidate-overlay-mode
|
||||||
(progn
|
(progn
|
||||||
(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)
|
||||||
(message "Enabled `corfu-candidate-overlay-mode'."))
|
(message "Enabled `corfu-candidate-overlay-mode'."))
|
||||||
(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)
|
||||||
(message "Disabled `corfu-candidate-overlay-mode'."))))
|
(message "Disabled `corfu-candidate-overlay-mode'."))))
|
||||||
|
|
||||||
;;; corfu-candidate-overlay.el ends here
|
;;; corfu-candidate-overlay.el ends here
|
||||||
|
|
Loading…
Reference in a new issue