Typo
[emacs-capf-autosuggest.git] / capf-autosuggest.el
blob5f4f59adaeb1e063694f4074d7ecd8736e9167ee
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 lets you preview the most recent matching history element,
28 ;; similar to zsh-autosuggestions or fish. It works in eshell and in modes
29 ;; derived from comint-mode, for example M-x shell or M-x run-python
31 ;; Installation:
33 ;; Add the following to your Emacs init file:
35 ;; (add-to-list 'load-path "/path/to/emacs-capf-autosuggest")
36 ;; (require 'capf-autosuggest)
37 ;; (add-hook 'comint-mode-hook #'capf-autosuggest-setup-comint)
38 ;; (add-hook 'eshell-mode-hook #'capf-autosuggest-setup-eshell)
40 ;; Configuration:
42 ;; Use `capf-autosuggest-define-partial-accept-cmd' to make a command that can
43 ;; move point into an auto-suggested layer.
45 ;; Example: to make C-M-f (forward-sexp) movable into suggested text, put the
46 ;; following into you init file:
48 ;; (with-eval-after-load 'capf-autosuggest
49 ;; (capf-autosuggest-define-partial-accept-cmd
50 ;; movable-forward-sexp forward-sexp)
51 ;; (define-key capf-autosuggest-active-mode-map [remap forward-sexp]
52 ;; #'movable-forward-sexp))
54 ;; Details:
56 ;; capf-autosuggest provides a minor mode, capf-autosuggest-mode, that lets you
57 ;; preview the first completion candidate for in-buffer completion as an
58 ;; overlay. Instead of using the default hook `completion-at-point-functions',
59 ;; it uses its own hook `capf-autosuggest-capf-functions'. However, by
60 ;; default, this hook contains a function that reads the default hook, but only
61 ;; if point is at end of line, because an auto-suggested overlay can be
62 ;; annoying in the middle of a line. If you want, you can try enabling this
63 ;; minor mode in an ordinary buffer for previewing tab completion candidates at
64 ;; end of line.
66 ;; completion-at-point functions for comint and eshell history are also
67 ;; provided. Because they are less useful for tab completion and more useful
68 ;; for auto-suggestion preview, they should be added to
69 ;; `capf-autosuggest-capf-functions', which doesn't interfere with tab
70 ;; completion. The setup functions mentioned in "Installation" paragraph add
71 ;; them to this hook locally. By default, if there are no matches for history
72 ;; completion and point is at end of line, we fall back to previewing the
73 ;; default tab completion candidates, as described in the previous paragraph.
75 ;; You can customize this behaviour by customizing
76 ;; `capf-autosuggest-capf-functions'. For example, you could add
77 ;; `capf-autosuggest-orig-capf' to enable auto-suggestions of tab completion
78 ;; candidates in the middle of a line.
80 ;; Alternatives:
82 ;; There is also esh-autosuggest[1] with similar functionality. Differences:
83 ;; it optionally allows having a delay, it depends on emacs package company, it
84 ;; is implemented only for eshell.
86 ;; [1]: http://github.com/dieggsy/esh-autosuggest
88 ;;; Code:
90 (require 'ring)
91 (eval-when-compile
92 (require 'subr-x)
93 (require 'cl-lib))
95 (defvar comint-input-ring)
96 (defvar comint-accum-marker)
97 (defvar comint-use-prompt-regexp)
98 (defvar eshell-history-ring)
99 (defvar eshell-last-output-end)
100 (declare-function eshell-bol "esh-mode")
101 (declare-function comint-previous-matching-input-from-input "comint")
102 (declare-function eshell-previous-matching-input-from-input "em-hist")
104 ;;; Auto-suggestion overlay
106 (defface capf-autosuggest-face '((t :inherit file-name-shadow))
107 "Face used for auto suggestions."
108 :group 'completion)
110 (defvar capf-autosuggest-capf-functions '(capf-autosuggest-orig-if-at-eol-capf)
111 "`completion-at-point-functions', used by capf-autosuggest.
112 It is used instead of the standard
113 `completion-at-point-functions', but the default value contains
114 `capf-autosuggest-orig-if-at-eol-capf' which searches the
115 standard capf functions, if point is at the end of line.")
117 (defvar capf-autosuggest-all-completions-only-one nil
118 "Non-nil if only the first result of `all-completions' is of interest.
119 capf-autosuggest binds this to t around calls to
120 `all-completions'. A dynamic completion table can take this as a
121 hint to only return a list of one element for optimization.")
123 (defvar-local capf-autosuggest--overlay nil)
124 (defvar-local capf-autosuggest--str "")
125 (defvar-local capf-autosuggest--tick nil)
126 (defvar-local capf-autosuggest--region '(nil)
127 "Region of `completion-at-point'.")
129 (defun capf-autosuggest-orig-capf (&optional capf-functions)
130 "A capf that chooses from hook variable CAPF-FUNCTIONS.
131 CAPF-FUNCTIONS defaults to `completion-at-point-functions'.
132 Don't add this function to `completion-at-point-functions', as
133 this will result in an infinite loop. Useful for adding to
134 `capf-autosuggest-capf-functions', making it search the standard
135 capf functions."
136 (cdr (run-hook-wrapped (or capf-functions 'completion-at-point-functions)
137 #'completion--capf-wrapper 'all)))
139 (defun capf-autosuggest-orig-if-at-eol-capf ()
140 "`capf-autosuggest-orig-capf' if at the end of buffer.
141 Otherwise, return nil."
142 (when (eolp)
143 (capf-autosuggest-orig-capf)))
145 (defvar capf-autosuggest-active-mode)
147 (defun capf-autosuggest--post-h ()
148 "Create an auto-suggest overlay."
149 (when capf-autosuggest-active-mode
150 ;; `identity' is used to generate slightly faster byte-code
151 (pcase-let ((`(,beg . ,end) (identity capf-autosuggest--region)))
152 (unless (and (< beg (point) end)
153 (eq (buffer-modified-tick) capf-autosuggest--tick))
154 (capf-autosuggest-active-mode -1))))
156 (unless capf-autosuggest-active-mode
157 (pcase (capf-autosuggest-orig-capf 'capf-autosuggest-capf-functions)
158 (`(,beg ,end ,table . ,plist)
159 (let* ((pred (plist-get plist :predicate))
160 (string (buffer-substring-no-properties beg end))
161 ;; See `completion-emacs21-all-completions'
162 (base (car (completion-boundaries string table pred ""))))
163 (when-let*
164 ((completions
165 (let ((capf-autosuggest-all-completions-only-one t))
166 ;; Use `all-completions' rather than
167 ;; `completion-all-completions' to bypass completion styles
168 ;; and strictly match only on prefix. This makes sense here
169 ;; as we only use the string without the prefix for the
170 ;; overlay.
171 (all-completions string table pred)))
172 ;; `all-completions' may return strings that don't strictly
173 ;; match on our prefix. Ignore them.
174 ((string-prefix-p (substring string base) (car completions)))
175 (str (substring (car completions) (- end beg base)))
176 ((/= 0 (length str))))
177 (setq capf-autosuggest--region (cons beg end)
178 capf-autosuggest--str (copy-sequence str)
179 capf-autosuggest--tick (buffer-modified-tick))
180 (move-overlay capf-autosuggest--overlay end end)
181 (when (eq ?\n (aref str 0))
182 (setq str (concat " " str)))
183 (add-text-properties 0 1 (list 'cursor (length str)) str)
184 (add-face-text-property 0 (length str) 'capf-autosuggest-face t str)
185 (overlay-put capf-autosuggest--overlay 'after-string str)
186 (capf-autosuggest-active-mode)))))))
188 ;;;###autoload
189 (define-minor-mode capf-autosuggest-mode
190 "Auto-suggest first completion at point with an overlay."
191 :group 'completion
192 (if capf-autosuggest-mode
193 (progn
194 (setq capf-autosuggest--overlay (make-overlay (point) (point) nil t t))
195 (add-hook 'post-command-hook #'capf-autosuggest--post-h nil t)
196 (add-hook 'change-major-mode-hook
197 #'capf-autosuggest-active-mode-deactivate nil t))
198 (remove-hook 'change-major-mode-hook
199 #'capf-autosuggest-active-mode-deactivate t)
200 (remove-hook 'post-command-hook #'capf-autosuggest--post-h t)
201 (capf-autosuggest-active-mode -1)))
203 ;;; Various commands and menu-items
205 ;;;###autoload
206 (defmacro capf-autosuggest-define-partial-accept-cmd (name command)
207 "Define a command NAME.
208 It will call COMMAND interactively, allowing it to move point
209 into an auto-suggested overlay. COMMAND must not modify buffer.
210 NAME must not be called if variable
211 `capf-autosuggest-active-mode' is inactive. NAME is suitable for
212 binding in `capf-autosuggest-active-mode-map'."
213 `(defun ,name ()
214 ,(format "`%s', possibly moving point into an auto-suggested overlay."
215 command)
216 (interactive)
217 (capf-autosuggest-call-partial-accept-cmd #',command)))
219 (defun capf-autosuggest-call-partial-accept-cmd (command)
220 "Call COMMAND interactively, stepping into auto-suggested overlay.
221 Temporarily convert the overlay to buffer text and call COMMAND
222 interactively. Afterwards, the added text is deleted, but only
223 the portion after point. Additionally, if point is outside of
224 the added text, the whole text is deleted."
225 (let (beg end text)
226 (with-silent-modifications
227 (catch 'cancel-atomic-change
228 (atomic-change-group
229 (save-excursion
230 (goto-char (overlay-start capf-autosuggest--overlay))
231 (setq beg (point))
232 (insert-and-inherit capf-autosuggest--str)
233 (setq end (point)))
234 (call-interactively command)
235 (and (> (point) beg)
236 (<= (point) end)
237 (setq text (buffer-substring beg (point))))
238 (throw 'cancel-atomic-change nil))))
239 (when text
240 (if (= (point) beg)
241 (insert text)
242 (save-excursion
243 (goto-char beg)
244 (insert text))))))
246 (declare-function evil-forward-char "ext:evil-commands" nil t)
247 (declare-function evil-end-of-line "ext:evil-commands" nil t)
248 (declare-function evil-end-of-visual-line "ext:evil-commands" nil t)
249 (declare-function evil-end-of-line-or-visual-line "ext:evil-commands" nil t)
250 (declare-function evil-middle-of-visual-line "ext:evil-commands" nil t)
251 (declare-function evil-last-non-blank "ext:evil-commands" nil t)
252 (declare-function evil-forward-word-begin "ext:evil-commands" nil t)
253 (declare-function evil-forward-word-end "ext:evil-commands" nil t)
254 (declare-function evil-forward-WORD-begin "ext:evil-commands" nil t)
255 (declare-function evil-forward-WORD-end "ext:evil-commands" nil t)
257 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-forward-word forward-word)
258 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-forward-char forward-char)
259 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-forward-sexp forward-sexp)
260 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-end-of-line end-of-line)
261 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-move-end-of-line move-end-of-line)
262 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-end-of-visual-line end-of-visual-line)
263 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-char evil-forward-char)
264 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-end-of-line evil-end-of-line)
265 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-end-of-visual-line evil-end-of-visual-line)
266 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-end-of-line-or-visual-line evil-end-of-line-or-visual-line)
267 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-middle-of-visual-line evil-middle-of-visual-line)
268 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-last-non-blank evil-last-non-blank)
269 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-word-begin evil-forward-word-begin)
270 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-word-end evil-forward-word-end)
271 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-WORD-begin evil-forward-WORD-begin)
272 (capf-autosuggest-define-partial-accept-cmd capf-autosuggest-evil-forward-WORD-end evil-forward-WORD-end)
274 (defun capf-autosuggest-accept ()
275 "Accept current auto-suggestion.
276 Do not call this command if variable `capf-autosuggest-active-mode' is
277 inactive."
278 (interactive)
279 (capf-autosuggest-call-partial-accept-cmd
280 (lambda ()
281 (interactive)
282 (goto-char (overlay-start capf-autosuggest--overlay)))))
285 (defun capf-autosuggest-comint-previous-matching-input-from-input (n)
286 "Like `comint-previous-matching-input-from-input'.
287 But increase arument N by 1, if positive, but not on command
288 repetition."
289 (interactive "p")
290 (and (not (memq last-command '(comint-previous-matching-input-from-input
291 comint-next-matching-input-from-input)))
292 (> n 0)
293 (setq n (1+ n)))
294 (comint-previous-matching-input-from-input n)
295 (setq this-command #'comint-previous-matching-input-from-input))
297 (defun capf-autosuggest-eshell-previous-matching-input-from-input (n)
298 "Like `eshell-previous-matching-input-from-input'.
299 But increase arument N by 1, if positive, but not on command
300 repetition."
301 (interactive "p")
302 (and (not (memq last-command '(eshell-previous-matching-input-from-input
303 eshell-next-matching-input-from-input)))
304 (> n 0)
305 (setq n (1+ n)))
306 (eshell-previous-matching-input-from-input n)
307 (setq this-command #'eshell-previous-matching-input-from-input))
309 (defvar capf-autosuggest-active-mode-map
310 (let ((map (make-sparse-keymap)))
311 (define-key map [remap forward-word] #'capf-autosuggest-forward-word)
312 (define-key map [remap forward-char] #'capf-autosuggest-forward-char)
313 (define-key map [remap forward-sexp] #'capf-autosuggest-forward-sexp)
314 (define-key map [remap end-of-line] #'capf-autosuggest-end-of-line)
315 (define-key map [remap move-end-of-line] #'capf-autosuggest-move-end-of-line)
316 (define-key map [remap end-of-visual-line] #'capf-autosuggest-end-of-visual-line)
318 (define-key map [remap evil-forward-char] #'capf-autosuggest-evil-forward-char)
319 (define-key map [remap evil-end-of-line] #'capf-autosuggest-evil-end-of-line)
320 (define-key map [remap evil-end-of-visual-line] #'capf-autosuggest-evil-end-of-visual-line)
321 (define-key map [remap evil-end-of-line-or-visual-line] #'capf-autosuggest-evil-end-of-line-or-visual-line)
322 (define-key map [remap evil-middle-of-visual-line] #'capf-autosuggest-evil-middle-of-visual-line)
323 (define-key map [remap evil-last-non-blank] #'capf-autosuggest-evil-last-non-blank)
324 (define-key map [remap evil-forward-word-begin] #'capf-autosuggest-evil-forward-word-begin)
325 (define-key map [remap evil-forward-word-end] #'capf-autosuggest-evil-forward-word-end)
326 (define-key map [remap evil-forward-WORD-begin] #'capf-autosuggest-evil-forward-WORD-begin)
327 (define-key map [remap evil-forward-WORD-end] #'capf-autosuggest-evil-forward-WORD-end)
329 (define-key map [remap eshell-previous-matching-input-from-input]
330 #'capf-autosuggest-eshell-previous-matching-input-from-input)
331 (define-key map [remap comint-previous-matching-input-from-input]
332 #'capf-autosuggest-comint-previous-matching-input-from-input)
333 map)
334 "Keymap active when an auto-suggestion is shown.")
336 (define-minor-mode capf-autosuggest-active-mode
337 "Active when auto-suggested overlay is shown."
338 :group 'completion
339 (unless capf-autosuggest-active-mode
340 (delete-overlay capf-autosuggest--overlay)))
342 (defun capf-autosuggest-active-mode-deactivate ()
343 "Deactivate `capf-autosuggest-active-mode'."
344 (capf-autosuggest-active-mode -1))
346 ;;;; History completion function
348 ;;;###autoload
349 (defun capf-autosuggest-comint-capf ()
350 "Completion-at-point function for comint input history.
351 Is only applicable if point is after the last prompt."
352 (let ((ring comint-input-ring)
353 (beg nil))
354 (and ring (ring-p ring) (not (ring-empty-p ring))
355 (or (and (setq beg comint-accum-marker)
356 (setq beg (marker-position beg)))
357 (and (setq beg (get-buffer-process (current-buffer)))
358 (setq beg (marker-position (process-mark beg)))))
359 (>= (point) beg)
360 (list beg (if comint-use-prompt-regexp
361 (line-end-position)
362 (field-end))
363 (capf-autosuggest--completion-table ring)
364 :exclusive 'no))))
366 ;;;###autoload
367 (defun capf-autosuggest-eshell-capf ()
368 "Completion-at-point function for eshell input history.
369 Is only applicable if point is after the last prompt."
370 (let ((ring eshell-history-ring)
371 (beg nil))
372 (and ring (ring-p ring) (not (ring-empty-p ring))
373 (setq beg eshell-last-output-end)
374 (setq beg (marker-position beg))
375 (>= (point) beg)
376 (list (save-excursion (eshell-bol) (point)) (point-max)
377 (capf-autosuggest--completion-table ring)
378 :exclusive 'no))))
380 (defun capf-autosuggest--completion-table (ring)
381 "Return a completion table to complete on RING."
382 (let (self)
383 (setq
384 self
385 (lambda (input predicate action)
386 (cond
387 ((eq action t)
388 (cl-loop
389 with only-one = capf-autosuggest-all-completions-only-one
390 with regexps = completion-regexp-list
391 for i below (ring-size ring)
392 for elem = (ring-ref ring i)
393 if (string-prefix-p input elem)
394 if (cl-loop for regex in regexps
395 always (string-match-p regex elem))
396 if (or (null predicate)
397 (funcall predicate elem))
398 if only-one
399 return (list elem)
400 else collect elem))
401 ((eq action nil)
402 (complete-with-action
403 nil (let ((capf-autosuggest-all-completions-only-one nil))
404 (funcall self input predicate t))
405 input predicate))
406 ((eq action 'lambda)
407 (and (ring-member ring input)
408 (or (null predicate)
409 (funcall predicate input))
410 (cl-loop for regex in completion-regexp-list
411 always (string-match-p regex input))))
412 (t (complete-with-action
413 action (ring-elements ring) input predicate)))))))
415 ;;;###autoload
416 (defun capf-autosuggest-setup-comint ()
417 "Setup capf-autosuggest for history suggestion in comint."
418 (capf-autosuggest-mode)
419 (add-hook 'capf-autosuggest-capf-functions #'capf-autosuggest-comint-capf nil t))
421 ;;;###autoload
422 (defun capf-autosuggest-setup-eshell ()
423 "Setup capf-autosuggest for history suggestion in eshell."
424 (capf-autosuggest-mode)
425 (add-hook 'capf-autosuggest-capf-functions #'capf-autosuggest-eshell-capf nil t))
428 (provide 'capf-autosuggest)
429 ;;; capf-autosuggest.el ends here