]> code.delx.au - gnu-emacs-elpa/blob - swiper.el
ivy.el (ivy-index-functions-alist): New variable
[gnu-emacs-elpa] / swiper.el
1 ;;; swiper.el --- Isearch with an overview. Oh, man! -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
4
5 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
6 ;; URL: https://github.com/abo-abo/swiper
7 ;; Version: 0.5.1
8 ;; Package-Requires: ((emacs "24.1"))
9 ;; Keywords: matching
10
11 ;; This file is part of GNU Emacs.
12
13 ;; This file is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 3, or (at your option)
16 ;; any later version.
17
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; For a full copy of the GNU General Public License
24 ;; see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27 ;;
28 ;; This package gives an overview of the current regex search
29 ;; candidates. The search regex can be split into groups with a
30 ;; space. Each group is highlighted with a different face.
31 ;;
32 ;; It can double as a quick `regex-builder', although only single
33 ;; lines will be matched.
34 ;;
35 ;; It also provides `ivy-mode': a global minor mode that uses the
36 ;; matching back end of `swiper' for all matching on your system,
37 ;; including file matching. You can use it in place of `ido-mode'
38 ;; (can't have both on at once).
39
40 ;;; Code:
41 (require 'ivy)
42
43 (defgroup swiper nil
44 "`isearch' with an overview."
45 :group 'matching
46 :prefix "swiper-")
47
48 (defface swiper-match-face-1
49 '((t (:inherit isearch-lazy-highlight-face)))
50 "The background face for `swiper' matches.")
51
52 (defface swiper-match-face-2
53 '((t (:inherit isearch)))
54 "Face for `swiper' matches modulo 1.")
55
56 (defface swiper-match-face-3
57 '((t (:inherit match)))
58 "Face for `swiper' matches modulo 2.")
59
60 (defface swiper-match-face-4
61 '((t (:inherit isearch-fail)))
62 "Face for `swiper' matches modulo 3.")
63
64 (defface swiper-minibuffer-match-face-1
65 '((((class color) (background light))
66 :background "#d3d3d3")
67 (((class color) (background dark))
68 :background "#555555"))
69 "The background face for `swiper' minibuffer matches."
70 :group 'function-args-faces)
71
72 (defface swiper-minibuffer-match-face-2
73 '((((class color) (background light))
74 :background "#e99ce8" :weight bold)
75 (((class color) (background dark))
76 :background "#777777" :weight bold))
77 "Face for `swiper' minibuffer matches modulo 1.")
78
79 (defface swiper-minibuffer-match-face-3
80 '((((class color) (background light))
81 :background "#bbbbff" :weight bold)
82 (((class color) (background dark))
83 :background "#7777ff" :weight bold))
84 "Face for `swiper' minibuffer matches modulo 2.")
85
86 (defface swiper-minibuffer-match-face-4
87 '((((class color) (background light))
88 :background "#ffbbff" :weight bold)
89 (((class color) (background dark))
90 :background "#8a498a" :weight bold))
91 "Face for `swiper' minibuffer matches modulo 3.")
92
93 (defface swiper-line-face
94 '((t (:inherit highlight)))
95 "Face for current `swiper' line.")
96
97 (defcustom swiper-faces '(swiper-match-face-1
98 swiper-match-face-2
99 swiper-match-face-3
100 swiper-match-face-4)
101 "List of `swiper' faces for group matches.")
102
103 (defcustom swiper-min-highlight 2
104 "Only highlight matches for regexps at least this long."
105 :type 'integer)
106
107 (defvar swiper-map
108 (let ((map (make-sparse-keymap)))
109 (define-key map (kbd "M-q") 'swiper-query-replace)
110 (define-key map (kbd "C-l") 'swiper-recenter-top-bottom)
111 (define-key map (kbd "C-'") 'swiper-avy)
112 map)
113 "Keymap for swiper.")
114
115 (defun swiper-query-replace ()
116 "Start `query-replace' with string to replace from last search string."
117 (interactive)
118 (if (null (window-minibuffer-p))
119 (user-error "Should only be called in the minibuffer through `swiper-map'")
120 (let* ((enable-recursive-minibuffers t)
121 (from (ivy--regex ivy-text))
122 (to (query-replace-read-to from "Query replace" t)))
123 (delete-minibuffer-contents)
124 (ivy-set-action (lambda (_)
125 (with-ivy-window
126 (move-beginning-of-line 1)
127 (perform-replace from to
128 t t nil))))
129 (swiper--cleanup)
130 (exit-minibuffer))))
131
132 (defvar avy-background)
133 (defvar avy-all-windows)
134 (defvar avy-style)
135 (defvar avy-keys)
136 (declare-function avy--regex-candidates "ext:avy")
137 (declare-function avy--process "ext:avy")
138 (declare-function avy--overlay-post "ext:avy")
139 (declare-function avy-action-goto "ext:avy")
140 (declare-function avy--done "ext:avy")
141 (declare-function avy--make-backgrounds "ext:avy")
142 (declare-function avy-window-list "ext:avy")
143 (declare-function avy-read "ext:avy")
144 (declare-function avy-read-de-bruijn "ext:avy")
145 (declare-function avy-tree "ext:avy")
146 (declare-function avy-push-mark "ext:avy")
147 (declare-function avy--remove-leading-chars "ext:avy")
148
149 ;;;###autoload
150 (defun swiper-avy ()
151 "Jump to one of the current swiper candidates."
152 (interactive)
153 (unless (string= ivy-text "")
154 (let* ((avy-all-windows nil)
155 (candidates (append
156 (with-ivy-window
157 (avy--regex-candidates
158 (ivy--regex ivy-text)))
159 (save-excursion
160 (save-restriction
161 (narrow-to-region (window-start) (window-end))
162 (goto-char (point-min))
163 (forward-line)
164 (let ((cands))
165 (while (< (point) (point-max))
166 (push (cons (1+ (point))
167 (selected-window))
168 cands)
169 (forward-line))
170 cands)))))
171 (candidate (unwind-protect
172 (prog2
173 (avy--make-backgrounds
174 (append (avy-window-list)
175 (list (ivy-state-window ivy-last))))
176 (if (eq avy-style 'de-bruijn)
177 (avy-read-de-bruijn
178 candidates avy-keys)
179 (avy-read (avy-tree candidates avy-keys)
180 #'avy--overlay-post
181 #'avy--remove-leading-chars))
182 (avy-push-mark))
183 (avy--done))))
184 (if (window-minibuffer-p (cdr candidate))
185 (progn
186 (ivy-set-index (- (line-number-at-pos (car candidate)) 2))
187 (ivy--exhibit)
188 (ivy-done)
189 (ivy-call))
190 (ivy-quit-and-run
191 (avy-action-goto (caar candidate)))))))
192
193 (defun swiper-recenter-top-bottom (&optional arg)
194 "Call (`recenter-top-bottom' ARG)."
195 (interactive "P")
196 (with-ivy-window
197 (recenter-top-bottom arg)))
198
199 (defun swiper-font-lock-ensure ()
200 "Ensure the entired buffer is highlighted."
201 (unless (or (derived-mode-p 'magit-mode)
202 (bound-and-true-p magit-blame-mode)
203 (memq major-mode '(package-menu-mode
204 gnus-summary-mode
205 gnus-article-mode
206 gnus-group-mode
207 emms-playlist-mode erc-mode
208 org-agenda-mode
209 dired-mode
210 jabber-chat-mode
211 elfeed-search-mode
212 fundamental-mode
213 Man-mode
214 woman-mode
215 mu4e-view-mode
216 mu4e-headers-mode
217 help-mode)))
218 (unless (> (buffer-size) 100000)
219 (if (fboundp 'font-lock-ensure)
220 (font-lock-ensure)
221 (with-no-warnings (font-lock-fontify-buffer))))))
222
223 (defvar swiper--format-spec ""
224 "Store the current candidates format spec.")
225
226 (defvar swiper--width nil
227 "Store the amount of digits needed for the longest line nubmer.")
228
229 (defvar swiper-use-visual-line nil
230 "When non-nil, use `line-move' instead of `forward-line'.")
231
232 (defun swiper--candidates ()
233 "Return a list of this buffer lines."
234 (setq swiper-use-visual-line
235 (and (not (eq major-mode 'org-mode))
236 visual-line-mode
237 (< (buffer-size) 20000)))
238 (let ((n-lines (count-lines (point-min) (point-max))))
239 (unless (zerop n-lines)
240 (setq swiper--width (1+ (floor (log n-lines 10))))
241 (setq swiper--format-spec
242 (format "%%-%dd " swiper--width))
243 (let ((line-number 0)
244 (advancer (if swiper-use-visual-line
245 (lambda (arg) (line-move arg t))
246 #'forward-line))
247 candidates)
248 (save-excursion
249 (goto-char (point-min))
250 (swiper-font-lock-ensure)
251 (while (< (point) (point-max))
252 (let ((str (concat " " (buffer-substring
253 (point)
254 (if swiper-use-visual-line
255 (save-excursion
256 (end-of-visual-line)
257 (point))
258 (line-end-position))))))
259 (put-text-property 0 1 'display
260 (format swiper--format-spec
261 (cl-incf line-number))
262 str)
263 (push str candidates))
264 (funcall advancer 1))
265 (nreverse candidates))))))
266
267 (defvar swiper--opoint 1
268 "The point when `swiper' starts.")
269
270 ;;;###autoload
271 (defun swiper (&optional initial-input)
272 "`isearch' with an overview.
273 When non-nil, INITIAL-INPUT is the initial search pattern."
274 (interactive)
275 (swiper--ivy initial-input))
276
277 (defvar swiper--anchor nil
278 "A line number to which the search should be anchored.")
279
280 (defvar swiper--len 0
281 "The last length of input for which an anchoring was made.")
282
283 (defun swiper--init ()
284 "Perform initialization common to both completion methods."
285 (setq swiper--opoint (point))
286 (setq swiper--len 0)
287 (setq swiper--anchor (line-number-at-pos)))
288
289 (defun swiper--re-builder (str)
290 "Transform STR into a swiper regex.
291 This is the regex used in the minibuffer, since the candidates
292 there have line numbers. In the buffer, `ivy--regex' should be used."
293 (cond
294 ((equal str "")
295 "")
296 ((equal str "^")
297 ".")
298 ((string-match "^\\^" str)
299 (setq ivy--old-re "")
300 (let ((re (ivy--regex-plus (substring str 1))))
301 (format "^[0-9][0-9 ]\\{%d\\}%s"
302 swiper--width
303 (if (zerop ivy--subexps)
304 (prog1 (format "\\(%s\\)" re)
305 (setq ivy--subexps 1))
306 re))))
307 (t
308 (ivy--regex-plus str))))
309
310 (defvar swiper-history nil
311 "History for `swiper'.")
312
313 (defun swiper--ivy (&optional initial-input)
314 "`isearch' with an overview using `ivy'.
315 When non-nil, INITIAL-INPUT is the initial search pattern."
316 (interactive)
317 (swiper--init)
318 (let ((candidates (swiper--candidates))
319 (preselect (buffer-substring-no-properties
320 (line-beginning-position)
321 (line-end-position)))
322 (minibuffer-allow-text-properties t)
323 res)
324 (unwind-protect
325 (setq res (ivy-read
326 "Swiper: "
327 candidates
328 :initial-input initial-input
329 :keymap swiper-map
330 :preselect preselect
331 :require-match t
332 :update-fn #'swiper--update-input-ivy
333 :unwind #'swiper--cleanup
334 :re-builder #'swiper--re-builder
335 :history 'swiper-history
336 :caller 'swiper))
337 (if (null ivy-exit)
338 (goto-char swiper--opoint)
339 (swiper--action res ivy-text)))))
340
341 (defun swiper--ensure-visible ()
342 "Remove overlays hiding point."
343 (let ((overlays (overlays-at (point)))
344 ov expose)
345 (while (setq ov (pop overlays))
346 (if (and (invisible-p (overlay-get ov 'invisible))
347 (setq expose (overlay-get ov 'isearch-open-invisible)))
348 (funcall expose ov)))))
349
350 (defvar swiper--overlays nil
351 "Store overlays.")
352
353 (defun swiper--cleanup ()
354 "Clean up the overlays."
355 (while swiper--overlays
356 (delete-overlay (pop swiper--overlays)))
357 (save-excursion
358 (goto-char (point-min))
359 (isearch-clean-overlays)))
360
361 (defun swiper--update-input-ivy ()
362 "Called when `ivy' input is updated."
363 (with-ivy-window
364 (swiper--cleanup)
365 (when (> (length ivy--current) 0)
366 (let* ((re (funcall ivy--regex-function ivy-text))
367 (re (if (stringp re) re (caar re)))
368 (str (get-text-property 0 'display ivy--current))
369 (num (if (string-match "^[0-9]+" str)
370 (string-to-number (match-string 0 str))
371 0)))
372 (goto-char (point-min))
373 (when (cl-plusp num)
374 (goto-char (point-min))
375 (if swiper-use-visual-line
376 (line-move (1- num))
377 (forward-line (1- num)))
378 (if (and (equal ivy-text "")
379 (>= swiper--opoint (line-beginning-position))
380 (<= swiper--opoint (line-end-position)))
381 (goto-char swiper--opoint)
382 (re-search-forward re (line-end-position) t))
383 (isearch-range-invisible (line-beginning-position)
384 (line-end-position))
385 (unless (and (>= (point) (window-start))
386 (<= (point) (window-end (ivy-state-window ivy-last) t)))
387 (recenter)))
388 (swiper--add-overlays re)))))
389
390 (defun swiper--add-overlays (re &optional beg end)
391 "Add overlays for RE regexp in visible part of the current buffer.
392 BEG and END, when specified, are the point bounds."
393 (let ((ov (if visual-line-mode
394 (make-overlay
395 (save-excursion
396 (beginning-of-visual-line)
397 (point))
398 (save-excursion
399 (end-of-visual-line)
400 (point)))
401 (make-overlay
402 (line-beginning-position)
403 (1+ (line-end-position))))))
404 (overlay-put ov 'face 'swiper-line-face)
405 (overlay-put ov 'window (ivy-state-window ivy-last))
406 (push ov swiper--overlays)
407 (let* ((wh (window-height))
408 (beg (or beg (save-excursion
409 (forward-line (- wh))
410 (point))))
411 (end (or end (save-excursion
412 (forward-line wh)
413 (point)))))
414 (when (>= (length re) swiper-min-highlight)
415 (save-excursion
416 (goto-char beg)
417 ;; RE can become an invalid regexp
418 (while (and (ignore-errors (re-search-forward re end t))
419 (> (- (match-end 0) (match-beginning 0)) 0))
420 (let ((i 0))
421 (while (<= i ivy--subexps)
422 (when (match-beginning i)
423 (let ((overlay (make-overlay (match-beginning i)
424 (match-end i)))
425 (face
426 (cond ((zerop ivy--subexps)
427 (cadr swiper-faces))
428 ((zerop i)
429 (car swiper-faces))
430 (t
431 (nth (1+ (mod (+ i 2) (1- (length swiper-faces))))
432 swiper-faces)))))
433 (push overlay swiper--overlays)
434 (overlay-put overlay 'face face)
435 (overlay-put overlay 'window (ivy-state-window ivy-last))
436 (overlay-put overlay 'priority i)))
437 (cl-incf i)))))))))
438
439 (defun swiper--action (x input)
440 "Goto line X and search for INPUT."
441 (if (null x)
442 (user-error "No candidates")
443 (goto-char (point-min))
444 (funcall (if swiper-use-visual-line
445 #'line-move
446 #'forward-line)
447 (1- (read (get-text-property 0 'display x))))
448 (re-search-forward
449 (ivy--regex input) (line-end-position) t)
450 (swiper--ensure-visible)
451 (when (/= (point) swiper--opoint)
452 (unless (and transient-mark-mode mark-active)
453 (push-mark swiper--opoint t)
454 (message "Mark saved where search started")))))
455
456 ;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch)
457 (defun swiper-from-isearch ()
458 "Invoke `swiper' from isearch."
459 (interactive)
460 (let ((query (if isearch-regexp
461 isearch-string
462 (regexp-quote isearch-string))))
463 (isearch-exit)
464 (swiper query)))
465
466 (defvar swiper-multi-buffers nil
467 "Store the current list of buffers.")
468
469 (defvar swiper-multi-candidates nil
470 "Store the list of candidates for `swiper-multi'.")
471
472 (defun swiper-multi-prompt ()
473 (format "Buffers (%s): "
474 (mapconcat #'identity swiper-multi-buffers ", ")))
475
476 (defun swiper-multi ()
477 "Select one or more buffers.
478 Run `swiper' for those buffers."
479 (interactive)
480 (setq swiper-multi-buffers nil)
481 (setq swiper-multi-candidates nil)
482 (ivy-read (swiper-multi-prompt)
483 'internal-complete-buffer
484 :action 'swiper-multi-action-1)
485 (ivy-read "Swiper: " swiper-multi-candidates
486 :action 'swiper-multi-action-2
487 :unwind #'swiper--cleanup
488 :caller 'swiper-multi))
489
490 (defun swiper-multi-action-1 (x)
491 (if (member x swiper-multi-buffers)
492 (progn
493 (setq swiper-multi-buffers (delete x swiper-multi-buffers)))
494 (unless (equal x "")
495 (setq swiper-multi-buffers (append swiper-multi-buffers (list x)))))
496 (let ((prompt (swiper-multi-prompt)))
497 (setf (ivy-state-prompt ivy-last) prompt)
498 (setq ivy--prompt (concat "%-4d " prompt)))
499 (cond ((memq this-command '(ivy-done
500 ivy-alt-done
501 ivy-immediate-done))
502 (let ((ww (window-width)))
503 (dolist (buf swiper-multi-buffers)
504 (with-current-buffer buf
505 (setq swiper-multi-candidates
506 (append
507 (mapcar
508 (lambda (s)
509 (setq s (concat s " "))
510 (let ((len (length s)))
511 (put-text-property
512 (1- len) len 'display
513 (concat
514 (make-string
515 (max
516 (- ww
517 (string-width s)
518 (length (buffer-name))
519 1)
520 0)
521 ?\ )
522 (buffer-name))
523 s)
524 s))
525 (swiper--candidates))
526 swiper-multi-candidates))))))
527 ((eq this-command 'ivy-call)
528 (delete-minibuffer-contents))))
529
530 (defun swiper-multi-action-2 (x)
531 (let ((buf-space (get-text-property (1- (length x)) 'display x)))
532 (with-ivy-window
533 (when (string-match "\\` *\\([^ ]+\\)\\'" buf-space)
534 (switch-to-buffer (match-string 1 buf-space))
535 (goto-char (point-min))
536 (forward-line (1- (read x)))
537 (re-search-forward
538 (ivy--regex ivy-text)
539 (line-end-position) t)
540 (unless (eq ivy-exit 'done)
541 (swiper--cleanup)
542 (swiper--add-overlays (ivy--regex ivy-text)))))))
543
544 (provide 'swiper)
545
546 ;;; swiper.el ends here