]> code.delx.au - gnu-emacs-elpa/blob - swiper.el
swiper.el (swiper-font-lock-ensure): Add occur-mode
[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.6.0
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 (define-obsolete-face-alias 'swiper-minibuffer-match-face-1
65 'ivy-minibuffer-match-face-1 "0.6.0")
66
67 (define-obsolete-face-alias 'swiper-minibuffer-match-face-2
68 'ivy-minibuffer-match-face-2 "0.6.0")
69
70 (define-obsolete-face-alias 'swiper-minibuffer-match-face-3
71 'ivy-minibuffer-match-face-3 "0.6.0")
72
73 (define-obsolete-face-alias 'swiper-minibuffer-match-face-4
74 'ivy-minibuffer-match-face-4 "0.6.0")
75
76 (defface swiper-line-face
77 '((t (:inherit highlight)))
78 "Face for current `swiper' line.")
79
80 (defcustom swiper-faces '(swiper-match-face-1
81 swiper-match-face-2
82 swiper-match-face-3
83 swiper-match-face-4)
84 "List of `swiper' faces for group matches.")
85
86 (defcustom swiper-min-highlight 2
87 "Only highlight matches for regexps at least this long."
88 :type 'integer)
89
90 (defvar swiper-map
91 (let ((map (make-sparse-keymap)))
92 (define-key map (kbd "M-q") 'swiper-query-replace)
93 (define-key map (kbd "C-l") 'swiper-recenter-top-bottom)
94 (define-key map (kbd "C-'") 'swiper-avy)
95 (define-key map (kbd "C-7") 'swiper-mc)
96 (define-key map (kbd "C-c C-f") 'swiper-toggle-face-matching)
97 map)
98 "Keymap for swiper.")
99
100 (defun swiper-query-replace ()
101 "Start `query-replace' with string to replace from last search string."
102 (interactive)
103 (if (null (window-minibuffer-p))
104 (user-error "Should only be called in the minibuffer through `swiper-map'")
105 (let* ((enable-recursive-minibuffers t)
106 (from (ivy--regex ivy-text))
107 (to (query-replace-read-to from "Query replace" t)))
108 (delete-minibuffer-contents)
109 (ivy-set-action (lambda (_)
110 (with-ivy-window
111 (move-beginning-of-line 1)
112 (perform-replace from to
113 t t nil))))
114 (swiper--cleanup)
115 (exit-minibuffer))))
116
117 (defvar avy-background)
118 (defvar avy-all-windows)
119 (defvar avy-style)
120 (defvar avy-keys)
121 (declare-function avy--regex-candidates "ext:avy")
122 (declare-function avy--process "ext:avy")
123 (declare-function avy--overlay-post "ext:avy")
124 (declare-function avy-action-goto "ext:avy")
125 (declare-function avy--done "ext:avy")
126 (declare-function avy--make-backgrounds "ext:avy")
127 (declare-function avy-window-list "ext:avy")
128 (declare-function avy-read "ext:avy")
129 (declare-function avy-read-de-bruijn "ext:avy")
130 (declare-function avy-tree "ext:avy")
131 (declare-function avy-push-mark "ext:avy")
132 (declare-function avy--remove-leading-chars "ext:avy")
133
134 ;;;###autoload
135 (defun swiper-avy ()
136 "Jump to one of the current swiper candidates."
137 (interactive)
138 (unless (string= ivy-text "")
139 (let* ((avy-all-windows nil)
140 (candidates (append
141 (with-ivy-window
142 (avy--regex-candidates
143 (ivy--regex ivy-text)))
144 (save-excursion
145 (save-restriction
146 (narrow-to-region (window-start) (window-end))
147 (goto-char (point-min))
148 (forward-line)
149 (let ((cands))
150 (while (< (point) (point-max))
151 (push (cons (1+ (point))
152 (selected-window))
153 cands)
154 (forward-line))
155 cands)))))
156 (candidate (unwind-protect
157 (prog2
158 (avy--make-backgrounds
159 (append (avy-window-list)
160 (list (ivy-state-window ivy-last))))
161 (if (eq avy-style 'de-bruijn)
162 (avy-read-de-bruijn
163 candidates avy-keys)
164 (avy-read (avy-tree candidates avy-keys)
165 #'avy--overlay-post
166 #'avy--remove-leading-chars))
167 (avy-push-mark))
168 (avy--done))))
169 (if (window-minibuffer-p (cdr candidate))
170 (progn
171 (ivy-set-index (- (line-number-at-pos (car candidate)) 2))
172 (ivy--exhibit)
173 (ivy-done)
174 (ivy-call))
175 (ivy-quit-and-run
176 (avy-action-goto (caar candidate)))))))
177
178 (declare-function mc/create-fake-cursor-at-point "ext:multiple-cursors-core")
179 (declare-function multiple-cursors-mode "ext:multiple-cursors-core")
180
181 ;;;###autoload
182 (defun swiper-mc ()
183 (interactive)
184 (unless (require 'multiple-cursors nil t)
185 (error "multiple-cursors isn't installed"))
186 (let ((cands (nreverse ivy--old-cands)))
187 (unless (string= ivy-text "")
188 (ivy-set-action
189 (lambda (_)
190 (let (cand)
191 (while (setq cand (pop cands))
192 (swiper--action cand)
193 (when cands
194 (mc/create-fake-cursor-at-point))))
195 (multiple-cursors-mode 1)))
196 (setq ivy-exit 'done)
197 (exit-minibuffer))))
198
199 (defun swiper-recenter-top-bottom (&optional arg)
200 "Call (`recenter-top-bottom' ARG)."
201 (interactive "P")
202 (with-ivy-window
203 (recenter-top-bottom arg)))
204
205 (defun swiper-font-lock-ensure ()
206 "Ensure the entired buffer is highlighted."
207 (unless (or (derived-mode-p 'magit-mode)
208 (bound-and-true-p magit-blame-mode)
209 (memq major-mode '(package-menu-mode
210 gnus-summary-mode
211 gnus-article-mode
212 gnus-group-mode
213 emms-playlist-mode
214 emms-stream-mode
215 erc-mode
216 org-agenda-mode
217 dired-mode
218 jabber-chat-mode
219 elfeed-search-mode
220 elfeed-show-mode
221 fundamental-mode
222 Man-mode
223 woman-mode
224 mu4e-view-mode
225 mu4e-headers-mode
226 help-mode
227 debbugs-gnu-mode
228 occur-mode
229 occur-edit-mode
230 bongo-mode
231 w3m-mode)))
232 (unless (> (buffer-size) 100000)
233 (if (fboundp 'font-lock-ensure)
234 (font-lock-ensure)
235 (with-no-warnings (font-lock-fontify-buffer))))))
236
237 (defvar swiper--format-spec ""
238 "Store the current candidates format spec.")
239
240 (defvar swiper--width nil
241 "Store the amount of digits needed for the longest line nubmer.")
242
243 (defvar swiper-use-visual-line nil
244 "When non-nil, use `line-move' instead of `forward-line'.")
245
246 (defun swiper--candidates ()
247 "Return a list of this buffer lines."
248 (setq swiper-use-visual-line
249 (and (not (eq major-mode 'org-mode))
250 visual-line-mode
251 (< (buffer-size) 20000)))
252 (let ((n-lines (count-lines (point-min) (point-max))))
253 (unless (zerop n-lines)
254 (setq swiper--width (1+ (floor (log n-lines 10))))
255 (setq swiper--format-spec
256 (format "%%-%dd " swiper--width))
257 (let ((line-number 0)
258 (advancer (if swiper-use-visual-line
259 (lambda (arg) (line-move arg t))
260 #'forward-line))
261 candidates)
262 (save-excursion
263 (goto-char (point-min))
264 (swiper-font-lock-ensure)
265 (while (< (point) (point-max))
266 (let ((str (concat " " (buffer-substring
267 (point)
268 (if swiper-use-visual-line
269 (save-excursion
270 (end-of-visual-line)
271 (point))
272 (line-end-position))))))
273 (put-text-property 0 1 'display
274 (format swiper--format-spec
275 (cl-incf line-number))
276 str)
277 (push str candidates))
278 (funcall advancer 1))
279 (nreverse candidates))))))
280
281 (defvar swiper--opoint 1
282 "The point when `swiper' starts.")
283
284 ;;;###autoload
285 (defun swiper (&optional initial-input)
286 "`isearch' with an overview.
287 When non-nil, INITIAL-INPUT is the initial search pattern."
288 (interactive)
289 (swiper--ivy initial-input))
290
291 (defvar swiper--anchor nil
292 "A line number to which the search should be anchored.")
293
294 (defvar swiper--len 0
295 "The last length of input for which an anchoring was made.")
296
297 (defun swiper--init ()
298 "Perform initialization common to both completion methods."
299 (setq swiper--opoint (point))
300 (setq swiper--len 0)
301 (setq swiper--anchor (line-number-at-pos)))
302
303 (defun swiper--re-builder (str)
304 "Transform STR into a swiper regex.
305 This is the regex used in the minibuffer, since the candidates
306 there have line numbers. In the buffer, `ivy--regex' should be used."
307 (cond
308 ((equal str "")
309 "")
310 ((equal str "^")
311 (setq ivy--subexps 0)
312 ".")
313 ((string-match "^\\^" str)
314 (setq ivy--old-re "")
315 (let ((re (ivy--regex-plus (substring str 1))))
316 (if (zerop ivy--subexps)
317 (prog1 (format "^ ?\\(%s\\)" re)
318 (setq ivy--subexps 1))
319 (format "^ %s" re))))
320 (t
321 (ivy--regex-plus str))))
322
323 (defvar swiper-history nil
324 "History for `swiper'.")
325
326 (defvar swiper-invocation-face nil
327 "The face at the point of invocation of `swiper'.")
328
329 (defun swiper--ivy (&optional initial-input)
330 "`isearch' with an overview using `ivy'.
331 When non-nil, INITIAL-INPUT is the initial search pattern."
332 (interactive)
333 (swiper--init)
334 (setq swiper-invocation-face
335 (plist-get (text-properties-at (point)) 'face))
336 (let ((candidates (swiper--candidates))
337 (preselect (buffer-substring-no-properties
338 (line-beginning-position)
339 (line-end-position)))
340 (minibuffer-allow-text-properties t))
341 (unwind-protect
342 (ivy-read
343 "Swiper: "
344 candidates
345 :initial-input initial-input
346 :keymap swiper-map
347 :preselect preselect
348 :require-match t
349 :update-fn #'swiper--update-input-ivy
350 :unwind #'swiper--cleanup
351 :action #'swiper--action
352 :re-builder #'swiper--re-builder
353 :history 'swiper-history
354 :caller 'swiper)
355 (when (null ivy-exit)
356 (goto-char swiper--opoint)))))
357
358 (defun swiper-toggle-face-matching ()
359 "Toggle matching only the candidates with `swiper-invocation-face'."
360 (interactive)
361 (setf (ivy-state-matcher ivy-last)
362 (if (ivy-state-matcher ivy-last)
363 nil
364 #'swiper--face-matcher))
365 (setq ivy--old-re nil))
366
367 (defun swiper--face-matcher (regexp candidates)
368 "Return REGEXP-matching CANDIDATES.
369 Matched candidates should have `swiper-invocation-face'."
370 (cl-remove-if-not
371 (lambda (x)
372 (and
373 (string-match regexp x)
374 (let ((s (match-string 0 x))
375 (i 0))
376 (while (and (< i (length s))
377 (text-property-any
378 i (1+ i)
379 'face swiper-invocation-face
380 s))
381 (cl-incf i))
382 (eq i (length s)))))
383 candidates))
384
385 (defun swiper--ensure-visible ()
386 "Remove overlays hiding point."
387 (let ((overlays (overlays-at (point)))
388 ov expose)
389 (while (setq ov (pop overlays))
390 (if (and (invisible-p (overlay-get ov 'invisible))
391 (setq expose (overlay-get ov 'isearch-open-invisible)))
392 (funcall expose ov)))))
393
394 (defvar swiper--overlays nil
395 "Store overlays.")
396
397 (defun swiper--cleanup ()
398 "Clean up the overlays."
399 (while swiper--overlays
400 (delete-overlay (pop swiper--overlays)))
401 (save-excursion
402 (goto-char (point-min))
403 (isearch-clean-overlays)))
404
405 (defun swiper--update-input-ivy ()
406 "Called when `ivy' input is updated."
407 (with-ivy-window
408 (swiper--cleanup)
409 (when (> (length ivy--current) 0)
410 (let* ((re (funcall ivy--regex-function ivy-text))
411 (re (if (stringp re) re (caar re)))
412 (str (get-text-property 0 'display ivy--current))
413 (num (if (string-match "^[0-9]+" str)
414 (string-to-number (match-string 0 str))
415 0)))
416 (goto-char (point-min))
417 (when (cl-plusp num)
418 (goto-char (point-min))
419 (if swiper-use-visual-line
420 (line-move (1- num))
421 (forward-line (1- num)))
422 (if (and (equal ivy-text "")
423 (>= swiper--opoint (line-beginning-position))
424 (<= swiper--opoint (line-end-position)))
425 (goto-char swiper--opoint)
426 (re-search-forward re (line-end-position) t))
427 (isearch-range-invisible (line-beginning-position)
428 (line-end-position))
429 (unless (and (>= (point) (window-start))
430 (<= (point) (window-end (ivy-state-window ivy-last) t)))
431 (recenter)))
432 (swiper--add-overlays re)))))
433
434 (defun swiper--add-overlays (re &optional beg end wnd)
435 "Add overlays for RE regexp in visible part of the current buffer.
436 BEG and END, when specified, are the point bounds.
437 WND, when specified is the window."
438 (setq wnd (or wnd (ivy-state-window ivy-last)))
439 (let ((ov (if visual-line-mode
440 (make-overlay
441 (save-excursion
442 (beginning-of-visual-line)
443 (point))
444 (save-excursion
445 (end-of-visual-line)
446 (point)))
447 (make-overlay
448 (line-beginning-position)
449 (1+ (line-end-position))))))
450 (overlay-put ov 'face 'swiper-line-face)
451 (overlay-put ov 'window wnd)
452 (push ov swiper--overlays)
453 (let* ((wh (window-height))
454 (beg (or beg (save-excursion
455 (forward-line (- wh))
456 (point))))
457 (end (or end (save-excursion
458 (forward-line wh)
459 (point)))))
460 (when (>= (length re) swiper-min-highlight)
461 (save-excursion
462 (goto-char beg)
463 ;; RE can become an invalid regexp
464 (while (and (ignore-errors (re-search-forward re end t))
465 (> (- (match-end 0) (match-beginning 0)) 0))
466 (let ((i 0))
467 (while (<= i ivy--subexps)
468 (when (match-beginning i)
469 (let ((overlay (make-overlay (match-beginning i)
470 (match-end i)))
471 (face
472 (cond ((zerop ivy--subexps)
473 (cadr swiper-faces))
474 ((zerop i)
475 (car swiper-faces))
476 (t
477 (nth (1+ (mod (+ i 2) (1- (length swiper-faces))))
478 swiper-faces)))))
479 (push overlay swiper--overlays)
480 (overlay-put overlay 'face face)
481 (overlay-put overlay 'window wnd)
482 (overlay-put overlay 'priority i)))
483 (cl-incf i)))))))))
484
485 (defun swiper--action (x)
486 "Goto line X."
487 (if (null x)
488 (user-error "No candidates")
489 (with-ivy-window
490 (unless (equal (current-buffer)
491 (ivy-state-buffer ivy-last))
492 (switch-to-buffer (ivy-state-buffer ivy-last)))
493 (goto-char (point-min))
494 (funcall (if swiper-use-visual-line
495 #'line-move
496 #'forward-line)
497 (1- (read (get-text-property 0 'display x))))
498 (re-search-forward
499 (ivy--regex ivy-text) (line-end-position) t)
500 (swiper--ensure-visible)
501 (when (/= (point) swiper--opoint)
502 (unless (and transient-mark-mode mark-active)
503 (push-mark swiper--opoint t)
504 (message "Mark saved where search started"))))))
505
506 ;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch)
507 (defun swiper-from-isearch ()
508 "Invoke `swiper' from isearch."
509 (interactive)
510 (let ((query (if isearch-regexp
511 isearch-string
512 (regexp-quote isearch-string))))
513 (isearch-exit)
514 (swiper query)))
515
516 (defvar swiper-multi-buffers nil
517 "Store the current list of buffers.")
518
519 (defvar swiper-multi-candidates nil
520 "Store the list of candidates for `swiper-multi'.")
521
522 (defun swiper-multi-prompt ()
523 (format "Buffers (%s): "
524 (mapconcat #'identity swiper-multi-buffers ", ")))
525
526 (defun swiper-multi ()
527 "Select one or more buffers.
528 Run `swiper' for those buffers."
529 (interactive)
530 (setq swiper-multi-buffers nil)
531 (setq swiper-multi-candidates nil)
532 (ivy-read (swiper-multi-prompt)
533 'internal-complete-buffer
534 :action 'swiper-multi-action-1)
535 (ivy-read "Swiper: " swiper-multi-candidates
536 :action 'swiper-multi-action-2
537 :unwind #'swiper--cleanup
538 :caller 'swiper-multi))
539
540 (defun swiper-multi-action-1 (x)
541 (if (member x swiper-multi-buffers)
542 (progn
543 (setq swiper-multi-buffers (delete x swiper-multi-buffers)))
544 (unless (equal x "")
545 (setq swiper-multi-buffers (append swiper-multi-buffers (list x)))))
546 (let ((prompt (swiper-multi-prompt)))
547 (setf (ivy-state-prompt ivy-last) prompt)
548 (setq ivy--prompt (concat "%-4d " prompt)))
549 (cond ((memq this-command '(ivy-done
550 ivy-alt-done
551 ivy-immediate-done))
552 (let ((ww (window-width)))
553 (dolist (buf swiper-multi-buffers)
554 (with-current-buffer buf
555 (setq swiper-multi-candidates
556 (append
557 (mapcar
558 (lambda (s)
559 (setq s (concat s " "))
560 (let ((len (length s)))
561 (put-text-property
562 (1- len) len 'display
563 (concat
564 (make-string
565 (max
566 (- ww
567 (string-width s)
568 (length (buffer-name))
569 1)
570 0)
571 ?\ )
572 (buffer-name))
573 s)
574 s))
575 (swiper--candidates))
576 swiper-multi-candidates))))))
577 ((eq this-command 'ivy-call)
578 (delete-minibuffer-contents))))
579
580 (defun swiper-multi-action-2 (x)
581 (let ((buf-space (get-text-property (1- (length x)) 'display x)))
582 (with-ivy-window
583 (when (string-match "\\` *\\([^ ]+\\)\\'" buf-space)
584 (switch-to-buffer (match-string 1 buf-space))
585 (goto-char (point-min))
586 (forward-line (1- (read x)))
587 (re-search-forward
588 (ivy--regex ivy-text)
589 (line-end-position) t)
590 (unless (eq ivy-exit 'done)
591 (swiper--cleanup)
592 (swiper--add-overlays (ivy--regex ivy-text)))))))
593
594 (provide 'swiper)
595
596 ;;; swiper.el ends here