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