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