]> code.delx.au - gnu-emacs-elpa/blob - swiper.el
swiper.el (swiper--update-input-ivy): Add a work-around for "M-j"
[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 (swiper--cleanup)
109 (ivy-exit-with-action
110 (lambda (_)
111 (with-ivy-window
112 (move-beginning-of-line 1)
113 (perform-replace from to
114 t t nil)))))))
115
116 (defvar avy-background)
117 (defvar avy-all-windows)
118 (defvar avy-style)
119 (defvar avy-keys)
120 (declare-function avy--regex-candidates "ext:avy")
121 (declare-function avy--process "ext:avy")
122 (declare-function avy--overlay-post "ext:avy")
123 (declare-function avy-action-goto "ext:avy")
124 (declare-function avy--done "ext:avy")
125 (declare-function avy--make-backgrounds "ext:avy")
126 (declare-function avy-window-list "ext:avy")
127 (declare-function avy-read "ext:avy")
128 (declare-function avy-read-de-bruijn "ext:avy")
129 (declare-function avy-tree "ext:avy")
130 (declare-function avy-push-mark "ext:avy")
131 (declare-function avy--remove-leading-chars "ext:avy")
132
133 ;;;###autoload
134 (defun swiper-avy ()
135 "Jump to one of the current swiper candidates."
136 (interactive)
137 (unless (string= ivy-text "")
138 (let* ((avy-all-windows nil)
139 (candidates (append
140 (with-ivy-window
141 (avy--regex-candidates
142 (ivy--regex ivy-text)))
143 (save-excursion
144 (save-restriction
145 (narrow-to-region (window-start) (window-end))
146 (goto-char (point-min))
147 (forward-line)
148 (let ((cands))
149 (while (< (point) (point-max))
150 (push (cons (1+ (point))
151 (selected-window))
152 cands)
153 (forward-line))
154 cands)))))
155 (candidate (unwind-protect
156 (prog2
157 (avy--make-backgrounds
158 (append (avy-window-list)
159 (list (ivy-state-window ivy-last))))
160 (if (eq avy-style 'de-bruijn)
161 (avy-read-de-bruijn
162 candidates avy-keys)
163 (avy-read (avy-tree candidates avy-keys)
164 #'avy--overlay-post
165 #'avy--remove-leading-chars))
166 (avy-push-mark))
167 (avy--done))))
168 (if (window-minibuffer-p (cdr candidate))
169 (progn
170 (ivy-set-index (- (line-number-at-pos (car candidate)) 2))
171 (ivy--exhibit)
172 (ivy-done)
173 (ivy-call))
174 (ivy-quit-and-run
175 (avy-action-goto (caar candidate)))))))
176
177 (declare-function mc/create-fake-cursor-at-point "ext:multiple-cursors-core")
178 (declare-function multiple-cursors-mode "ext:multiple-cursors-core")
179
180 ;;;###autoload
181 (defun swiper-mc ()
182 (interactive)
183 (unless (require 'multiple-cursors nil t)
184 (error "multiple-cursors isn't installed"))
185 (let ((cands (nreverse ivy--old-cands)))
186 (unless (string= ivy-text "")
187 (ivy-exit-with-action
188 (lambda (_)
189 (let (cand)
190 (while (setq cand (pop cands))
191 (swiper--action cand)
192 (when cands
193 (mc/create-fake-cursor-at-point))))
194 (multiple-cursors-mode 1))))))
195
196 (defun swiper-recenter-top-bottom (&optional arg)
197 "Call (`recenter-top-bottom' ARG)."
198 (interactive "P")
199 (with-ivy-window
200 (recenter-top-bottom arg)))
201
202 (defun swiper-font-lock-ensure ()
203 "Ensure the entired buffer is highlighted."
204 (unless (or (derived-mode-p 'magit-mode)
205 (bound-and-true-p magit-blame-mode)
206 (memq major-mode '(package-menu-mode
207 gnus-summary-mode
208 gnus-article-mode
209 gnus-group-mode
210 emms-playlist-mode
211 emms-stream-mode
212 erc-mode
213 org-agenda-mode
214 dired-mode
215 jabber-chat-mode
216 elfeed-search-mode
217 elfeed-show-mode
218 fundamental-mode
219 Man-mode
220 woman-mode
221 mu4e-view-mode
222 mu4e-headers-mode
223 help-mode
224 debbugs-gnu-mode
225 occur-mode
226 occur-edit-mode
227 bongo-mode
228 eww-mode
229 twittering-mode
230 w3m-mode)))
231 (unless (> (buffer-size) 100000)
232 (if (fboundp 'font-lock-ensure)
233 (font-lock-ensure)
234 (with-no-warnings (font-lock-fontify-buffer))))))
235
236 (defvar swiper--format-spec ""
237 "Store the current candidates format spec.")
238
239 (defvar swiper--width nil
240 "Store the amount of digits needed for the longest line nubmer.")
241
242 (defvar swiper-use-visual-line nil
243 "When non-nil, use `line-move' instead of `forward-line'.")
244
245 (declare-function outline-show-all "outline")
246
247 (defun swiper--candidates (&optional numbers-width)
248 "Return a list of this buffer lines.
249
250 NUMBERS-WIDTH, when specified, is used for line numbers width
251 spec, instead of calculating it as the log of the buffer line
252 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 (declare-function evil-jumper--set-jump "ext:evil-jumper")
316
317 (defun swiper--init ()
318 "Perform initialization common to both completion methods."
319 (setq swiper--opoint (point))
320 (when (bound-and-true-p evil-jumper-mode)
321 (evil-jumper--set-jump)))
322
323 (defun swiper--re-builder (str)
324 "Transform STR into a swiper regex.
325 This is the regex used in the minibuffer, since the candidates
326 there have line numbers. In the buffer, `ivy--regex' should be used."
327 (cond
328 ((equal str "")
329 "")
330 ((equal str "^")
331 (setq ivy--subexps 0)
332 ".")
333 ((string-match "^\\^" str)
334 (setq ivy--old-re "")
335 (let ((re (ivy--regex-plus (substring str 1))))
336 (if (zerop ivy--subexps)
337 (prog1 (format "^ ?\\(%s\\)" re)
338 (setq ivy--subexps 1))
339 (format "^ %s" re))))
340 (t
341 (ivy--regex-plus str))))
342
343 (defvar swiper-history nil
344 "History for `swiper'.")
345
346 (defvar swiper-invocation-face nil
347 "The face at the point of invocation of `swiper'.")
348
349 (defun swiper--ivy (&optional initial-input)
350 "`isearch' with an overview using `ivy'.
351 When non-nil, INITIAL-INPUT is the initial search pattern."
352 (interactive)
353 (swiper--init)
354 (setq swiper-invocation-face
355 (plist-get (text-properties-at (point)) 'face))
356 (let ((candidates (swiper--candidates))
357 (preselect
358 (if swiper-use-visual-line
359 (count-screen-lines
360 (point-min)
361 (save-excursion (beginning-of-visual-line) (point)))
362 (1- (line-number-at-pos))))
363 (minibuffer-allow-text-properties t)
364 res)
365 (unwind-protect
366 (setq res
367 (ivy-read
368 "Swiper: "
369 candidates
370 :initial-input initial-input
371 :keymap swiper-map
372 :preselect preselect
373 :require-match t
374 :update-fn #'swiper--update-input-ivy
375 :unwind #'swiper--cleanup
376 :action #'swiper--action
377 :re-builder #'swiper--re-builder
378 :history 'swiper-history
379 :caller 'swiper))
380 (unless res
381 (goto-char swiper--opoint)))))
382
383 (defun swiper-toggle-face-matching ()
384 "Toggle matching only the candidates with `swiper-invocation-face'."
385 (interactive)
386 (setf (ivy-state-matcher ivy-last)
387 (if (ivy-state-matcher ivy-last)
388 nil
389 #'swiper--face-matcher))
390 (setq ivy--old-re nil))
391
392 (defun swiper--face-matcher (regexp candidates)
393 "Return REGEXP-matching CANDIDATES.
394 Matched candidates should have `swiper-invocation-face'."
395 (cl-remove-if-not
396 (lambda (x)
397 (and
398 (string-match regexp x)
399 (let ((s (match-string 0 x))
400 (i 0))
401 (while (and (< i (length s))
402 (text-property-any
403 i (1+ i)
404 'face swiper-invocation-face
405 s))
406 (cl-incf i))
407 (eq i (length s)))))
408 candidates))
409
410 (defun swiper--ensure-visible ()
411 "Remove overlays hiding point."
412 (let ((overlays (overlays-at (point)))
413 ov expose)
414 (while (setq ov (pop overlays))
415 (if (and (invisible-p (overlay-get ov 'invisible))
416 (setq expose (overlay-get ov 'isearch-open-invisible)))
417 (funcall expose ov)))))
418
419 (defvar swiper--overlays nil
420 "Store overlays.")
421
422 (defun swiper--cleanup ()
423 "Clean up the overlays."
424 (while swiper--overlays
425 (delete-overlay (pop swiper--overlays)))
426 (save-excursion
427 (goto-char (point-min))
428 (isearch-clean-overlays)))
429
430 (defun swiper--update-input-ivy ()
431 "Called when `ivy' input is updated."
432 (with-ivy-window
433 (swiper--cleanup)
434 (when (> (length ivy--current) 0)
435 (let* ((re (funcall ivy--regex-function ivy-text))
436 (re (if (stringp re) re (caar re)))
437 (str (get-text-property 0 'display ivy--current))
438 (num (if (string-match "^[0-9]+" str)
439 (string-to-number (match-string 0 str))
440 0)))
441 (unless (eq this-command 'ivy-yank-word)
442 (goto-char (point-min))
443 (when (cl-plusp num)
444 (goto-char (point-min))
445 (if swiper-use-visual-line
446 (line-move (1- num))
447 (forward-line (1- num)))
448 (if (and (equal ivy-text "")
449 (>= swiper--opoint (line-beginning-position))
450 (<= swiper--opoint (line-end-position)))
451 (goto-char swiper--opoint)
452 (re-search-forward re (line-end-position) t))
453 (isearch-range-invisible (line-beginning-position)
454 (line-end-position))
455 (unless (and (>= (point) (window-start))
456 (<= (point) (window-end (ivy-state-window ivy-last) t)))
457 (recenter))))
458 (swiper--add-overlays re)))))
459
460 (defun swiper--add-overlays (re &optional beg end wnd)
461 "Add overlays for RE regexp in visible part of the current buffer.
462 BEG and END, when specified, are the point bounds.
463 WND, when specified is the window."
464 (setq wnd (or wnd (ivy-state-window ivy-last)))
465 (let ((ov (if visual-line-mode
466 (make-overlay
467 (save-excursion
468 (beginning-of-visual-line)
469 (point))
470 (save-excursion
471 (end-of-visual-line)
472 (point)))
473 (make-overlay
474 (line-beginning-position)
475 (1+ (line-end-position))))))
476 (overlay-put ov 'face 'swiper-line-face)
477 (overlay-put ov 'window wnd)
478 (push ov swiper--overlays)
479 (let* ((wh (window-height))
480 (beg (or beg (save-excursion
481 (forward-line (- wh))
482 (point))))
483 (end (or end (save-excursion
484 (forward-line wh)
485 (point)))))
486 (when (>= (length re) swiper-min-highlight)
487 (save-excursion
488 (goto-char beg)
489 ;; RE can become an invalid regexp
490 (while (and (ignore-errors (re-search-forward re end t))
491 (> (- (match-end 0) (match-beginning 0)) 0))
492 (let ((i 0))
493 (while (<= i ivy--subexps)
494 (when (match-beginning i)
495 (let ((overlay (make-overlay (match-beginning i)
496 (match-end i)))
497 (face
498 (cond ((zerop ivy--subexps)
499 (cadr swiper-faces))
500 ((zerop i)
501 (car swiper-faces))
502 (t
503 (nth (1+ (mod (+ i 2) (1- (length swiper-faces))))
504 swiper-faces)))))
505 (push overlay swiper--overlays)
506 (overlay-put overlay 'face face)
507 (overlay-put overlay 'window wnd)
508 (overlay-put overlay 'priority i)))
509 (cl-incf i)))))))))
510
511 (defun swiper--action (x)
512 "Goto line X."
513 (if (null x)
514 (user-error "No candidates")
515 (with-ivy-window
516 (unless (equal (current-buffer)
517 (ivy-state-buffer ivy-last))
518 (switch-to-buffer (ivy-state-buffer ivy-last)))
519 (goto-char (point-min))
520 (funcall (if swiper-use-visual-line
521 #'line-move
522 #'forward-line)
523 (1- (read (get-text-property 0 'display x))))
524 (re-search-forward
525 (ivy--regex ivy-text) (line-end-position) t)
526 (swiper--ensure-visible)
527 (when (/= (point) swiper--opoint)
528 (unless (and transient-mark-mode mark-active)
529 (when (eq ivy-exit 'done)
530 (push-mark swiper--opoint t)
531 (message "Mark saved where search started")))))))
532
533 ;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch)
534 (defun swiper-from-isearch ()
535 "Invoke `swiper' from isearch."
536 (interactive)
537 (let ((query (if isearch-regexp
538 isearch-string
539 (regexp-quote isearch-string))))
540 (isearch-exit)
541 (swiper query)))
542
543 (defvar swiper-multi-buffers nil
544 "Store the current list of buffers.")
545
546 (defvar swiper-multi-candidates nil
547 "Store the list of candidates for `swiper-multi'.")
548
549 (defun swiper-multi-prompt ()
550 (format "Buffers (%s): "
551 (mapconcat #'identity swiper-multi-buffers ", ")))
552
553 (defun swiper-multi ()
554 "Select one or more buffers.
555 Run `swiper' for those buffers."
556 (interactive)
557 (setq swiper-multi-buffers nil)
558 (ivy-read (swiper-multi-prompt)
559 'internal-complete-buffer
560 :action 'swiper-multi-action-1)
561 (ivy-read "Swiper: " swiper-multi-candidates
562 :action 'swiper-multi-action-2
563 :unwind #'swiper--cleanup
564 :caller 'swiper-multi))
565
566 (defun swiper-all ()
567 "Run `swiper' for all opened buffers."
568 (interactive)
569 (ivy-read "Swiper: " (swiper--multi-candidates
570 (cl-remove-if-not
571 #'buffer-file-name
572 (buffer-list)))
573 :action 'swiper-multi-action-2
574 :unwind #'swiper--cleanup
575 :caller 'swiper-multi))
576
577 (defun swiper--multi-candidates (buffers)
578 (let* ((ww (window-width))
579 (res nil)
580 (column-2 (apply #'max
581 (mapcar
582 (lambda (b)
583 (length (buffer-name b)))
584 buffers)))
585 (column-1 (- ww 4 column-2 1)))
586 (dolist (buf buffers)
587 (with-current-buffer buf
588 (setq res
589 (append
590 (mapcar
591 (lambda (s)
592 (setq s (concat (ivy--truncate-string s column-1) " "))
593 (let ((len (length s)))
594 (put-text-property
595 (1- len) len 'display
596 (concat
597 (make-string
598 (- ww (string-width s) (length (buffer-name)) 3)
599 ?\ )
600 (buffer-name))
601 s)
602 s))
603 (swiper--candidates 4))
604 res))
605 nil))
606 res))
607
608 (defun swiper-multi-action-1 (x)
609 (if (member x swiper-multi-buffers)
610 (progn
611 (setq swiper-multi-buffers (delete x swiper-multi-buffers)))
612 (unless (equal x "")
613 (setq swiper-multi-buffers (append swiper-multi-buffers (list x)))))
614 (let ((prompt (swiper-multi-prompt)))
615 (setf (ivy-state-prompt ivy-last) prompt)
616 (setq ivy--prompt (concat "%-4d " prompt)))
617 (cond ((memq this-command '(ivy-done
618 ivy-alt-done
619 ivy-immediate-done))
620 (setq swiper-multi-candidates
621 (swiper--multi-candidates
622 (mapcar #'get-buffer swiper-multi-buffers))))
623 ((eq this-command 'ivy-call)
624 (delete-minibuffer-contents))))
625
626 (defun swiper-multi-action-2 (x)
627 (let ((buf-space (get-text-property (1- (length x)) 'display x)))
628 (with-ivy-window
629 (when (string-match "\\` *\\([^ ]+\\)\\'" buf-space)
630 (switch-to-buffer (match-string 1 buf-space))
631 (goto-char (point-min))
632 (forward-line (1- (read (get-text-property 0 'display x))))
633 (re-search-forward
634 (ivy--regex ivy-text)
635 (line-end-position) t)
636 (unless (eq ivy-exit 'done)
637 (swiper--cleanup)
638 (swiper--add-overlays (ivy--regex ivy-text)))))))
639
640 (provide 'swiper)
641
642 ;;; swiper.el ends here