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