]> code.delx.au - gnu-emacs-elpa/blob - packages/ioccur/ioccur.el
Merge commit 'd4a9dad594473c511f975017d792efc8a8339671'
[gnu-emacs-elpa] / packages / ioccur / ioccur.el
1 ;;; ioccur.el --- Incremental occur
2
3 ;; Copyright (C) 2010-2013 Free Software Foundation, Inc.
4
5 ;; Author: Thierry Volpiatto <thierry dot volpiatto at gmail dot com>
6 ;; X-URL: https://github.com/thierryvolpiatto/ioccur
7 ;; Version: 2.4
8 ;; Compatibility: GNU Emacs >=22.3
9
10 ;; This program is free software; you can redistribute it and/or
11 ;; modify it under the terms of the GNU General Public License as
12 ;; published by the Free Software Foundation; either version 3, or
13 ;; (at your option) any later version.
14 ;;
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ;; General Public License for more details.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program; if not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Install:
24 ;;
25 ;; Add this file to your `load-path', BYTE-COMPILE it and
26 ;; add (require 'ioccur) in your .emacs.
27 ;;
28 ;; Start with (C-u) M-x ioccur
29 ;; or
30 ;; (C-u) M-x ioccur-find-buffer-matching
31 ;;
32 ;; Do C-h f ioccur or ioccur-find-buffer-matching for more info.
33
34 ;;; Commentary:
35 ;;
36 ;; This package provides the command M-x ioccur, which is similar to
37 ;; M-x occur, except that it is incremental.
38 ;;
39 ;; You can jump and quit to an occurrence, or jump and save the search
40 ;; buffer (ioccur-buffer) for further use. You can toggle literal and
41 ;; regexp searching while running. It is auto documented both in
42 ;; mode-line and tooltip. It has its own history, `ioccur-history',
43 ;; which is a real ring.
44 ;;
45 ;; To save `ioccur-history' via the Desktop package, add this to your
46 ;; init file (see (info "(emacs) Saving Emacs Sessions") for details):
47 ;;
48 ;; (add-to-list 'desktop-globals-to-save 'ioccur-history)
49
50 ;;; Code:
51 (require 'derived)
52 (eval-when-compile (require 'cl))
53 (require 'outline)
54 (eval-when-compile (require 'wdired))
55
56 (defvar ioccur-mode-map
57 (let ((map (make-sparse-keymap)))
58 (define-key map (kbd "q") 'ioccur-quit)
59 (define-key map (kbd "RET") 'ioccur-jump-and-quit)
60 (define-key map (kbd "<left>") 'ioccur-jump-and-quit)
61 (define-key map (kbd "<right>") 'ioccur-jump-without-quit)
62 (define-key map (kbd "C-z") 'ioccur-jump-without-quit)
63 (define-key map (kbd "<C-down>") 'ioccur-scroll-down)
64 (define-key map (kbd "<C-up>") 'ioccur-scroll-up)
65 (define-key map (kbd "C-v") 'ioccur-scroll-other-window-up)
66 (define-key map (kbd "M-v") 'ioccur-scroll-other-window-down)
67 (define-key map (kbd "<down>") 'ioccur-next-line)
68 (define-key map (kbd "<up>") 'ioccur-precedent-line)
69 (define-key map (kbd "C-n") 'ioccur-next-line)
70 (define-key map (kbd "C-p") 'ioccur-precedent-line)
71 (define-key map (kbd "R") 'ioccur-restart)
72 (define-key map (kbd "C-|") 'ioccur-split-window)
73 (define-key map (kbd "M-<") 'ioccur-beginning-of-buffer)
74 (define-key map (kbd "M->") 'ioccur-end-of-buffer)
75 map)
76 "Keymap used for ioccur commands.")
77
78
79 (defgroup ioccur nil
80 "Mode that provide incremental searching in buffer."
81 :prefix "ioccur-"
82 :group 'text)
83
84 ;;; User variables.
85 (defcustom ioccur-search-delay 0.5
86 "During incremental searching, display is updated all these seconds."
87 :group 'ioccur
88 :type 'integer)
89
90 (defcustom ioccur-search-prompt "Pattern: "
91 "Prompt used for `ioccur-occur'."
92 :group 'ioccur
93 :type 'string)
94
95 (defcustom ioccur-mode-line-string
96 (if (window-system)
97 " RET:Exit,C-g:Quit,C-j/left:Jump&quit,C-z/right:Jump,\
98 C-k/x:Kill(as sexp),M-p/n:Hist,C/M-v:Scroll,C-down/up:Follow,C-w:Yank tap"
99
100 " RET:Exit,C-g:Quit,C-j:Jump&quit,C-z:Jump,C-k/x:Kill(as sexp),\
101 S-/Tab:Hist,C-v/t:Scroll,C-d/u:Follow,C-w:Yank tap")
102
103 "Minimal documentation of `ioccur' commands displayed in mode-line.
104 Set it to nil to remove doc in mode-line."
105 :group 'ioccur
106 :type 'string)
107
108 (defcustom ioccur-length-line 80
109 "Length of the line displayed in ioccur buffer.
110 When set to nil lines displayed in `ioccur-buffer' will not be modified.
111 See `ioccur-truncate-line'."
112 :group 'ioccur
113 :type 'integer)
114
115 (defcustom ioccur-max-length-history 100
116 "Maximum number of element stored in `ioccur-history'."
117 :group 'ioccur
118 :type 'integer)
119
120 (defcustom ioccur-buffer-completion-use-ido nil
121 "Use ido to choose buffers in `ioccur-find-buffer-matching'."
122 :group 'ioccur
123 :type 'symbol)
124
125 (defcustom ioccur-default-search-function 're-search-forward
126 "Default search function.
127 Use here one of `re-search-forward' or `search-forward'."
128 :group 'ioccur
129 :type 'symbol)
130
131 (defcustom ioccur-highlight-match-p t
132 "Highlight matchs in `ioccur-buffer' when non--nil."
133 :group 'ioccur
134 :type 'boolean)
135
136 (defcustom ioccur-fontify-buffer-p nil
137 "Fontify `ioccur-current-buffer' when non--nil.
138 This allow to have syntactic coloration in `ioccur-buffer' but
139 it slow down the start of ioccur at first time on large buffers."
140 :group 'ioccur
141 :type 'boolean)
142
143 (defcustom ioccur-case-fold-search 'smart
144 "Add 'smart' option to `case-fold-search'.
145 When smart is enabled, Ignore case in the search strings
146 if pattern contains no uppercase characters.
147 Otherwise, with a nil or t value, the behavior is same as
148 `case-fold-search'.
149 Default value is smart, other possible values are nil and t."
150 :group 'ioccur
151 :type 'symbol)
152
153 (defvar ioccur-read-char-or-event-skip-read-key nil
154 "Force not using `read-key' to read input in minibuffer even if bounded.
155 Set it to non--nil if menu disapear or if keys are echoing in minibuffer.
156 Deprecated, should be used only in old Emacs versions.")
157
158 (defvar ioccur-save-pos-before-jump-hook nil
159 "A hook that run before jumping and quitting `ioccur'.")
160
161 ;;; Faces.
162 (defface ioccur-overlay-face
163 '((t (:background "Green4" :underline t)))
164 "Face for highlight line in ioccur buffer."
165 :group 'ioccur-faces)
166
167 (defface ioccur-match-overlay-face
168 '((t (:background "Indianred4" :underline t)))
169 "Face for highlight line in matched buffer."
170 :group 'ioccur-faces)
171
172 (defface ioccur-title-face
173 '((t (:background "Dodgerblue4")))
174 "Face for highlight incremental buffer title."
175 :group 'ioccur-faces)
176
177 (defface ioccur-regexp-face
178 '((t (:background "DeepSkyBlue" :underline t)))
179 "Face for highlight found regexp in `ioccur-buffer'."
180 :group 'ioccur-faces)
181
182 (defface ioccur-match-face
183 '((t (:background "DeepSkyBlue")))
184 "Face for highlight matches in `ioccur-buffer'."
185 :group 'ioccur-faces)
186
187 (defface ioccur-num-line-face
188 '((t (:foreground "OrangeRed")))
189 "Face for highlight number line in ioccur buffer."
190 :group 'ioccur-faces)
191
192 (defface ioccur-invalid-regexp
193 '((t (:foreground "Goldenrod")))
194 "Face for highlight wrong regexp message in ioccur buffer."
195 :group 'ioccur-faces)
196
197 (defface ioccur-cursor
198 '((t (:foreground "green")))
199 "Face for cursor color in minibuffer."
200 :group 'ioccur-faces)
201
202 ;;; Internal variables.
203 ;; String entered in prompt.
204 (defvar ioccur-pattern "")
205 ;; The ioccur timer.
206 (defvar ioccur-search-timer nil)
207 ;; Signal C-g hit.
208 (defvar ioccur-quit-flag nil)
209 ;; The buffer we search in.
210 (defvar ioccur-current-buffer nil)
211 ;; The overlay in `ioccur-buffer'.
212 (defvar ioccur-occur-overlay nil)
213 (make-variable-buffer-local 'ioccur-occur-overlay)
214 ;; Signal we quit and kill `ioccur-buffer'.
215 (defvar ioccur-exit-and-quit-p nil)
216 ;; A list to store history.
217 (defvar ioccur-history nil)
218 ;; The overlay in `ioccur-current-buffer'.
219 (defvar ioccur-match-overlay nil)
220 ;; Number of occurences found.
221 (defvar ioccur-count-occurences 0)
222 ;;The buffer where we send results.
223 (defvar ioccur-buffer nil)
224 (make-variable-buffer-local 'ioccur-buffer)
225 ;; True when jumping to a founded occurence.
226 (defvar ioccur-success nil)
227 ;; Search function actually in use.
228 (defvar ioccur-search-function ioccur-default-search-function)
229 ;; Message to send when ioccur exit
230 (defvar ioccur-message nil)
231 ;; Store last window-configuration
232 (defvar ioccur-last-window-configuration nil)
233 ;; Save point in current buffer here.
234 (defvar ioccur-current-pos nil)
235
236 (define-derived-mode ioccur-mode
237 text-mode "ioccur"
238 "Major mode to search occurences of regexp in current buffer.
239
240 Special commands:
241 \\{ioccur-mode-map}"
242 (if ioccur-mode-line-string
243 (setq mode-line-format
244 '(" " mode-line-buffer-identification " "
245 (line-number-mode "%l") " "
246 ioccur-mode-line-string "-%-"))
247 (kill-local-variable 'mode-line-format)))
248
249 (defsubst* ioccur-position (item seq &key (test 'eq))
250 "A simple replacement of CL `position'."
251 (loop for i in seq for index from 0
252 when (funcall test i item) return index))
253
254 ;; Compatibility
255 (unless (fboundp 'window-system)
256 (defun window-system (&optional _arg)
257 window-system))
258
259 ;;; Iterators.
260 (defmacro ioccur-iter-list (list-obj)
261 "Return an iterator from list LIST-OBJ."
262 `(lexical-let ((lis ,list-obj))
263 (lambda ()
264 (let ((elm (car lis)))
265 (setq lis (cdr lis))
266 elm))))
267
268 (defun ioccur-iter-next (iterator)
269 "Return next elm of ITERATOR."
270 (funcall iterator))
271
272 (defun ioccur-iter-circular (seq)
273 "Infinite iteration on SEQ."
274 (lexical-let ((it (ioccur-iter-list seq))
275 (lis seq))
276 (lambda ()
277 (let ((elm (ioccur-iter-next it)))
278 (or elm
279 (progn (setq it (ioccur-iter-list lis))
280 (ioccur-iter-next it)))))))
281
282 (defun ioccur-butlast (seq pos)
283 "Return SEQ from index 0 to POS."
284 (butlast seq (- (length seq) pos)))
285
286 (defun* ioccur-sub-prec-circular (seq elm &key (test 'eq))
287 "Infinite reverse iteration of SEQ starting at ELM."
288 (lexical-let* ((rev-seq (reverse seq))
289 (pos (ioccur-position elm rev-seq :test test))
290 (sub (append (nthcdr (1+ pos) rev-seq)
291 (ioccur-butlast rev-seq pos)))
292 (iterator (ioccur-iter-list sub)))
293 (lambda ()
294 (let ((elm (ioccur-iter-next iterator)))
295 (or elm
296 (progn (setq iterator (ioccur-iter-list sub))
297 (ioccur-iter-next iterator)))))))
298
299 (defun* ioccur-sub-next-circular (seq elm &key (test 'eq))
300 "Infinite iteration of SEQ starting at ELM."
301 (lexical-let* ((pos (ioccur-position elm seq :test test))
302 (sub (append (nthcdr (1+ pos) seq)
303 (ioccur-butlast seq pos)))
304 (iterator (ioccur-iter-list sub)))
305 (lambda ()
306 (let ((elm (ioccur-iter-next iterator)))
307 (or elm (progn
308 (setq iterator (ioccur-iter-list sub))
309 (ioccur-iter-next iterator)))))))
310
311 (defun ioccur-print-results (regexp)
312 "Print in `ioccur-buffer' lines matching REGEXP in `ioccur-current-buffer'."
313 (setq ioccur-count-occurences 0)
314 (with-current-buffer ioccur-current-buffer
315 (let ((case-fold-search (case ioccur-case-fold-search
316 (smart (let ((case-fold-search nil))
317 (if (string-match "[A-Z]" regexp) nil t)))
318 (t ioccur-case-fold-search))))
319 (save-excursion
320 (goto-char (point-min))
321 (loop
322 while (not (eobp))
323 ;; We need to read also C-g from here
324 ;; Because when loop is started `ioccur-read-search-input'
325 ;; will read key only when loop is finished
326 ;; and we have no chance to exit loop.
327 when quit-flag do (setq ioccur-quit-flag t) and return nil
328 for count from 0
329 when (funcall ioccur-search-function regexp (point-at-eol) t)
330 do (ioccur-print-line
331 (buffer-substring (point-at-bol) (point-at-eol))
332 count (match-string 0))
333 do (forward-line 1))))))
334
335
336 (defun ioccur-print-match (str &optional all)
337 "Highlight in string STR all occurences matching `ioccur-pattern'.
338 If ALL is non--nil highlight the whole string STR."
339 (condition-case nil
340 (with-temp-buffer
341 (insert str)
342 (goto-char (point-min))
343 (if all
344 (add-text-properties
345 (point) (point-at-eol)
346 '(face ioccur-match-face))
347 (while (and (funcall ioccur-search-function ioccur-pattern nil t)
348 ;; Don't try to highlight line with a length <= 0.
349 (> (- (match-end 0) (match-beginning 0)) 0))
350 (add-text-properties
351 (match-beginning 0) (match-end 0)
352 '(face ioccur-match-face))))
353 (buffer-string))
354 (error nil)))
355
356 (defun ioccur-print-line (line nline match)
357 "Prepare and insert a matched LINE at line number NLINE in `ioccur-buffer'."
358 (with-current-buffer ioccur-buffer
359 (let* ((lineno (int-to-string (1+ nline)))
360 (whole-line-matched (string= match line))
361 (hightline (if ioccur-highlight-match-p
362 (ioccur-print-match
363 line
364 whole-line-matched)
365 line))
366 (trunc-line (ioccur-truncate-line hightline)))
367 (incf ioccur-count-occurences)
368 (insert " " (propertize lineno 'face 'ioccur-num-line-face
369 'help-echo line)
370 ":" trunc-line "\n"))))
371
372 (defun* ioccur-truncate-line (line &optional (columns ioccur-length-line))
373 "Remove indentation in LINE and truncate modified LINE of num COLUMNS.
374 COLUMNS default value is `ioccur-length-line'.
375 If COLUMNS is nil return original indented LINE.
376 If COLUMNS is 0 only remove indentation in LINE.
377 So just set `ioccur-length-line' to nil if you don't want lines truncated."
378 (let ((old-line line))
379 (when (string-match "^[[:blank:]]*" line)
380 ;; Remove tab and spaces at beginning of LINE.
381 (setq line (replace-match "" nil nil line)))
382 (if (and columns (> columns 0) (> (length line) columns))
383 (substring line 0 columns)
384 (if columns line old-line))))
385
386 (defun ioccur-buffer-contain (buffer regexp)
387 "Return BUFFER if it contain an occurence of REGEXP."
388 (with-current-buffer buffer
389 (save-excursion
390 (goto-char (point-min))
391 (when (re-search-forward regexp nil t) buffer))))
392
393 (defun ioccur-list-buffers-matching (buffer-match regexp buffer-list)
394 "Collect all buffers in BUFFER-LIST whose names match BUFFER-MATCH and \
395 contain lines matching REGEXP."
396 (loop
397 with ini-buf-list = (loop for buf in buffer-list
398 unless (rassq buf dired-buffers)
399 collect buf)
400 for buf in ini-buf-list
401 for bname = (buffer-name buf)
402 when (and (string-match buffer-match bname)
403 (ioccur-buffer-contain buf regexp))
404 collect bname))
405
406 (defun ioccur-list-buffers-containing (regexp buffer-list)
407 "Collect all buffers in BUFFER-LIST containing lines matching REGEXP."
408 (loop with buf-list = (loop for i in buffer-list
409 when (buffer-file-name (get-buffer i))
410 collect i)
411 for buf in buf-list
412 when (ioccur-buffer-contain buf regexp)
413 collect (buffer-name buf)))
414
415 (defun* ioccur-find-buffer-matching1 (regexp
416 &optional
417 match-buf-name
418 (buffer-list (buffer-list)))
419 "Find all buffers containing a text matching REGEXP \
420 and connect `ioccur' to the selected one.
421
422 If MATCH-BUF-NAME is non--nil search is performed only in buffers
423 with name matching specified expression (prompt).
424
425 Hitting C-g in a `ioccur' session will return to completion list.
426 Hitting C-g in the completion list will jump back to initial buffer.
427
428 The buffer completion list is provided by one of:
429 `ido-completing-read', `completing-read'
430 depending on which `ioccur-buffer-completion-use-ido' you have choosen."
431 ;; Remove doublons maybe added by minibuffer in `ioccur-history'.
432 (setq ioccur-history
433 (loop for i in ioccur-history
434 when (not (member i hist)) collect i into hist
435 finally return hist))
436
437 (let ((prompt (format "Search (%s) in Buffer: " regexp))
438 (win-conf (current-window-configuration))
439 (buf-list (if match-buf-name
440 (ioccur-list-buffers-matching
441 (read-string "In Buffer names matching: ")
442 regexp buffer-list)
443 (ioccur-list-buffers-containing regexp buffer-list))))
444
445 (labels
446 ((find-buffer ()
447 (let ((buf (if (and ido-mode
448 (eq ioccur-buffer-completion-use-ido 'ido))
449 (ido-completing-read prompt buf-list nil t)
450 (completing-read prompt buf-list nil t))))
451 (unwind-protect
452 (progn
453 (switch-to-buffer buf)
454 (ioccur regexp)
455 ;; Exit if we jump to this `ioccur-current-buffer',
456 ;; otherwise, if C-g is hitten,
457 ;; go back to buffer completion list.
458 (unless ioccur-success
459 (find-buffer)))
460 ;; C-g hit in buffer completion restore window config.
461 (unless ioccur-success
462 (set-window-configuration win-conf))))))
463
464 (find-buffer))))
465
466 (defvar savehist-save-minibuffer-history)
467
468 ;;;###autoload
469 (defun ioccur-find-buffer-matching (regexp)
470 "Find all buffers containing a text matching REGEXP.
471 See `ioccur-find-buffer-matching1'."
472 (interactive (list (let ((savehist-save-minibuffer-history nil))
473 (read-from-minibuffer "Search for Pattern: "
474 nil nil nil '(ioccur-history . 0)
475 (thing-at-point 'symbol)))))
476 (ioccur-find-buffer-matching1 regexp current-prefix-arg))
477
478 ;;; Ioccur dired
479 ;;;###autoload
480 (defun ioccur-dired (regexp)
481 (interactive (list (let ((savehist-save-minibuffer-history nil))
482 (read-from-minibuffer "Search for Pattern: "
483 nil nil nil '(ioccur-history . 0)
484 (thing-at-point 'symbol)))))
485 (let ((buf-list (loop for f in (dired-get-marked-files)
486 do (find-file-noselect f)
487 unless (file-directory-p f)
488 collect (get-buffer (file-name-nondirectory f)))))
489 (ioccur-find-buffer-matching1 regexp nil buf-list)))
490
491 ;;;###autoload
492 (defun ioccur-restart ()
493 "Restart `ioccur' from `ioccur-buffer'.
494 `ioccur-buffer' is erased and a new search is started."
495 (interactive)
496 (when (eq major-mode 'ioccur-mode)
497 (pop-to-buffer ioccur-current-buffer)
498 (kill-buffer ioccur-buffer)
499 (set-window-configuration ioccur-last-window-configuration)
500 (ioccur)))
501
502 ;;;###autoload
503 (defun ioccur-quit ()
504 "Quit `ioccur-buffer'."
505 (interactive)
506 (let ((pos (with-current-buffer ioccur-current-buffer (point))))
507 (when ioccur-match-overlay
508 (delete-overlay ioccur-match-overlay))
509 (quit-window)
510 (set-window-configuration ioccur-last-window-configuration)
511 (pop-to-buffer ioccur-current-buffer)
512 (goto-char pos)))
513
514 (defun ioccur-goto-line (lineno)
515 "Goto LINENO without modifying outline visibility if needed."
516 (goto-char (point-min))
517 (forward-line (1- lineno))
518 (if (and (fboundp 'org-reveal)
519 (or (derived-mode-p 'org-mode)
520 outline-minor-mode))
521 (org-reveal)))
522
523 (defun ioccur-forward-line (n)
524 "Forward N lines but empty one's."
525 (let (pos)
526 (save-excursion
527 (forward-line n) (forward-line 0)
528 (when (looking-at "^\\s-[0-9]+:")
529 (forward-line 0) (setq pos (point))))
530 (when pos (goto-char pos) (ioccur-color-current-line))))
531
532 ;;;###autoload
533 (defun ioccur-next-line ()
534 "Goto next line if it is not an empty line."
535 (interactive)
536 (ioccur-forward-line 1))
537
538 ;;;###autoload
539 (defun ioccur-precedent-line ()
540 "Goto precedent line if it is not an empty line."
541 (interactive)
542 (ioccur-forward-line -1))
543
544 ;;;###autoload
545 (defun ioccur-beginning-of-buffer ()
546 "Goto beginning of `ioccur-buffer'."
547 (interactive)
548 (when (looking-at "^\\s-[0-9]+:")
549 (goto-char (point-min))
550 (re-search-forward "^\\s-[0-9]+:" nil t)
551 (forward-line 0)
552 (ioccur-color-current-line)))
553
554 ;;;###autoload
555 (defun ioccur-end-of-buffer ()
556 "Go to end of `ioccur-buffer'."
557 (interactive)
558 (when (looking-at "^\\s-[0-9]+:")
559 (goto-char (point-max))
560 (forward-line -1)
561 (ioccur-color-current-line)))
562
563 (defun ioccur-jump (&optional win-conf)
564 "Jump to line in other buffer and put an overlay on it.
565 Move point to first occurence of `ioccur-pattern'."
566 (let* ((line (buffer-substring (point-at-bol) (point-at-eol)))
567 (pos (string-to-number line)))
568 (unless (string= line "")
569 (if win-conf
570 (set-window-configuration win-conf)
571 (pop-to-buffer ioccur-current-buffer))
572 (ioccur-goto-line pos)
573 (recenter)
574 ;; Go to beginning of first occurence in this line
575 ;; of what match `ioccur-pattern'.
576 (when (funcall ioccur-search-function
577 ioccur-pattern (point-at-eol) t)
578 (goto-char (match-beginning 0)))
579 (ioccur-color-matched-line))))
580
581 ;;;###autoload
582 (defun ioccur-jump-and-quit ()
583 "Jump to line in other buffer and quit search buffer."
584 (interactive)
585 (run-hooks 'ioccur-save-pos-before-jump-hook)
586 (when (ioccur-jump ioccur-last-window-configuration)
587 (sit-for 0.3)
588 (when ioccur-match-overlay
589 (delete-overlay ioccur-match-overlay))))
590
591 (defun ioccur-save-current-pos-to-mark-ring ()
592 "Save current buffer position to mark ring.
593 To use this add it to `ioccur-save-pos-before-jump-hook'."
594 (with-current-buffer ioccur-current-buffer
595 (set-marker (mark-marker) ioccur-current-pos)
596 (push-mark ioccur-current-pos 'nomsg)))
597
598 ;;;###autoload
599 (defun ioccur-jump-without-quit (&optional mark)
600 "Jump to line in `ioccur-current-buffer' without quitting."
601 (interactive)
602 (when (ioccur-jump ioccur-last-window-configuration)
603 (and mark (set-marker (mark-marker) (point))
604 (push-mark (point) 'nomsg))
605 (switch-to-buffer-other-window ioccur-buffer t)))
606
607 ;;;###autoload
608 (defun ioccur-scroll-other-window-down ()
609 "Scroll other window down."
610 (interactive)
611 (let ((other-window-scroll-buffer ioccur-current-buffer))
612 (scroll-other-window 1)))
613
614 ;;;###autoload
615 (defun ioccur-scroll-other-window-up ()
616 "Scroll other window up."
617 (interactive)
618 (let ((other-window-scroll-buffer ioccur-current-buffer))
619 (scroll-other-window -1)))
620
621 (defun ioccur-scroll (n)
622 "Scroll `ioccur-buffer' and `ioccur-current-buffer' simultaneously."
623 (ioccur-forward-line n)
624 (ioccur-color-current-line)
625 (and (ioccur-jump ioccur-last-window-configuration)
626 (switch-to-buffer-other-window ioccur-buffer t)))
627
628 ;;;###autoload
629 (defun ioccur-scroll-down ()
630 "Scroll down `ioccur-buffer' and `ioccur-current-buffer' simultaneously."
631 (interactive)
632 (ioccur-scroll 1))
633
634 ;;;###autoload
635 (defun ioccur-scroll-up ()
636 "Scroll up `ioccur-buffer' and `ioccur-current-buffer' simultaneously."
637 (interactive)
638 (ioccur-scroll -1))
639
640 ;;;###autoload
641 (defun ioccur-split-window ()
642 "Toggle split window, vertically or horizontally."
643 (interactive)
644 (with-current-buffer ioccur-current-buffer
645 (let ((old-size (window-height)))
646 (delete-window)
647 (set-window-buffer
648 (select-window (if (= (window-height) old-size)
649 (split-window-vertically)
650 (split-window-horizontally)))
651 (get-buffer ioccur-buffer)))))
652
653 (defun ioccur-read-char-or-event (prompt)
654 "Replace `read-key' when not available using PROMPT."
655 (if (and (fboundp 'read-key)
656 (not ioccur-read-char-or-event-skip-read-key))
657 (read-key prompt)
658 (let* ((chr (condition-case nil (read-char prompt) (error nil)))
659 (evt (unless chr (read-event prompt))))
660 (or chr evt))))
661
662 (defun ioccur-read-search-input (initial-input start-point)
663 "Read each keyboard input and add it to `ioccur-pattern'.
664 INITIAL-INPUT is a string given as default input, generally thing at point.
665 START-POINT is the point where we start searching in buffer."
666 (let* ((prompt (propertize ioccur-search-prompt
667 'face 'minibuffer-prompt))
668 (inhibit-quit (or (eq system-type 'windows-nt)
669 (not (fboundp 'read-key))
670 ioccur-read-char-or-event-skip-read-key))
671 (tmp-list ())
672 (it-prec nil)
673 (it-next nil)
674 (cur-hist-elm (car ioccur-history))
675 (start-hist nil) ; Flag to notify if cycling history started.
676 yank-point
677 (index 0))
678 (unless (string= initial-input "")
679 (loop for char across initial-input do (push char tmp-list)))
680 (setq ioccur-pattern initial-input)
681 ;; Cycle history function.
682 ;;
683 (flet ((cycle-hist (arg)
684 ;; ARG can be positive or negative depending we call M-p or M-n.
685 (if ioccur-history
686 (progn
687 ;; Cycle history will start at second call,
688 ;; at first call just use the car of hist ring.
689 ;; We build a new iterator based on a sublist
690 ;; starting at the current element of history.
691 ;; This is a circular iterator. (no end)
692 (if start-hist ; At first call, start-hist is nil.
693 (progn
694 (if (< arg 0)
695 ;; M-p (move from left to right in hist ring).
696 (unless it-prec ; Don't rebuild iterator if exists.
697 (setq it-prec (ioccur-sub-next-circular
698 ioccur-history
699 cur-hist-elm :test 'equal))
700 (setq it-next nil)) ; Kill forward iterator.
701 ;; M-n (move from right to left in hist ring).
702 (unless it-next ; Don't rebuild iterator if exists.
703 (setq it-next (ioccur-sub-prec-circular
704 ioccur-history
705 cur-hist-elm :test 'equal))
706 (setq it-prec nil))) ; kill backward iterator.
707 (let ((it (or it-prec it-next)))
708 (setq cur-hist-elm (ioccur-iter-next it))
709 (setq tmp-list nil)
710 (loop for char across cur-hist-elm
711 do (push char tmp-list))
712 (setq ioccur-pattern cur-hist-elm)))
713 ;; First call use car of history ring.
714 (setq tmp-list nil)
715 (loop for char across cur-hist-elm
716 do (push char tmp-list))
717 (setq ioccur-pattern cur-hist-elm)
718 (setq start-hist t)))
719 (message "No history available.") (sit-for 2) t))
720 ;; Insert INITIAL-INPUT.
721 ;;
722 (insert-initial-input ()
723 (unless (string= initial-input "")
724 (loop for char across initial-input
725 do (push char (nthcdr index tmp-list)))))
726 ;; Maybe start timer.
727 ;;
728 (start-timer ()
729 (unless ioccur-search-timer
730 (ioccur-start-timer)))
731 ;; Maybe stop timer.
732 ;;
733 (stop-timer ()
734 (when ioccur-search-timer
735 (ioccur-cancel-search)))
736 ;; Kill pattern
737 ;;
738 (kill (str)
739 (with-current-buffer ioccur-current-buffer
740 (goto-char start-point)
741 (setq yank-point start-point))
742 (kill-new (substring str (- (length tmp-list) index)))
743 (setq tmp-list (nthcdr index tmp-list)))
744 ;; Add cursor in minibuffer
745 ;;
746 (set-cursor (str pos)
747 (setq pos (min index (1- (length tmp-list))))
748 (when (not (string= str ""))
749 (let* ((real-index (- (1- (length tmp-list)) pos))
750 (cur-str (substring str real-index (1+ real-index))))
751 (concat (substring str 0 real-index)
752 (propertize cur-str 'display
753 (if (= index (length tmp-list))
754 (concat
755 (propertize "|" 'face 'ioccur-cursor)
756 cur-str)
757 (concat
758 cur-str
759 (propertize "|" 'face 'ioccur-cursor))))
760 (substring str (1+ real-index)))))))
761
762 ;; Start incremental loop.
763 (while (let ((char (ioccur-read-char-or-event
764 (concat prompt (set-cursor ioccur-pattern index)))))
765 (message nil)
766 (case char
767 ((not (?\M-p ?\M-n ?\t C-tab)) ; Reset history
768 (setq start-hist nil)
769 (setq cur-hist-elm (car ioccur-history)) t)
770 ((down ?\C-n) ; Next line.
771 (stop-timer) (ioccur-next-line)
772 (ioccur-color-current-line) t)
773 ((up ?\C-p) ; Precedent line.
774 (stop-timer) (ioccur-precedent-line)
775 (ioccur-color-current-line) t)
776 (?\M-< ; Beginning of buffer.
777 (when (ioccur-beginning-of-buffer)
778 (stop-timer)) t)
779 (?\M-> ; End of buffer.
780 (when (ioccur-end-of-buffer)
781 (stop-timer)) t)
782 ((?\C-d C-down) ; Scroll both windows down.
783 (stop-timer) (ioccur-scroll-down) t)
784 ((?\C-u C-up) ; Scroll both windows up.
785 (stop-timer) (ioccur-scroll-up) t)
786 (?\r ; RET break and exit code.
787 nil)
788 (?\d ; Delete backward with DEL.
789 (start-timer)
790 (with-current-buffer ioccur-current-buffer
791 (goto-char start-point)
792 (setq yank-point start-point))
793 (with-no-warnings (pop (nthcdr index tmp-list)))
794 t)
795 (?\C-g ; Quit and restore buffers.
796 (setq ioccur-quit-flag t) nil)
797 ((right ?\C-z) ; Persistent action.
798 (ioccur-jump-without-quit) t)
799 ((?\C- ) ; Persistent action save mark.
800 (ioccur-jump-without-quit t) t)
801 ((left ?\C-j) ; Jump and kill search buffer.
802 (setq ioccur-exit-and-quit-p t) nil)
803 ((next ?\C-v) ; Scroll down.
804 (ioccur-scroll-other-window-down) t)
805 ((?\C-t ?\M-v prior) ; Scroll up.
806 (ioccur-scroll-other-window-up) t)
807 (?\C-s ; Toggle split window.
808 (ioccur-split-window) t)
809 ((?\C-: ?\C-l) ; Toggle regexp/litteral search.
810 (start-timer)
811 (if (eq ioccur-search-function 're-search-forward)
812 (setq ioccur-search-function 'search-forward)
813 (setq ioccur-search-function 're-search-forward)) t)
814 (?\C-k ; Kill input.
815 (start-timer)
816 (kill ioccur-pattern) (setq index 0) t)
817 ((?\M-k ?\C-x) ; Kill input as sexp.
818 (start-timer)
819 (let ((sexp (prin1-to-string ioccur-pattern)))
820 (kill sexp)
821 (setq ioccur-quit-flag t)
822 (setq ioccur-message (format "Killed: %s" sexp)))
823 nil)
824 (?\C-y ; Yank from `kill-ring'.
825 (setq initial-input (car kill-ring))
826 (insert-initial-input) t)
827 (?\C-w ; Yank stuff at point.
828 (start-timer)
829 (with-current-buffer ioccur-current-buffer
830 ;; Start to initial point if C-w have never been hit.
831 (unless yank-point (setq yank-point start-point))
832 ;; After a search `ioccur-print-results' have put point
833 ;; to point-max, so reset position.
834 (when yank-point (goto-char yank-point))
835 (let ((pmax (point-at-eol))
836 (eoword (save-excursion (forward-word 1) (point))))
837 ;; Don't yank further than eol.
838 (unless (> eoword pmax)
839 (goto-char eoword)
840 (setq initial-input (buffer-substring-no-properties
841 yank-point (point)))
842 (setq yank-point (point)) ; End of last forward-word
843 (insert-initial-input)))) t)
844 ((?\t ?\M-p) ; Precedent history elm.
845 (start-timer)
846 (setq index 0)
847 (cycle-hist -1))
848 ((backtab ?\M-n) ; Next history elm.
849 (start-timer)
850 (setq index 0)
851 (cycle-hist 1))
852 (?\C-q ; quoted-insert.
853 (stop-timer)
854 (let ((char (with-temp-buffer
855 (call-interactively 'quoted-insert)
856 (buffer-string))))
857 (push (string-to-char char) tmp-list))
858 (start-timer)
859 t)
860 ;; Movements in minibuffer
861 (?\C-b ; backward-char.
862 (setq index (min (1+ index) (length tmp-list))) t)
863 (?\C-f ; forward-char.
864 (setq index (max (1- index) 0)) t)
865 (?\C-a ; move bol.
866 (setq index (length tmp-list)) t)
867 (?\C-e ; move eol.
868 (setq index 0) t)
869 (t ; Store character.
870 (start-timer)
871 (if (characterp char)
872 (push char (nthcdr index tmp-list))
873 (setq unread-command-events
874 (nconc (mapcar 'identity
875 (this-single-command-raw-keys))
876 unread-command-events))
877 nil))))
878 (setq ioccur-pattern (apply 'string (reverse tmp-list)))))))
879
880 (defun ioccur-print-buffer (regexp)
881 "Pretty Print results matching REGEXP in `ioccur-buffer'."
882 ;; FIXME: Why force tooltip-mode? What about sessions with both GUI and
883 ;; tty frames?
884 (unless (window-system) (setq tooltip-use-echo-area t) (tooltip-mode 1))
885 (let* ((cur-method (if (eq ioccur-search-function 're-search-forward)
886 "Regexp" "Literal"))
887 (title (propertize
888 (format
889 "* Ioccur %s searching %s"
890 cur-method
891 (if (window-system)
892 "* (`C-:' to Toggle Method, Mouse over for help.)"
893 "* (`C-l' to Toggle Method.)"))
894 'face 'ioccur-title-face
895 'help-echo
896 " Ioccur map:\n
897 C-n or <down> Next line.\n
898 C-p or <up> Precedent line.\n
899 C-v and M-v/C-t Scroll up and down.\n
900 C-z or <right> Jump without quitting loop.\n
901 C-TAB Jump without quitting and save to mark-ring.\n
902 C-j or <left> Jump and kill `ioccur-buffer'.\n
903 RET Exit keeping `ioccur-buffer'.\n
904 DEL Remove last character entered.\n
905 C-k Kill current input.\n
906 C-a/e/b/f Movements in minibuffer.\n
907 M-k/C-x Kill current input as sexp.\n
908 C-w Yank stuff at point.\n
909 C-g Quit and restore buffer.\n
910 C-s Toggle split window.\n
911 C-:/l Toggle regexp/litteral search.\n
912 C-down or C-u Follow in other buffer.\n
913 C-up/d or C-d Follow in other buffer.\n
914 M-<, M-> Beginning and end of buffer.\n
915 M-p/n or tab/S-tab History."))
916 wrong-regexp)
917 (if (string= regexp "")
918 (progn (erase-buffer) (insert title "\n\n"))
919 (erase-buffer)
920 (condition-case _
921 (ioccur-print-results regexp)
922 (error (setq wrong-regexp t)))
923 (goto-char (point-min))
924 (if wrong-regexp
925 (insert
926 title "\n\n"
927 (propertize "Invalid Regexp: "
928 'face 'ioccur-invalid-regexp)
929 (format "No match for `%s'" regexp) "\n\n")
930 (insert title "\n\n"
931 (propertize (format "Found %s occurences matching "
932 ioccur-count-occurences)
933 'face 'underline)
934 (propertize regexp 'face 'ioccur-regexp-face)
935 (propertize
936 (format " in %s" ioccur-current-buffer)
937 'face 'underline) "\n\n")
938 (ioccur-color-current-line)))))
939
940 (defun ioccur-start-timer ()
941 "Start ioccur incremental timer."
942 (setq ioccur-search-timer
943 (run-with-idle-timer
944 ioccur-search-delay 'repeat
945 #'(lambda ()
946 (ioccur-print-buffer
947 ioccur-pattern)))))
948
949 (defun ioccur-send-message ()
950 "Send message defined in `ioccur-message'."
951 (message ioccur-message))
952
953 ;;;###autoload
954 (defun ioccur (&optional initial-input)
955 "Incremental search of lines in current buffer matching input.
956 With a prefix arg search symbol at point (INITIAL-INPUT).
957
958 While you are incremental searching, commands provided are:
959
960 C-n or <down> next line.
961 C-p or <up> precedent line.
962 C-v and M-v scroll up and down.
963 C-z or <right> jump without quitting loop.
964 C-j or <left> jump and kill `ioccur-buffer'.
965 RET exit keeping `ioccur-buffer'.
966 DEL remove last character entered.
967 C-k Kill current input from cursor to eol.
968 C-a/e/b/f Movements in minibuffer.
969 M-k Kill current input as sexp.
970 C-w Yank stuff at point.
971 C-g quit and restore buffer.
972 C-s Toggle split window.
973 C-: Toggle regexp/litteral search.
974 C-down Follow in other buffer.
975 C-up Follow in other buffer.
976 M-p/n Precedent and next `ioccur-history' element.
977 M-<, M-> Beginning and end of buffer.
978
979 Unlike minibuffer history, cycling in ioccur history have no end:
980
981 M-p ,-->A B C D E F G H I---,
982 | |
983 `---I H G F E D C B A<--'
984
985 M-n ,-->I H G F E D C B A---,
986 | |
987 `---A B C D E F G H I<--'
988
989
990 Special NOTE for terms:
991 =======================
992 tab/S-tab are bound to history.
993 C-d/u are for following in other buffer.
994 Use C-t to Scroll up.
995
996 When you quit incremental search with RET, see `ioccur-mode'
997 for commands provided in the `ioccur-buffer'."
998 (interactive "P")
999 (let (pop-up-frames)
1000 (setq ioccur-exit-and-quit-p nil)
1001 (setq ioccur-success nil)
1002 (setq ioccur-current-buffer (buffer-name (current-buffer)))
1003 (when ioccur-fontify-buffer-p
1004 (message "Fontifying buffer...Please wait it could be long.")
1005 (jit-lock-fontify-now) (message nil))
1006 (setq ioccur-buffer (concat "*ioccur-" ioccur-current-buffer "*"))
1007 (setq ioccur-last-window-configuration (current-window-configuration))
1008 (setq ioccur-current-pos (point))
1009 (if (and (not initial-input)
1010 (get-buffer ioccur-buffer)
1011 (not (get-buffer-window ioccur-buffer)))
1012 ;; An hidden `ioccur-buffer' exists jump to it and reuse it.
1013 (switch-to-buffer-other-window ioccur-buffer t)
1014 ;; `ioccur-buffer' doesn't exists or is visible, start searching
1015 ;; Creating a new `ioccur-buffer' or reusing the visible one after
1016 ;; erasing it.
1017 (let* ((init-str (if initial-input
1018 (if (stringp initial-input)
1019 initial-input (thing-at-point 'symbol))
1020 ""))
1021 (len (length init-str))
1022 (curpos (point))
1023 (inhibit-read-only t)
1024 (cur-mode (with-current-buffer ioccur-current-buffer
1025 (prog1
1026 major-mode
1027 ;; If current `major-mode' is wdired
1028 ;; Turn it off.
1029 (when (eq major-mode 'wdired-mode)
1030 (wdired-change-to-dired-mode)))))
1031 str-no-prop)
1032 (set-text-properties 0 len nil init-str)
1033 (setq str-no-prop init-str)
1034 (pop-to-buffer (get-buffer-create ioccur-buffer))
1035 (ioccur-mode)
1036 (unwind-protect
1037 ;; Start incremental search.
1038 (progn
1039 (ioccur-start-timer)
1040 (ioccur-read-search-input str-no-prop curpos))
1041 ;; At this point incremental search loop is exited.
1042 (progn
1043 (ioccur-cancel-search)
1044 (kill-local-variable 'mode-line-format)
1045 (when (equal (buffer-substring (point-at-bol) (point-at-eol)) "")
1046 (setq ioccur-quit-flag t))
1047 (cond (ioccur-quit-flag ; C-g hit or empty `ioccur-buffer'.
1048 (kill-buffer ioccur-buffer)
1049 (pop-to-buffer ioccur-current-buffer)
1050 (when ioccur-match-overlay
1051 (delete-overlay ioccur-match-overlay))
1052 (set-window-configuration ioccur-last-window-configuration)
1053 (goto-char curpos)
1054 (ioccur-send-message)
1055 ;; If `ioccur-message' is non--nil, thats mean we exit
1056 ;; with a specific action other than `C-g',
1057 ;; e.g kill-as-sexp, so we save history.
1058 (when ioccur-message (ioccur-save-history)))
1059 (ioccur-exit-and-quit-p ; Jump and kill `ioccur-buffer'.
1060 (ioccur-jump-and-quit)
1061 (kill-buffer ioccur-buffer)
1062 (ioccur-send-message) (ioccur-save-history))
1063 (t ; Jump keeping `ioccur-buffer'.
1064 (ioccur-jump)
1065 (pop-to-buffer ioccur-buffer)
1066 (setq buffer-read-only t)
1067 (ioccur-save-history)))
1068 ;; Maybe reenable `wdired-mode'.
1069 (when (eq cur-mode 'wdired-mode) (wdired-change-to-wdired-mode))
1070 (setq ioccur-count-occurences 0)
1071 (setq ioccur-quit-flag nil)
1072 (setq ioccur-message nil)
1073 (setq ioccur-search-function ioccur-default-search-function)
1074 (setq ioccur-current-pos nil)))))))
1075
1076 (defun ioccur-save-history ()
1077 "Save last ioccur element found in `ioccur-history'."
1078 (unless (string= ioccur-pattern "")
1079 (setq ioccur-history
1080 (cons ioccur-pattern (delete ioccur-pattern ioccur-history)))
1081 (when (> (length ioccur-history) ioccur-max-length-history)
1082 (setq ioccur-history (delete (car (last ioccur-history))
1083 ioccur-history)))
1084 (setq ioccur-success t)))
1085
1086 (defun ioccur-cancel-search ()
1087 "Cancel timer used for ioccur searching."
1088 (when ioccur-search-timer
1089 (cancel-timer ioccur-search-timer)
1090 (setq ioccur-search-timer nil)))
1091
1092 (defun ioccur-color-current-line ()
1093 "Highlight and underline current line in `ioccur-buffer'."
1094 (if ioccur-occur-overlay
1095 (move-overlay ioccur-occur-overlay
1096 (point-at-bol) (1+ (point-at-eol)) ioccur-buffer)
1097 (setq ioccur-occur-overlay
1098 (make-overlay (point-at-bol) (1+ (point-at-eol)) ioccur-buffer)))
1099 (overlay-put ioccur-occur-overlay 'face 'ioccur-overlay-face))
1100
1101 (defun ioccur-color-matched-line ()
1102 "Highlight and underline current position \
1103 of matched line in `ioccur-current-buffer'."
1104 (if ioccur-match-overlay
1105 (move-overlay ioccur-match-overlay
1106 (point-at-bol) (1+ (point-at-eol)))
1107 (setq ioccur-match-overlay
1108 (make-overlay (point-at-bol) (1+ (point-at-eol)))))
1109 (overlay-put ioccur-match-overlay 'face 'ioccur-match-overlay-face))
1110
1111
1112 (provide 'ioccur)
1113
1114 ;;; ioccur.el ends here