1 ;;; avy-jump.el --- jump to things tree-style. -*- lexical-binding: t -*-
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
6 ;; URL: https://github.com/abo-abo/avy-jump
8 ;; Keywords: point, location
10 ;; This file is part of GNU Emacs.
12 ;; This file 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, or (at your option)
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 ;; For a full copy of the GNU General Public License
23 ;; see <http://www.gnu.org/licenses/>.
27 ;; This package offers various commands for navigating to things using `avy'.
28 ;; They are in the "Commands" outline.
35 (defgroup avy-jump nil
36 "Jump to things tree-style."
40 (defcustom avy-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
43 (defcustom avy-background nil
44 "When non-nil, a gray background will be added during the selection."
47 (defcustom avy-word-punc-regexp "[!-/:-@[-`{-~]"
48 "Regexp of punctuation characters that should be matched when calling
49 `avy-goto-word-1' command. When nil, punctuation chars will not be matched.
51 \"[!-/:-@[-`{-~]\" will match all printable punctuation chars.")
53 (defface avy-lead-face
54 '((t (:foreground "white" :background "#e52b50")))
55 "Face used for the leading chars.")
57 (defface avy-background-face
58 '((t (:foreground "gray40")))
59 "Face for whole window background during selection.")
62 (defcustom avy-all-windows t
63 "When non-nil, loop though all windows for candidates."
66 (defmacro avy-dowindows (flip &rest body)
67 "Depending on FLIP and `avy-all-windows' run BODY in each or selected window."
69 `(let ((avy-all-windows (if ,flip
72 (dolist (wnd (if avy-all-windows
74 (list (selected-window))))
75 (with-selected-window wnd
76 (unless (memq major-mode '(image-mode doc-view-mode))
82 POS is either a position or (BEG . END)."
84 (message "zero candidates")
85 (select-window (cdr x))
89 (unless (= pt (point)) (push-mark))
92 (defun avy--process (candidates overlay-fn)
93 "Select one of CANDIDATES using `avy-read'."
95 (cl-case (length candidates)
101 (avy--make-backgrounds (list (selected-window)))
102 (avy-read (avy-tree candidates avy-keys)
104 #'avy--remove-leading-chars)))
107 (defvar avy--overlays-back nil
108 "Hold overlays for when `avy-background' is t.")
110 (defun avy--make-backgrounds (wnd-list)
111 "Create a dim background overlay for each window on WND-LIST."
113 (setq avy--overlays-back
115 (let ((ol (make-overlay
119 (overlay-put ol 'face 'avy-background-face)
125 (mapc #'delete-overlay avy--overlays-back)
126 (setq avy--overlays-back nil)
127 (avy--remove-leading-chars))
129 (defun avy--regex-candidates (regex &optional wnd beg end pred)
130 "Return all elements that match REGEX in WND.
131 Each element of the list is ((BEG . END) . WND)
132 When PRED is non-nil, it's a filter for matching point positions."
135 (let ((we (or end (window-end (selected-window) t))))
137 (goto-char (or beg (window-start)))
138 (while (re-search-forward regex we t)
139 (unless (get-char-property (point) 'invisible)
140 (when (or (null pred)
142 (push (cons (cons (match-beginning 0)
144 wnd) candidates)))))))
145 (nreverse candidates)))
147 (defvar avy--overlay-offset 0
148 "The offset to apply in `avy--overlay'.")
150 (defvar avy--overlays-lead nil
151 "Hold overlays for leading chars.")
153 (defun avy--remove-leading-chars ()
154 "Remove leading char overlays."
155 (mapc #'delete-overlay avy--overlays-lead)
156 (setq avy--overlays-lead nil))
158 (defun avy--overlay (str pt wnd)
159 "Create an overlay with STR at PT in WND."
160 (when (<= (1+ pt) (with-selected-window wnd (point-max)))
161 (let* ((pt (+ pt avy--overlay-offset))
162 (ol (make-overlay pt (1+ pt) (window-buffer wnd)))
163 (old-str (with-selected-window wnd
164 (buffer-substring pt (1+ pt)))))
166 (setq old-str (propertize
167 old-str 'face 'avy-background-face)))
168 (overlay-put ol 'window wnd)
169 (overlay-put ol 'display (concat str old-str))
170 (push ol avy--overlays-lead))))
172 (defun avy--overlay-pre (path leaf)
173 "Create an overlay with STR at LEAF.
174 PATH is a list of keys from tree root to LEAF.
175 LEAF is ((BEG . END) . WND)."
177 (propertize (apply #'string (reverse path))
178 'face 'avy-lead-face)
179 (cond ((numberp leaf)
189 (defun avy--overlay-at (path leaf)
190 "Create an overlay with STR at LEAF.
191 PATH is a list of keys from tree root to LEAF.
192 LEAF is ((BEG . END) . WND)."
193 (let ((str (propertize
194 (string (car (last path)))
195 'face 'avy-lead-face))
196 (pt (if (consp (car leaf))
200 (let ((ol (make-overlay pt (1+ pt)
201 (window-buffer wnd)))
202 (old-str (with-selected-window wnd
203 (buffer-substring pt (1+ pt)))))
205 (setq old-str (propertize
206 old-str 'face 'avy-background-face)))
207 (overlay-put ol 'window wnd)
208 (overlay-put ol 'display (if (string= old-str "\n")
211 (push ol avy--overlays-lead))))
213 (defun avy--overlay-post (path leaf)
214 "Create an overlay with STR at LEAF.
215 PATH is a list of keys from tree root to LEAF.
216 LEAF is ((BEG . END) . WND)."
218 (propertize (apply #'string (reverse path))
219 'face 'avy-lead-face)
220 (cond ((numberp leaf)
230 (defun avy--style-fn (style)
231 "Transform STYLE symbol to a style function."
233 (pre #'avy--overlay-pre)
234 (at #'avy--overlay-at)
235 (post #'avy--overlay-post)
236 (t (error "Unexpected style %S" style))))
238 (defun avy--generic-jump (regex window-flip style)
240 When WINDOW-FLIP is non-nil, do the opposite of `avy-all-windows'.
241 STYLE determines the leading char overlay style."
242 (let ((avy-all-windows
244 (not avy-all-windows)
248 (avy--regex-candidates
250 (avy--style-fn style)))))
252 (defcustom avy-goto-char-style 'pre
253 "Method of displaying the overlays for `avy-goto-char' and `avy-goto-char-2'."
255 (const :tag "Pre" pre)
257 (const :tag "Post" post)))
259 (defcustom avy-goto-word-style 'pre
260 "Method of displaying the overlays for `avy-goto-word-0' and `avy-goto-word-0'."
262 (const :tag "Pre" pre)
264 (const :tag "Post" post)))
268 (defun avy-goto-char (&optional arg)
269 "Read one char and jump to it.
270 The window scope is determined by `avy-all-windows' (ARG negates it)."
273 (let ((c (read-char "char: ")))
276 (regexp-quote (string c))))
278 avy-goto-char-style))
281 (defun avy-goto-char-2 (&optional arg)
282 "Read two consecutive chars and jump to the first one.
283 The window scope is determined by `avy-all-windows' (ARG negates it)."
286 (regexp-quote (string
287 (read-char "char 1: ")
288 (read-char "char 2: ")))
290 avy-goto-char-style))
293 (defun avy-isearch ()
294 "Jump to one of the current isearch candidates."
297 (avy--regex-candidates isearch-string))
300 (avy--process candidates #'avy--overlay-post)))
302 (avy--goto candidate)))
305 (defun avy-goto-word-0 (arg)
306 "Jump to a word start.
307 The window scope is determined by `avy-all-windows' (ARG negates it)."
309 (let ((avy-keys (number-sequence ?a ?z)))
310 (avy--generic-jump "\\b\\sw" arg avy-goto-word-style)))
313 (defun avy-goto-word-1 (&optional arg)
314 "Read one char at word start and jump there.
315 The window scope is determined by `avy-all-windows' (ARG negates it)."
317 (let* ((str (string (read-char "char: ")))
318 (regex (cond ((string= str ".")
320 ((and avy-word-punc-regexp
321 (string-match avy-word-punc-regexp str))
327 (avy--generic-jump regex arg avy-goto-word-style)))
329 (declare-function subword-backward "subword")
332 (defun avy-goto-subword-0 (&optional arg predicate)
333 "Jump to a word or subword start.
335 The window scope is determined by `avy-all-windows' (ARG negates it).
337 When PREDICATE is non-nil it's a function of zero parameters that
341 (let ((avy-keys (number-sequence ?a ?z))
342 (case-fold-search nil)
345 (let ((ws (window-start)))
347 (goto-char (window-end (selected-window) t))
349 (while (> (point) ws)
350 (when (or (null predicate)
351 (and predicate (funcall predicate)))
352 (push (cons (point) (selected-window)) candidates))
353 (subword-backward)))))
355 (avy--process candidates (avy--style-fn avy-goto-word-style)))))
358 (defun avy-goto-subword-1 (&optional arg)
359 "Prompt for a subword start char and jump there.
360 The window scope is determined by `avy-all-windows' (ARG negates it).
361 The case is ignored."
363 (let ((char (downcase (read-char "char: "))))
365 arg (lambda () (eq (downcase (char-after)) char)))))
367 (defun avy--line (&optional arg)
368 "Select line in current window."
369 (let ((avy-background nil)
372 (let ((ws (window-start)))
375 (narrow-to-region ws (window-end (selected-window) t))
376 (goto-char (point-min))
377 (while (< (point) (point-max))
378 (unless (get-char-property
379 (max (1- (point)) ws) 'invisible)
380 (push (cons (point) (selected-window)) candidates))
381 (forward-line 1))))))
382 (avy--process (nreverse candidates) #'avy--overlay-pre)))
385 (defun avy-goto-line (&optional arg)
386 "Jump to a line start in current buffer."
388 (avy--goto (avy--line arg)))
391 (defun avy-copy-line (arg)
392 "Copy a selected line above the current line.
393 ARG lines can be used."
395 (let ((start (car (avy--line))))
396 (move-beginning-of-line nil)
399 (buffer-substring-no-properties
403 (move-end-of-line arg)
408 (defun avy-move-line (arg)
409 "Move a selected line above the current line.
410 ARG lines can be used."
412 (let ((start (car (avy--line))))
413 (move-beginning-of-line nil)
417 (move-end-of-line arg)
418 (kill-region start (point)))
424 (defun avy-copy-region ()
425 "Select two lines and copy the text between them here."
427 (let ((beg (car (avy--line)))
428 (end (car (avy--line)))
429 (pad (if (bolp) "" "\n")))
430 (move-beginning-of-line nil)
433 (buffer-substring-no-properties
437 (line-end-position)))
441 (defun avy-setup-default ()
442 "Setup the default shortcuts."
443 (eval-after-load 'isearch
444 '(define-key isearch-mode-map (kbd "C-'") 'avy-isearch)))
446 (define-obsolete-variable-alias 'avi-keys 'avy-keys "0.1.0")
447 (define-obsolete-variable-alias 'avi-background 'avy-background "0.1.0")
448 (define-obsolete-variable-alias 'avi-word-punc-regexp 'avy-word-punc-regexp "0.1.0")
449 (define-obsolete-face-alias 'avi-lead-face 'avy-lead-face "0.1.0")
450 (define-obsolete-function-alias 'avi--goto 'avy--goto "0.1.0")
451 (define-obsolete-function-alias 'avi--process 'avy--process "0.1.0")
452 (define-obsolete-variable-alias 'avi-all-windows 'avy-all-windows "0.1.0")
453 (define-obsolete-function-alias 'avi--overlay-pre 'avy--overlay-pre "0.1.0")
454 (define-obsolete-function-alias 'avi--overlay-at 'avy--overlay-at "0.1.0")
455 (define-obsolete-function-alias 'avi--overlay-post 'avy--overlay-post "0.1.0")
456 (define-obsolete-function-alias 'avi-goto-char 'avy-goto-char "0.1.0")
457 (define-obsolete-function-alias 'avi-goto-char-2 'avy-goto-char-2 "0.1.0")
458 (define-obsolete-function-alias 'avi-isearch 'avy-isearch "0.1.0")
459 (define-obsolete-function-alias 'avi-goto-word-0 'avy-goto-word-0 "0.1.0")
460 (define-obsolete-function-alias 'avi-goto-subword-0 'avy-goto-subword-0 "0.1.0")
461 (define-obsolete-function-alias 'avi-goto-word-1 'avy-goto-word-1 "0.1.0")
462 (define-obsolete-function-alias 'avi-goto-line 'avy-goto-line "0.1.0")
463 (define-obsolete-function-alias 'avi-copy-line 'avy-copy-line "0.1.0")
464 (define-obsolete-function-alias 'avi-move-line 'avy-move-line "0.1.0")
465 (define-obsolete-function-alias 'avi-copy-region 'avy-copy-region "0.1.0")
466 (define-obsolete-function-alias 'avi--regex-candidates 'avy--regex-candidates "0.1.0")
470 ;;; avy-jump.el ends here