Added corfu-candidate-overlay-complete-at-point function

This commit is contained in:
Adam Kruszewski 2023-07-04 10:54:43 +02:00
parent 56b5d12641
commit 6cdc33aa21
2 changed files with 111 additions and 87 deletions

View file

@ -5,7 +5,7 @@
;; Author: Adam Kruszewski <adam@kruszewski.name> ;; Author: Adam Kruszewski <adam@kruszewski.name>
;; Maintainer: Adam Kruszewski <adam@kruszewski.name> ;; Maintainer: Adam Kruszewski <adam@kruszewski.name>
;; Created: 2023 ;; Created: 2023
;; Version: 1.3 ;; Version: 1.4
;; Package-Requires: ((emacs "28.1") (corfu "0.36")) ;; Package-Requires: ((emacs "28.1") (corfu "0.36"))
;; Homepage: https://code.bsdgeek.org/adam/corfu-candidate-overlay/ ;; Homepage: https://code.bsdgeek.org/adam/corfu-candidate-overlay/
@ -232,6 +232,20 @@ Otherwise the overlay can influence movement commands (i.e. the cursor is
is-delete-command)))) is-delete-command))))
(corfu-candidate-overlay--hide))))) (corfu-candidate-overlay--hide)))))
(defun corfu-candidate-overlay-at-word-boundary-p ()
"Return non-nil when cursor is at the word boundary for completion purposes.
Or nil otherwise."
(let ((next-char (char-after)))
(or (not next-char) ;; end of file
;; one of whitespace, quoting character, punctuation,
;; closing bracket, etc is next.
;; When those characters follow next completion won't trigger
;; either-way: ' = * - + / ~ _ (have not investigated further),
;; so they are not in the list.
(memq next-char '(?\s ?\t ?\r ?\n
?\" ?\` ?\) ?\] ?\>
?\. ?\, ?\: ?\;)))))
(defun corfu-candidate-overlay--post-command () (defun corfu-candidate-overlay--post-command ()
"Post command hook to update candidate overlay. "Post command hook to update candidate overlay.
Update happens when the user types character and the cursor is at Update happens when the user types character and the cursor is at
@ -242,90 +256,94 @@ the end of word."
;; dedicated to debugging those; but a timer corfu menu is using is much ;; dedicated to debugging those; but a timer corfu menu is using is much
;; more forgiving than how Emacs handle post and pre command hooks). ;; more forgiving than how Emacs handle post and pre command hooks).
(ignore-errors (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-candidate-overlay-auto-commands this-command))) (corfu--match-symbol-p corfu-candidate-overlay-auto-commands this-command)))
;; short-circuit conditions -- the earlier we return if don't need to do ;; short-circuit conditions -- the earlier we return, if don't need to do
;; anything the better. ;; 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 corfu-auto) ;; don't work with corfu-auto (not corfu-auto) ;; don't work with corfu-auto
;; corfu menu needs to be hidden ;; corfu menu needs to be hidden
(not (and (frame-live-p corfu--frame) (frame-visible-p corfu--frame))) (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-overlay--last-point corfu-candidate-overlay--last-point
(= corfu-candidate-overlay--last-point (point)))) (= corfu-candidate-overlay--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 ;; check whether we are at word boundardy.
;; next character. (corfu-candidate-overlay-at-word-boundary-p))
(let ((next-char (char-after))) ;; ...when main compound conditions is true clause...
(when (or ;; do not update if we are not at the end of the word. (progn
(not next-char) ;; end of file ;; When the completion backend is SLOW, i.e. like every LSP client,
;; one of whitespace, quoting character, punctuation, ;; then the overlay will often not update and will interfere with the typing.
;; closing bracket, etc is next. ;; That's why we operate on stored prefix and candidate giving an illusion
;; When those characters follow next completion won't trigger ;; of updating the overlay -- but using the previous auto suggestion candidate.
;; either-way: ' = * - + / ~ _ (have not investigated further) (when corfu-candidate-overlay--overlay
(memq next-char '(?\s ?\t ?\r ?\n (let* ((candidate
?\" ?\` ?\) ?\] ?\> (corfu-candidate-overlay--get-overlay-property 'corfu-candidate))
?\. ?\, ?\: ?\;))) (prefix
;; When the completion backend is SLOW, i.e. like every LSP client, (corfu-candidate-overlay--get-overlay-property 'corfu-prefix))
;; then the overlay will often not update and will interfere with the typing. (count
;; That's why we operate on stored prefix and candidate giving an illusion (corfu-candidate-overlay--get-overlay-property 'corfu-count))
;; of updating the overlay -- but using the previous auto suggestion candidate. (previous-text
(when corfu-candidate-overlay--overlay ;; need overlay active (corfu-candidate-overlay--get-overlay-property 'after-string)))
(let* ((candidate ;; We need to deal with the overlay and stored candidate differently
(corfu-candidate-overlay--get-overlay-property 'corfu-candidate)) ;; when inserting and deleting (i.e. we need to shift one characte from or to
(prefix ;; prefix to/from candidate)
(corfu-candidate-overlay--get-overlay-property 'corfu-prefix)) (cond
(count ;; TODO: Delete character case should probably be moved to pre-command hook?
(corfu-candidate-overlay--get-overlay-property 'corfu-count)) (is-delete-command
(previous-text (if (length> prefix 0)
(corfu-candidate-overlay--get-overlay-property 'after-string))) ;; we still have some characters present in the prefix,
;; We need to deal with the overlay and stored candidate differently ;; so we'll borrow one and move to the candidate.
;; when inserting and deleting (i.e. we need to shift one characte from or to (corfu-candidate-overlay--update
;; prefix to/from candidate) (point) ;; move to current cursor's position
(cond (substring prefix 0 (- (length prefix) 1 ))
;; TODO: Delete character case should probably be moved to pre-command hook? (concat (substring prefix (- (length prefix) 1)) candidate)
(is-delete-command count)
(if (length> prefix 0) ;; if the length of prefix is zero then we can only hide
;; we still have some characters present in the prefix, ;; the overlay as we are removing past the current word
;; so we'll borrow one and move to the candidate. ;; boundary.
(corfu-candidate-overlay--update (corfu-candidate-overlay--hide)))
(point) ;; move to current cursor's position ;; Inserting character - we still update using historical data
(substring prefix 0 (- (length prefix) 1 )) ;; in case the corfu backend would get interrupted;
(concat (substring prefix (- (length prefix) 1)) candidate) ;; Here we "borrow" a character from the candidate and append it to the prefix.
count) (is-insert-command
;; if the length of prefix is zero then we can only hide (if (and
;; the overlay as we are removing past the current word (not (string= previous-text ""))
;; boundary. (length> candidate 1))
(corfu-candidate-overlay--hide))) (corfu-candidate-overlay--update
;; Inserting character - we still update using historical data (point) ;; move to current cursor's position
;; in case the corfu backend would get interrupted; (concat prefix (substring candidate 0 1))
;; Here we "borrow" a character from the candidate and append it to the prefix. (substring candidate 1 (length candidate))
(is-insert-command count)
(if (and ;; no previous candidate or candidate is zero length,
(not (string= previous-text "")) ;; probably we have reached the end of suggested word,
(length> candidate 1)) ;; so let's hide the overlay.
(corfu-candidate-overlay--update (corfu-candidate-overlay--hide))))))
(point) ;; move to current cursor's position ;; preserve the current position, show and update the overlay.
(concat prefix (substring candidate 0 1)) ;; the corfu-candidate-overlay--show CAN be interrupted, that's why
(substring candidate 1 (length candidate)) ;; we did the shuffling above.
count) (setq corfu-candidate-overlay--last-point (point))
;; no previous candidate or candidate is zero length, (corfu-candidate-overlay--show))
;; probably we have reached the end of suggested word, ;; ...when main compound conditions is false clause...
;; so let's hide the overlay. ;; so hide the overlay if the conditions to show the overlay where not met.
(corfu-candidate-overlay--hide)))))) (corfu-candidate-overlay--hide)))))
;; preserve the current position, show and update the overlay.
;; the corfu-candidate-overlay--show CAN be interrupted, that's why ;;;###autoload
;; we did the shuffling above. (defun corfu-candidate-overlay-complete-at-point ()
(setq corfu-candidate-overlay--last-point (point)) "Insert the first completion candidate shown in the overlay."
(corfu-candidate-overlay--show))) (interactive)
;; or hide the overlay if the conditions to show the overlay where not met. (when
(corfu-candidate-overlay--hide))))) (and corfu-candidate-overlay--overlay
(overlayp corfu-candidate-overlay--overlay)
(corfu-candidate-overlay-at-word-boundary-p))
(insert (corfu-candidate-overlay--get-overlay-property 'corfu-candidate))))
;;;###autoload ;;;###autoload
(define-minor-mode corfu-candidate-overlay-mode (define-minor-mode corfu-candidate-overlay-mode

View file

@ -12,6 +12,8 @@ Overlay showing auto-completion candidate when there is only one available (so i
[[./readme-images/corfu-candidate-overlay-one.png]] [[./readme-images/corfu-candidate-overlay-one.png]]
It show the first auto-suggestion from [[https://github.com/minad/corfu][corfu]] and when there is only one it will show it underlined (face attributes can be configured). In case of many suggestions available, invoking =completion-at-point= will open the corfu popup menu to choose from, in case of single completion available (i.e. underlined) it will get auto-completed seeing the menu when invoked =completion-at-point=. It show the first auto-suggestion from [[https://github.com/minad/corfu][corfu]] and when there is only one it will show it underlined (face attributes can be configured). In case of many suggestions available, invoking =completion-at-point= will open the corfu popup menu to choose from, in case of single completion available (i.e. underlined) it will get auto-completed seeing the menu when invoked =completion-at-point=.
Package exposes also one interactive function =corfu-candidate-overlay-complete-at-point= which you can bind to a key combination. Invoking the function will complete the exact candidate overlay is currently showing[fn:4] (i.e. it will not show corfu's popup even if there are more candidates present, just complete what you see).
** Motivation ** Motivation
Similarly to Michell Hashimoto[fn:1] of HashiCorp fame, I like my editor to do less than more. I'm not as ascetic though ;-) From time to time I do use the auto-suggestion menu of [[https://github.com/minad/corfu][corfu]], especially when I forget the exact wording of a function. I do however trigger the auto-suggestion popup manually. Similarly to Michell Hashimoto[fn:1] of HashiCorp fame, I like my editor to do less than more. I'm not as ascetic though ;-) From time to time I do use the auto-suggestion menu of [[https://github.com/minad/corfu][corfu]], especially when I forget the exact wording of a function. I do however trigger the auto-suggestion popup manually.
@ -40,8 +42,11 @@ When using slow or cpu-heavy corfu backends (e.g. [[https://github.com/minad/cap
;; enable corfu-candidate-overlay mode globally ;; enable corfu-candidate-overlay mode globally
;; this relies on having corfu-auto set to nil ;; this relies on having corfu-auto set to nil
(corfu-candidate-overlay-mode +1) (corfu-candidate-overlay-mode +1)
;; bind Ctr + TAB to trigger the completion popup of corfu ;; bind Ctrl + TAB to trigger the completion popup of corfu
(global-set-key (kbd "C-<tab>") 'completion-at-point)) (global-set-key (kbd "C-<tab>") 'completion-at-point)
;; bind Ctrl + Shift + Tab to trigger completion of the first candidate
;; (keybing <iso-lefttab> may not work for your keyboard model)
(global-set-key (kbd "C-<iso-lefttab>") 'corfu-candidate-overlay-complete-at-point))
#+end_src #+end_src
** Customization ** Customization
Faces available for customization: Faces available for customization:
@ -72,6 +77,7 @@ Issue reports, questions, comments and code patches are welcome -- you can send
If you haven't sent code patches via e-mail yet and would like to learn how to work with an e-mail based workflow, you can read more at [[https://git-scm.com/docs/git-format-patch][git format-patch]] man page or at [[https://git-send-email.io/][git-send-email.io]]. If you haven't sent code patches via e-mail yet and would like to learn how to work with an e-mail based workflow, you can read more at [[https://git-scm.com/docs/git-format-patch][git format-patch]] man page or at [[https://git-send-email.io/][git-send-email.io]].
* Footnotes * Footnotes
[fn:4] Thanks to [[https://github.com/terlar][Terje Larsen]] for suggestion!
[fn:1] See video cast: [fn:1] See video cast:
[[https://www.youtube.com/watch?v=rysgxl35EGc][Worst Practices in Software Development: Mitchell Hashimoto uses a simple code editor]]. [[https://www.youtube.com/watch?v=rysgxl35EGc][Worst Practices in Software Development: Mitchell Hashimoto uses a simple code editor]].