1 ;;; avy-jump.el --- jump to things tree-style. -*- lexical-binding: t -*-
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
7 ;; This file is part of GNU Emacs.
9 ;; This file is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; For a full copy of the GNU General Public License
20 ;; see <http://www.gnu.org/licenses/>.
24 ;; This package offers various commands for navigating to things using `avy'.
25 ;; They are in the "Commands" outline.
33 (defgroup avy-jump nil
34 "Jump to things tree-style."
38 (defcustom avy-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
39 "Default keys for jumping."
40 :type '(repeat :tag "Keys" character))
42 (defcustom avy-keys-alist nil
43 "Alist of avy-jump commands to `avy-keys' overriding the default `avy-keys'."
45 :key-type (choice :tag "Command"
47 (const avy-goto-char-2)
50 (const avy-goto-subword-0)
51 (const avy-goto-subword-1)
52 (const avy-goto-word-0)
53 (const avy-goto-word-1)
55 (const avy-copy-region)
56 (const avy-move-line))
57 :value-type (repeat :tag "Keys" character)))
59 (defcustom avy-style 'pre
60 "The default method of displaying the overlays.
61 Use `avy-styles-alist' to customize this per-command."
63 (const :tag "Pre" pre)
65 (const :tag "At Full" at-full)
66 (const :tag "Post" post)))
68 (defcustom avy-styles-alist nil
69 "Alist of avy-jump commands to the style for each command.
70 If the commands isn't on the list, `avy-style' is used."
72 :key-type (choice :tag "Command"
74 (const avy-goto-char-2)
77 (const avy-goto-subword-0)
78 (const avy-goto-subword-1)
79 (const avy-goto-word-0)
80 (const avy-goto-word-1)
82 (const avy-copy-region)
83 (const avy-move-line))
85 (const :tag "Pre" pre)
87 (const :tag "At Full" at-full)
88 (const :tag "Post" post))))
90 (defmacro avy--with-avy-keys (command &rest body)
91 "Set `avy-keys' according to COMMAND and execute BODY."
93 `(let ((avy-keys (or (cdr (assq ',command avy-keys-alist))
95 (avy-style (or (cdr (assq ',command avy-styles-alist))
99 (defcustom avy-background nil
100 "When non-nil, a gray background will be added during the selection."
103 (defcustom avy-word-punc-regexp "[!-/:-@[-`{-~]"
104 "Regexp of punctuation chars that count as word starts for `avy-goto-word-1.
105 When nil, punctuation chars will not be matched.
107 \"[!-/:-@[-`{-~]\" will match all printable punctuation chars."
110 (defface avy-lead-face
111 '((t (:foreground "white" :background "#e52b50")))
112 "Face used for the leading chars.")
114 (defface avy-background-face
115 '((t (:foreground "gray40")))
116 "Face for whole window background during selection.")
119 (defcustom avy-all-windows t
120 "When non-nil, loop though all windows for candidates."
123 (defmacro avy-dowindows (flip &rest body)
124 "Depending on FLIP and `avy-all-windows' run BODY in each or selected window."
126 `(let ((avy-all-windows (if ,flip
127 (not avy-all-windows)
129 (dolist (wnd (if avy-all-windows
130 (cons (selected-window)
131 (delete (selected-window) (window-list)))
132 (list (selected-window))))
133 (with-selected-window wnd
134 (unless (memq major-mode '(image-mode doc-view-mode))
140 POS is either a position or (BEG . END)."
142 (message "zero candidates")
143 (select-window (cdr x))
147 (unless (= pt (point)) (push-mark))
150 (defun avy--process (candidates overlay-fn)
151 "Select one of CANDIDATES using `avy-read'.
152 Use OVERLAY-FN to visualize the decision overlay."
154 (cl-case (length candidates)
160 (avy--make-backgrounds
163 (list (selected-window))))
164 (avy-read (avy-tree candidates avy-keys)
166 #'avy--remove-leading-chars)))
169 (defvar avy--overlays-back nil
170 "Hold overlays for when `avy-background' is t.")
172 (defun avy--make-backgrounds (wnd-list)
173 "Create a dim background overlay for each window on WND-LIST."
175 (setq avy--overlays-back
177 (let ((ol (make-overlay
181 (overlay-put ol 'face 'avy-background-face)
182 (overlay-put ol 'window w)
188 (mapc #'delete-overlay avy--overlays-back)
189 (setq avy--overlays-back nil)
190 (avy--remove-leading-chars))
192 (defun avy--regex-candidates (regex &optional beg end pred)
193 "Return all elements that match REGEX.
194 Each element of the list is ((BEG . END) . WND)
195 When PRED is non-nil, it's a filter for matching point positions."
198 (let ((we (or end (window-end (selected-window) t))))
200 (goto-char (or beg (window-start)))
201 (while (re-search-forward regex we t)
202 (unless (get-char-property (point) 'invisible)
203 (when (or (null pred)
205 (push (cons (cons (match-beginning 0)
207 wnd) candidates)))))))
208 (nreverse candidates)))
210 (defvar avy--overlay-offset 0
211 "The offset to apply in `avy--overlay'.")
213 (defvar avy--overlays-lead nil
214 "Hold overlays for leading chars.")
216 (defun avy--remove-leading-chars ()
217 "Remove leading char overlays."
218 (mapc #'delete-overlay avy--overlays-lead)
219 (setq avy--overlays-lead nil))
221 (defun avy--overlay (str pt wnd)
222 "Create an overlay with STR at PT in WND."
223 (when (<= (1+ pt) (with-selected-window wnd (point-max)))
224 (let* ((pt (+ pt avy--overlay-offset))
225 (ol (make-overlay pt (1+ pt) (window-buffer wnd)))
226 (old-str (with-selected-window wnd
227 (buffer-substring pt (1+ pt)))))
229 (setq old-str (propertize
230 old-str 'face 'avy-background-face)))
231 (overlay-put ol 'window wnd)
232 (overlay-put ol 'display (concat str old-str))
233 (push ol avy--overlays-lead))))
235 (defun avy--overlay-pre (path leaf)
236 "Create an overlay with PATH at LEAF.
237 PATH is a list of keys from tree root to LEAF.
238 LEAF is normally ((BEG . END) . WND)."
240 (propertize (apply #'string (reverse path))
241 'face 'avy-lead-face)
242 (cond ((numberp leaf)
252 (defun avy--overlay-at (path leaf)
253 "Create an overlay with PATH at LEAF.
254 PATH is a list of keys from tree root to LEAF.
255 LEAF is normally ((BEG . END) . WND)."
256 (let ((str (propertize
257 (string (car (last path)))
258 'face 'avy-lead-face))
259 (pt (if (consp (car leaf))
263 (let ((ol (make-overlay pt (1+ pt)
264 (window-buffer wnd)))
265 (old-str (with-selected-window wnd
266 (buffer-substring pt (1+ pt)))))
268 (setq old-str (propertize
269 old-str 'face 'avy-background-face)))
270 (overlay-put ol 'window wnd)
271 (overlay-put ol 'display (if (string= old-str "\n")
274 (push ol avy--overlays-lead))))
276 (defun avy--overlay-at-full (path leaf)
277 "Create an overlay with PATH at LEAF.
278 PATH is a list of keys from tree root to LEAF.
279 LEAF is normally ((BEG . END) . WND)."
280 (let* ((str (propertize
281 (apply #'string (reverse path))
282 'face 'avy-lead-face))
284 (pt (if (consp (car leaf))
288 (let ((ol (make-overlay pt (+ pt len)
289 (window-buffer wnd)))
290 (old-str (with-selected-window wnd
291 (buffer-substring pt (1+ pt)))))
293 (setq old-str (propertize
294 old-str 'face 'avy-background-face)))
295 (overlay-put ol 'window wnd)
296 (overlay-put ol 'display (if (string= old-str "\n")
299 (push ol avy--overlays-lead))))
301 (defun avy--overlay-post (path leaf)
302 "Create an overlay with PATH at LEAF.
303 PATH is a list of keys from tree root to LEAF.
304 LEAF is normally ((BEG . END) . WND)."
306 (propertize (apply #'string (reverse path))
307 'face 'avy-lead-face)
308 (cond ((numberp leaf)
318 (defun avy--style-fn (style)
319 "Transform STYLE symbol to a style function."
321 (pre #'avy--overlay-pre)
322 (at #'avy--overlay-at)
323 (at-full 'avy--overlay-at-full)
324 (post #'avy--overlay-post)
325 (t (error "Unexpected style %S" style))))
327 (defun avy--generic-jump (regex window-flip style)
329 When WINDOW-FLIP is non-nil, do the opposite of `avy-all-windows'.
330 STYLE determines the leading char overlay style."
331 (let ((avy-all-windows
333 (not avy-all-windows)
337 (avy--regex-candidates regex)
338 (avy--style-fn style)))))
342 (defun avy-goto-char (&optional arg)
343 "Read one char and jump to it.
344 The window scope is determined by `avy-all-windows' (ARG negates it)."
346 (avy--with-avy-keys avy-goto-char
348 (let ((c (read-char "char: ")))
351 (regexp-quote (string c))))
356 (defun avy-goto-char-2 (&optional arg)
357 "Read two consecutive chars and jump to the first one.
358 The window scope is determined by `avy-all-windows' (ARG negates it)."
360 (avy--with-avy-keys avy-goto-char-2
362 (regexp-quote (string
363 (read-char "char 1: ")
364 (read-char "char 2: ")))
369 (defun avy-isearch ()
370 "Jump to one of the current isearch candidates."
372 (avy--with-avy-keys avy-isearch
374 (avy--regex-candidates isearch-string))
377 (avy--process candidates #'avy--overlay-post)))
379 (avy--goto candidate))))
382 (defun avy-goto-word-0 (arg)
383 "Jump to a word start.
384 The window scope is determined by `avy-all-windows' (ARG negates it)."
386 (avy--with-avy-keys avy-goto-word-0
387 (avy--generic-jump "\\b\\sw" arg avy-style)))
390 (defun avy-goto-word-1 (&optional arg)
391 "Read one char at word start and jump there.
392 The window scope is determined by `avy-all-windows' (ARG negates it)."
394 (avy--with-avy-keys avy-goto-word-1
395 (let* ((str (string (read-char "char: ")))
396 (regex (cond ((string= str ".")
398 ((and avy-word-punc-regexp
399 (string-match avy-word-punc-regexp str))
405 (avy--generic-jump regex arg avy-style))))
407 (declare-function subword-backward "subword")
410 (defun avy-goto-subword-0 (&optional arg predicate)
411 "Jump to a word or subword start.
413 The window scope is determined by `avy-all-windows' (ARG negates it).
415 When PREDICATE is non-nil it's a function of zero parameters that
419 (avy--with-avy-keys avy-goto-subword-0
420 (let ((case-fold-search nil)
423 (let ((ws (window-start)))
425 (goto-char (window-end (selected-window) t))
427 (while (> (point) ws)
428 (when (or (null predicate)
429 (and predicate (funcall predicate)))
430 (push (cons (point) (selected-window)) candidates))
431 (subword-backward)))))
433 (avy--process candidates (avy--style-fn avy-style))))))
436 (defun avy-goto-subword-1 (&optional arg)
437 "Prompt for a subword start char and jump there.
438 The window scope is determined by `avy-all-windows' (ARG negates it).
439 The case is ignored."
441 (avy--with-avy-keys avy-goto-subword-1
442 (let ((char (downcase (read-char "char: "))))
444 arg (lambda () (eq (downcase (char-after)) char))))))
446 (defun avy--line (&optional arg)
448 The window scope is determined by `avy-all-windows' (ARG negates it)."
449 (let ((avy-background nil)
452 (let ((ws (window-start)))
455 (narrow-to-region ws (window-end (selected-window) t))
456 (goto-char (point-min))
457 (while (< (point) (point-max))
458 (unless (get-char-property
459 (max (1- (point)) ws) 'invisible)
460 (push (cons (point) (selected-window)) candidates))
461 (forward-line 1))))))
462 (avy--process (nreverse candidates) #'avy--overlay-pre)))
465 (defun avy-goto-line (&optional arg)
466 "Jump to a line start in current buffer.
467 The window scope is determined by `avy-all-windows' (ARG negates it)."
469 (avy--with-avy-keys avy-goto-line
470 (avy--goto (avy--line arg))))
473 (defun avy-copy-line (arg)
474 "Copy a selected line above the current line.
475 ARG lines can be used."
477 (avy--with-avy-keys avy-copy-line
478 (let ((start (car (avy--line))))
479 (move-beginning-of-line nil)
482 (buffer-substring-no-properties
486 (move-end-of-line arg)
491 (defun avy-move-line (arg)
492 "Move a selected line above the current line.
493 ARG lines can be used."
495 (avy--with-avy-keys avy-move-line
496 (let ((start (car (avy--line))))
497 (move-beginning-of-line nil)
501 (move-end-of-line arg)
502 (kill-region start (point)))
508 (defun avy-copy-region ()
509 "Select two lines and copy the text between them here."
511 (avy--with-avy-keys avy-copy-region
512 (let ((beg (car (avy--line)))
513 (end (car (avy--line)))
514 (pad (if (bolp) "" "\n")))
515 (move-beginning-of-line nil)
518 (buffer-substring-no-properties
522 (line-end-position)))
526 (defun avy-setup-default ()
527 "Setup the default shortcuts."
528 (eval-after-load "isearch"
529 '(define-key isearch-mode-map (kbd "C-'") 'avy-isearch)))
531 (defcustom avy-timeout-seconds 0.5
532 "How many seconds to wait for the second char.")
535 (defun avy-goto-char-timer (&optional arg)
536 "Read one or two consecutive chars and jump to the first one.
537 The window scope is determined by `avy-all-windows' (ARG negates it)."
539 (let ((c1 (read-char "char 1: "))
540 (c2 (read-char "char 2: " nil avy-timeout-seconds)))
549 (define-obsolete-variable-alias
550 'avy-goto-char-style 'avy-style "0.1.0"
551 "Use `avy-style' and `avy-styles-alist' instead.")
552 (define-obsolete-variable-alias
553 'avy-goto-word-style 'avy-style "0.1.0"
554 "Use `avy-style' and `avy-styles-alist' instead.")
555 (define-obsolete-variable-alias 'avi-keys 'avy-keys "0.1.0")
556 (define-obsolete-variable-alias 'avi-background 'avy-background "0.1.0")
557 (define-obsolete-variable-alias 'avi-word-punc-regexp 'avy-word-punc-regexp "0.1.0")
558 (define-obsolete-face-alias 'avi-lead-face 'avy-lead-face "0.1.0")
559 (define-obsolete-function-alias 'avi--goto 'avy--goto "0.1.0")
560 (define-obsolete-function-alias 'avi--process 'avy--process "0.1.0")
561 (define-obsolete-variable-alias 'avi-all-windows 'avy-all-windows "0.1.0")
562 (define-obsolete-function-alias 'avi--overlay-pre 'avy--overlay-pre "0.1.0")
563 (define-obsolete-function-alias 'avi--overlay-at 'avy--overlay-at "0.1.0")
564 (define-obsolete-function-alias 'avi--overlay-post 'avy--overlay-post "0.1.0")
565 (define-obsolete-function-alias 'avi-goto-char 'avy-goto-char "0.1.0")
566 (define-obsolete-function-alias 'avi-goto-char-2 'avy-goto-char-2 "0.1.0")
567 (define-obsolete-function-alias 'avi-isearch 'avy-isearch "0.1.0")
568 (define-obsolete-function-alias 'avi-goto-word-0 'avy-goto-word-0 "0.1.0")
569 (define-obsolete-function-alias 'avi-goto-subword-0 'avy-goto-subword-0 "0.1.0")
570 (define-obsolete-function-alias 'avi-goto-word-1 'avy-goto-word-1 "0.1.0")
571 (define-obsolete-function-alias 'avi-goto-line 'avy-goto-line "0.1.0")
572 (define-obsolete-function-alias 'avi-copy-line 'avy-copy-line "0.1.0")
573 (define-obsolete-function-alias 'avi-move-line 'avy-move-line "0.1.0")
574 (define-obsolete-function-alias 'avi-copy-region 'avy-copy-region "0.1.0")
575 (define-obsolete-function-alias 'avi--regex-candidates 'avy--regex-candidates "0.1.0")
579 ;;; avy-jump.el ends here