]> code.delx.au - gnu-emacs-elpa/blob - swiper.el
swiper.el (swiper-font-lock-ensure): Don't fail when font-lock is off
[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 (or (> (buffer-size) 100000) (null font-lock-mode))
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 (swiper--candidates) initial-input))
314
315 (declare-function string-trim-right "subr-x")
316
317 (defun swiper-occur ()
318 "Generate a custom occur buffer for `swiper'."
319 (ivy-occur-grep-mode)
320 (font-lock-mode -1)
321 (let* ((fname (propertize
322 (with-ivy-window
323 (file-name-nondirectory
324 (buffer-file-name)))
325 'face
326 'compilation-info))
327 (cands (mapcar
328 (lambda (s)
329 (format "%s:%s:%s"
330 fname
331 (propertize
332 (string-trim-right
333 (get-text-property 0 'display s))
334 'face 'compilation-line-number)
335 (substring s 1)))
336 ivy--old-cands)))
337 (insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n"
338 default-directory))
339 (insert (format "%d candidates:\n" (length cands)))
340 (ivy--occur-insert-lines
341 (mapcar
342 (lambda (cand) (concat "./" cand))
343 cands))))
344
345 (ivy-set-occur 'swiper 'swiper-occur)
346
347 (declare-function evil-jumper--set-jump "ext:evil-jumper")
348
349 (defvar swiper--current-line nil)
350 (defvar swiper--current-match-start nil)
351
352 (defun swiper--init ()
353 "Perform initialization common to both completion methods."
354 (setq swiper--current-line nil)
355 (setq swiper--current-match-start nil)
356 (setq swiper--opoint (point))
357 (when (bound-and-true-p evil-jumper-mode)
358 (evil-jumper--set-jump)))
359
360 (defun swiper--re-builder (str)
361 "Transform STR into a swiper regex.
362 This is the regex used in the minibuffer where candidates have
363 line numbers. For the buffer, use `ivy--regex' instead."
364 (replace-regexp-in-string
365 "\t" " "
366 (cond
367 ((equal str "")
368 "")
369 ((equal str "^")
370 (setq ivy--subexps 0)
371 ".")
372 ((string-match "^\\^" str)
373 (setq ivy--old-re "")
374 (let ((re (ivy--regex-plus (substring str 1))))
375 (if (zerop ivy--subexps)
376 (prog1 (format "^ ?\\(%s\\)" re)
377 (setq ivy--subexps 1))
378 (format "^ %s" re))))
379 (t
380 (ivy--regex-plus str)))))
381
382 (defvar swiper-history nil
383 "History for `swiper'.")
384
385 (defvar swiper-invocation-face nil
386 "The face at the point of invocation of `swiper'.")
387
388 (defun swiper--ivy (candidates &optional initial-input)
389 "Select one of CANDIDATES and move there.
390 When non-nil, INITIAL-INPUT is the initial search pattern."
391 (interactive)
392 (swiper--init)
393 (setq swiper-invocation-face
394 (plist-get (text-properties-at (point)) 'face))
395 (let ((preselect
396 (if swiper-use-visual-line
397 (count-screen-lines
398 (point-min)
399 (save-excursion (beginning-of-visual-line) (point)))
400 (1- (line-number-at-pos))))
401 (minibuffer-allow-text-properties t)
402 res)
403 (unwind-protect
404 (and
405 (setq res
406 (ivy-read
407 "Swiper: "
408 candidates
409 :initial-input initial-input
410 :keymap swiper-map
411 :preselect preselect
412 :require-match t
413 :update-fn #'swiper--update-input-ivy
414 :unwind #'swiper--cleanup
415 :action #'swiper--action
416 :re-builder #'swiper--re-builder
417 :history 'swiper-history
418 :caller 'swiper))
419 (point))
420 (unless res
421 (goto-char swiper--opoint)))))
422
423 (defun swiper-toggle-face-matching ()
424 "Toggle matching only the candidates with `swiper-invocation-face'."
425 (interactive)
426 (setf (ivy-state-matcher ivy-last)
427 (if (ivy-state-matcher ivy-last)
428 nil
429 #'swiper--face-matcher))
430 (setq ivy--old-re nil))
431
432 (defun swiper--face-matcher (regexp candidates)
433 "Return REGEXP-matching CANDIDATES.
434 Matched candidates should have `swiper-invocation-face'."
435 (cl-remove-if-not
436 (lambda (x)
437 (and
438 (string-match regexp x)
439 (let ((s (match-string 0 x))
440 (i 0))
441 (while (and (< i (length s))
442 (text-property-any
443 i (1+ i)
444 'face swiper-invocation-face
445 s))
446 (cl-incf i))
447 (eq i (length s)))))
448 candidates))
449
450 (defun swiper--ensure-visible ()
451 "Remove overlays hiding point."
452 (let ((overlays (overlays-at (point)))
453 ov expose)
454 (while (setq ov (pop overlays))
455 (if (and (invisible-p (overlay-get ov 'invisible))
456 (setq expose (overlay-get ov 'isearch-open-invisible)))
457 (funcall expose ov)))))
458
459 (defvar swiper--overlays nil
460 "Store overlays.")
461
462 (defun swiper--cleanup ()
463 "Clean up the overlays."
464 (while swiper--overlays
465 (delete-overlay (pop swiper--overlays)))
466 (save-excursion
467 (goto-char (point-min))
468 (isearch-clean-overlays)))
469
470 (defun swiper--update-input-ivy ()
471 "Called when `ivy' input is updated."
472 (with-ivy-window
473 (swiper--cleanup)
474 (when (> (length ivy--current) 0)
475 (let* ((re (replace-regexp-in-string
476 " " "\t"
477 (funcall ivy--regex-function ivy-text)))
478 (re (if (stringp re) re (caar re)))
479 (str (get-text-property 0 'display ivy--current))
480 (num (if (string-match "^[0-9]+" str)
481 (string-to-number (match-string 0 str))
482 0)))
483 (unless (eq this-command 'ivy-yank-word)
484 (when (cl-plusp num)
485 (unless (if swiper--current-line
486 (eq swiper--current-line num)
487 (eq (line-number-at-pos) num))
488 (goto-char (point-min))
489 (if swiper-use-visual-line
490 (line-move (1- num))
491 (forward-line (1- num))))
492 (if (and (equal ivy-text "")
493 (>= swiper--opoint (line-beginning-position))
494 (<= swiper--opoint (line-end-position)))
495 (goto-char swiper--opoint)
496 (if (eq swiper--current-line num)
497 (when swiper--current-match-start
498 (goto-char swiper--current-match-start))
499 (setq swiper--current-line num))
500 (when (re-search-forward re (line-end-position) t)
501 (setq swiper--current-match-start (match-beginning 0))))
502 (isearch-range-invisible (line-beginning-position)
503 (line-end-position))
504 (unless (and (>= (point) (window-start))
505 (<= (point) (window-end (ivy-state-window ivy-last) t)))
506 (recenter))))
507 (swiper--add-overlays re)))))
508
509 (defun swiper--add-overlays (re &optional beg end wnd)
510 "Add overlays for RE regexp in visible part of the current buffer.
511 BEG and END, when specified, are the point bounds.
512 WND, when specified is the window."
513 (setq wnd (or wnd (ivy-state-window ivy-last)))
514 (let ((ov (if visual-line-mode
515 (make-overlay
516 (save-excursion
517 (beginning-of-visual-line)
518 (point))
519 (save-excursion
520 (end-of-visual-line)
521 (point)))
522 (make-overlay
523 (line-beginning-position)
524 (1+ (line-end-position))))))
525 (overlay-put ov 'face 'swiper-line-face)
526 (overlay-put ov 'window wnd)
527 (push ov swiper--overlays)
528 (let* ((wh (window-height))
529 (beg (or beg (save-excursion
530 (forward-line (- wh))
531 (point))))
532 (end (or end (save-excursion
533 (forward-line wh)
534 (point)))))
535 (when (>= (length re) swiper-min-highlight)
536 (save-excursion
537 (goto-char beg)
538 ;; RE can become an invalid regexp
539 (while (and (ignore-errors (re-search-forward re end t))
540 (> (- (match-end 0) (match-beginning 0)) 0))
541 (let ((i 0))
542 (while (<= i ivy--subexps)
543 (when (match-beginning i)
544 (let ((overlay (make-overlay (match-beginning i)
545 (match-end i)))
546 (face
547 (cond ((zerop ivy--subexps)
548 (cadr swiper-faces))
549 ((zerop i)
550 (car swiper-faces))
551 (t
552 (nth (1+ (mod (+ i 2) (1- (length swiper-faces))))
553 swiper-faces)))))
554 (push overlay swiper--overlays)
555 (overlay-put overlay 'face face)
556 (overlay-put overlay 'window wnd)
557 (overlay-put overlay 'priority i)))
558 (cl-incf i)))))))))
559
560 (defun swiper--action (x)
561 "Goto line X."
562 (let ((ln (1- (read (if (memq this-command '(ivy-occur-press))
563 (when (string-match ":\\([0-9]+\\):.*\\'" x)
564 (match-string-no-properties 1 x))
565 (get-text-property 0 'display x)))))
566 (re (ivy--regex ivy-text)))
567 (if (null x)
568 (user-error "No candidates")
569 (with-ivy-window
570 (unless (equal (current-buffer)
571 (ivy-state-buffer ivy-last))
572 (switch-to-buffer (ivy-state-buffer ivy-last)))
573 (goto-char (point-min))
574 (funcall (if swiper-use-visual-line
575 #'line-move
576 #'forward-line)
577 ln)
578 (re-search-forward re (line-end-position) t)
579 (swiper--ensure-visible)
580 (when (/= (point) swiper--opoint)
581 (unless (and transient-mark-mode mark-active)
582 (when (eq ivy-exit 'done)
583 (push-mark swiper--opoint t)
584 (message "Mark saved where search started"))))
585 (add-to-history
586 'regexp-search-ring
587 re
588 regexp-search-ring-max)))))
589
590 ;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch)
591 (defun swiper-from-isearch ()
592 "Invoke `swiper' from isearch."
593 (interactive)
594 (let ((query (if isearch-regexp
595 isearch-string
596 (regexp-quote isearch-string))))
597 (isearch-exit)
598 (swiper query)))
599
600 (defvar swiper-multi-buffers nil
601 "Store the current list of buffers.")
602
603 (defvar swiper-multi-candidates nil
604 "Store the list of candidates for `swiper-multi'.")
605
606 (defun swiper-multi-prompt ()
607 (format "Buffers (%s): "
608 (mapconcat #'identity swiper-multi-buffers ", ")))
609
610 (defun swiper-multi ()
611 "Select one or more buffers.
612 Run `swiper' for those buffers."
613 (interactive)
614 (setq swiper-multi-buffers nil)
615 (ivy-read (swiper-multi-prompt)
616 'internal-complete-buffer
617 :action 'swiper-multi-action-1)
618 (ivy-read "Swiper: " swiper-multi-candidates
619 :action 'swiper-multi-action-2
620 :unwind #'swiper--cleanup
621 :caller 'swiper-multi))
622
623 (defun swiper-all ()
624 "Run `swiper' for all opened buffers."
625 (interactive)
626 (ivy-read "Swiper: " (swiper--multi-candidates
627 (cl-remove-if-not
628 #'buffer-file-name
629 (buffer-list)))
630 :action 'swiper-multi-action-2
631 :unwind #'swiper--cleanup
632 :caller 'swiper-multi))
633
634 (defun swiper--multi-candidates (buffers)
635 (let* ((ww (window-width))
636 (res nil)
637 (column-2 (apply #'max
638 (mapcar
639 (lambda (b)
640 (length (buffer-name b)))
641 buffers)))
642 (column-1 (- ww 4 column-2 1)))
643 (dolist (buf buffers)
644 (with-current-buffer buf
645 (setq res
646 (append
647 (mapcar
648 (lambda (s)
649 (setq s (concat (ivy--truncate-string s column-1) " "))
650 (let ((len (length s)))
651 (put-text-property
652 (1- len) len 'display
653 (concat
654 (make-string
655 (- ww (string-width s) (length (buffer-name)) 3)
656 ?\ )
657 (buffer-name))
658 s)
659 s))
660 (swiper--candidates 4))
661 res))
662 nil))
663 res))
664
665 (defun swiper-multi-action-1 (x)
666 (if (member x swiper-multi-buffers)
667 (progn
668 (setq swiper-multi-buffers (delete x swiper-multi-buffers)))
669 (unless (equal x "")
670 (setq swiper-multi-buffers (append swiper-multi-buffers (list x)))))
671 (let ((prompt (swiper-multi-prompt)))
672 (setf (ivy-state-prompt ivy-last) prompt)
673 (setq ivy--prompt (concat "%-4d " prompt)))
674 (cond ((memq this-command '(ivy-done
675 ivy-alt-done
676 ivy-immediate-done))
677 (setq swiper-multi-candidates
678 (swiper--multi-candidates
679 (mapcar #'get-buffer swiper-multi-buffers))))
680 ((eq this-command 'ivy-call)
681 (delete-minibuffer-contents))))
682
683 (defun swiper-multi-action-2 (x)
684 (let ((buf-space (get-text-property (1- (length x)) 'display x)))
685 (with-ivy-window
686 (when (string-match "\\` *\\([^ ]+\\)\\'" buf-space)
687 (switch-to-buffer (match-string 1 buf-space))
688 (goto-char (point-min))
689 (forward-line (1- (read (get-text-property 0 'display x))))
690 (re-search-forward
691 (ivy--regex ivy-text)
692 (line-end-position) t)
693 (unless (eq ivy-exit 'done)
694 (swiper--cleanup)
695 (swiper--add-overlays (ivy--regex ivy-text)))))))
696
697 (provide 'swiper)
698
699 ;;; swiper.el ends here