Rearrange functions
[emacs-capf-autosuggest.git] / capf-autosuggest.el
blob4a2c2755d15da0b923cfc11482d429045745ed4a
1 ;;; capf-autosuggest.el --- Show first completion-at-point as an overlay -*- lexical-binding: t; -*-
3 ;; Filename: capf-autosuggest.el
4 ;; Description: Show first completion-at-point as an overlay
5 ;; Author: jakanakaevangeli <jakanakaevangeli@chiru.no>
6 ;; Created: 2021-07-13
7 ;; Version: 1.0
8 ;; URL: https://github.com/jakanakaevangeli/emacs-capf-autosuggest
10 ;; This file is not part of GNU Emacs.
12 ;; This program is free software: you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ;;; Commentary:
27 ;; capf-autosuggest-mode is a minor mode that lets you preview the first
28 ;; completion candidate of completion-at-point as an overlay. This is very
29 ;; useful in combination with history-capf.el which implements
30 ;; completion-at-point functions for comint and eshell history. Previewing the
31 ;; most recent matching history element gives you auto-suggestions, familiar to
32 ;; users of zsh-autosuggestions and fish.
34 ;;; Code:
36 (eval-when-compile (require 'subr-x))
38 (defface capf-autosuggest-face '((t :inherit file-name-shadow))
39 "Face used for auto suggestions."
40 :group 'completion)
42 (defvar-local capf-autosuggest-capf nil
43 "`completion-at-point-functions', used by capf-autossugest.
44 If nil, capf-autosuggest will use
45 `completion-at-point-functions', otherwise it will use this hook
46 variable.")
48 (defvar capf-autosuggest-all-completions-only-one nil
49 "Non-nil if only the first result of `all-completions' is of interest.
50 capf-autosuggest binds this to t around calls to
51 `all-completions'. A dynamic completion table can take this as a
52 hint to only return a list of one element for optimization.")
54 (defvar-local capf-autosuggest--overlay nil)
55 (defvar-local capf-autosuggest--str "")
56 (defvar-local capf-autosuggest--region '(nil)
57 "Region of `completion-at-point'.")
59 (defvar capf-autosuggest-active-mode)
61 (defun capf-autosuggest--post-h ()
62 "Create an auto-suggest overlay."
63 (if completion-in-region-mode
64 (capf-autosuggest-active-mode -1)
65 (when capf-autosuggest-active-mode
66 ;; `identity' is used to generate slightly faster byte-code
67 (pcase-let ((`(,beg . ,end) (identity capf-autosuggest--region)))
68 (unless (< beg (point) end)
69 (capf-autosuggest-active-mode -1))))
71 (unless capf-autosuggest-active-mode
72 (pcase (run-hook-wrapped (if capf-autosuggest-capf
73 'capf-autosuggest-capf
74 'completion-at-point-functions)
75 #'completion--capf-wrapper 'all)
76 (`(,_fun ,beg ,end ,table . ,plist)
77 (let* ((pred (plist-get plist :predicate))
78 (string (buffer-substring-no-properties beg end))
79 ;; See `completion-emacs21-all-completions'
80 (base (car (completion-boundaries string table pred ""))))
81 (when-let*
82 ((completions
83 (let ((capf-autosuggest-all-completions-only-one t))
84 ;; Use `all-completions' rather than
85 ;; `completion-all-completions' to bypass completion styles
86 ;; and strictly match only on prefix. This makes sense here
87 ;; as we only use the string without the prefix for the
88 ;; overlay.
89 (all-completions string table pred)))
90 ;; `all-completions' may return strings that don't strictly
91 ;; match on our prefix. Ignore them.
92 ((string-prefix-p (substring string base) (car completions)))
93 (str (substring (car completions) (- end beg base)))
94 ((/= 0 (length str))))
95 (setq capf-autosuggest--region (cons beg end)
96 capf-autosuggest--str (copy-sequence str))
97 (move-overlay capf-autosuggest--overlay end end)
98 (when (eq ?\n (aref str 0))
99 (setq str (concat " " str)))
100 (add-text-properties 0 1 (list 'cursor (length str)) str)
101 (add-face-text-property 0 (length str) 'capf-autosuggest-face t str)
102 (overlay-put capf-autosuggest--overlay 'after-string str)
103 (capf-autosuggest-active-mode))))))))
105 ;;;###autoload
106 (define-minor-mode capf-autosuggest-mode
107 "Auto-suggest first completion at point with an overlay."
108 :group 'completion
109 (if capf-autosuggest-mode
110 (progn
111 (setq capf-autosuggest--overlay (make-overlay (point) (point) nil t t))
112 (add-hook 'post-command-hook #'capf-autosuggest--post-h nil t)
113 (add-hook 'completion-in-region-mode-hook #'capf-autosuggest--post-h nil t))
114 (remove-hook 'post-command-hook #'capf-autosuggest--post-h t)
115 (remove-hook 'completion-in-region-mode-hook #'capf-autosuggest--post-h t)
116 (capf-autosuggest-active-mode -1)))
118 ;;;###autoload
119 (defmacro capf-autosuggest-define-partial-accept-cmd (name command)
120 "Define a command NAME.
121 It will call COMMAND interactively, allowing it to move point
122 into an auto-suggested overlay. COMMAND must not modify buffer.
123 NAME must not be called if variable
124 `capf-autosuggest-active-mode' is inactive. NAME is suitable for
125 binding in `capf-autosuggest-active-mode-map'."
126 `(defun ,name ()
127 ,(format "`%s', possibly moving point into an auto-suggested overlay."
128 command)
129 (interactive)
130 (capf-autosuggest-call-partial-accept-cmd #',command)))
132 (defun capf-autosuggest-call-partial-accept-cmd (command)
133 "Call COMMAND interactively, stepping into auto-suggested overlay.
134 Temporarily convert the overlay to buffer text and call COMMAND
135 interactively. Afterwards, the added text is deleted, but only
136 the portion after point. Additionally, if point is outside of
137 the added text, the whole text is deleted."
138 (let (beg end text)
139 (with-silent-modifications
140 (catch 'cancel-atomic-change
141 (atomic-change-group
142 (save-excursion
143 (goto-char (overlay-start capf-autosuggest--overlay))
144 (setq beg (point))
145 (insert capf-autosuggest--str)
146 (setq end (point)))
147 (call-interactively command)
148 (and (> (point) beg)
149 (<= (point) end)
150 (setq text (buffer-substring beg (point))))
151 (throw 'cancel-atomic-change nil))))
152 (when text
153 (if (= (point) beg)
154 (insert text)
155 (save-excursion
156 (goto-char beg)
157 (insert text))))))
159 (declare-function evil-forward-char "ext:evil-commands" nil t)
160 (declare-function evil-end-of-line "ext:evil-commands" nil t)
161 (declare-function evil-end-of-visual-line "ext:evil-commands" nil t)
162 (declare-function evil-end-of-line-or-visual-line "ext:evil-commands" nil t)
163 (declare-function evil-middle-of-visual-line "ext:evil-commands" nil t)
164 (declare-function evil-last-non-blank "ext:evil-commands" nil t)
165 (declare-function evil-forward-word-begin "ext:evil-commands" nil t)
166 (declare-function evil-forward-word-end "ext:evil-commands" nil t)
167 (declare-function evil-forward-WORD-begin "ext:evil-commands" nil t)
168 (declare-function evil-forward-WORD-end "ext:evil-commands" nil t)
170 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-forward-word forward-word)
171 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-forward-char forward-char)
172 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-end-of-line end-of-line)
173 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-move-end-of-line move-end-of-line)
174 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-end-of-visual-line end-of-visual-line)
175 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-char evil-forward-char)
176 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-end-of-line evil-end-of-line)
177 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-end-of-visual-line evil-end-of-visual-line)
178 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-end-of-line-or-visual-line evil-end-of-line-or-visual-line)
179 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-middle-of-visual-line evil-middle-of-visual-line)
180 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-last-non-blank evil-last-non-blank)
181 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-word-begin evil-forward-word-begin)
182 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-word-end evil-forward-word-end)
183 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-WORD-begin evil-forward-WORD-begin)
184 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-WORD-end evil-forward-WORD-end)
186 (defun capf-autosuggest-accept ()
187 "Accept current auto-suggestion.
188 Do not call this command if variable `capf-autosuggest-active-mode' is
189 inactive."
190 (interactive)
191 (capf-autosuggest-call-partial-accept-cmd
192 (lambda ()
193 (interactive)
194 (goto-char (overlay-start capf-autosuggest--overlay)))))
196 (defvar capf-autosuggest-active-mode-map
197 (let ((map (make-sparse-keymap)))
198 (define-key map [remap forward-word] #'capf-autosuggest-forward-word)
199 (define-key map [remap forward-char] #'capf-autosuggest-forward-char)
200 (define-key map [remap end-of-line] #'capf-autosuggest-end-of-line)
201 (define-key map [remap move-end-of-line] #'capf-autosuggest-move-end-of-line)
202 (define-key map [remap end-of-visual-line] #'capf-autosuggest-end-of-visual-line)
203 (define-key map [remap evil-forward-char] #'capf-autosuggest-evil-forward-char)
204 (define-key map [remap evil-end-of-line] #'capf-autosuggest-evil-end-of-line)
205 (define-key map [remap evil-end-of-visual-line] #'capf-autosuggest-evil-end-of-visual-line)
206 (define-key map [remap evil-end-of-line-or-visual-line] #'capf-autosuggest-evil-end-of-line-or-visual-line)
207 (define-key map [remap evil-middle-of-visual-line] #'capf-autosuggest-evil-middle-of-visual-line)
208 (define-key map [remap evil-last-non-blank] #'capf-autosuggest-evil-last-non-blank)
209 (define-key map [remap evil-forward-word-begin] #'capf-autosuggest-evil-forward-word-begin)
210 (define-key map [remap evil-forward-word-end] #'capf-autosuggest-evil-forward-word-end)
211 (define-key map [remap evil-forward-WORD-begin] #'capf-autosuggest-evil-forward-WORD-begin)
212 (define-key map [remap evil-forward-WORD-end] #'capf-autosuggest-evil-forward-WORD-end)
213 map)
214 "Keymap active when an auto-suggestion is shown.")
216 (defun capf-autosuggest--active-acf (beg end _length)
217 "Deactivate auto-suggestion on completion region modifications.
218 BEG and END denote the changed region."
219 ;; `identity' is used to generate slightly faster byte-code
220 (when (pcase-let ((`(,beg1 . ,end1) (identity capf-autosuggest--region)))
221 (if (< beg beg1)
222 (>= end beg1)
223 (<= beg end1)))
224 (capf-autosuggest-active-mode -1)))
226 (define-minor-mode capf-autosuggest-active-mode
227 "Active when auto-suggested overlay is shown."
228 :group 'completion
229 (if capf-autosuggest-active-mode
230 (add-hook 'after-change-functions #'capf-autosuggest--active-acf nil t)
231 (remove-hook 'after-change-functions #'capf-autosuggest--active-acf t)
232 (delete-overlay capf-autosuggest--overlay)))
234 (provide 'capf-autosuggest)
235 ;;; capf-autosuggest.el ends here