Added corfu-candidate-overlay-complete-at-point function
This commit is contained in:
parent
56b5d12641
commit
6cdc33aa21
2 changed files with 111 additions and 87 deletions
|
@ -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
|
||||||
|
|
10
readme.org
10
readme.org
|
@ -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]].
|
||||||
|
|
Loading…
Reference in a new issue