1 ;;; iigrep.el - incremental interactive grep
2 ;;; Copyright (C) 2004, 2005-2022
3 ;;; HIRAOKA Kazuyuki <khi@users.osdn.me>
5 ;;; This program is free software; you can redistribute it and/or modify
6 ;;; it under the terms of the GNU General Public License as published by
7 ;;; the Free Software Foundation; either version 1, or (at your option)
10 ;;; This program is distributed in the hope that it will be useful,
11 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;; GNU General Public License for more details.
15 ;;; The GNU General Public License is available by anonymouse ftp from
16 ;;; prep.ai.mit.edu in pub/gnu/COPYING. Alternately, you can write to
17 ;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
19 ;;;--------------------------------------------------------------------
25 ;; Search the directry ~/foo incrementally.
26 ;; M-x iigrep RET ~/foo RET
28 ;; Search the directry ~/foo with migemo incrementally.
29 ;; M-x iigrep-migemo RET ~/foo RET
33 ;; The original (obsolete) iigrep.el
34 ;; http://howm.sourceforge.jp/cgi-bin/hiki/hiki.cgi?IncrementalGrep
37 ;; http://0xcc.net/migemo/
38 ;; http://www.kaoriya.net/software/cmigemo/
42 ;; [2022-08-23] export to howm
43 ;; [2004-12-01] rename to iigrep.el
44 ;; [2004-11-30] prototype ingrep.el
50 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
53 (defvar iigrep-maximum-output 40000)
54 (defvar iigrep-command "egrep")
55 (defvar iigrep-option "-nIe")
56 (defvar iigrep-recursive-option "-r")
57 (defvar iigrep-default-show-what 'full
58 "One of 'full, 'contents, 'counts, or nil.")
60 (defvar iigrep-counts-face-rules
62 ;; (threshold-of-hits . face)
63 (10 . iigrep-counts-face1)
64 (50 . iigrep-counts-face2)
65 (150 . iigrep-counts-face3)
66 (300 . iigrep-counts-face4)
67 (500 . iigrep-counts-face5)
70 (defvar iigrep-buffer-name "*iigrep*")
71 (defvar iigrep-process-name "iigrep")
72 (defvar iigrep-mode-name "iigrep")
74 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81 (defface iigrep-counts-face1
82 '((((class color) (background light)) (:foreground "blue"))
83 (((class color) (background dark)) (:foreground "cyan"))
85 "*Face for iigrep counts."
88 (defface iigrep-counts-face2
89 '((((class color) (background light)) (:foreground "dark green"))
90 (((class color) (background dark)) (:foreground "green"))
92 "*Face for iigrep counts."
94 (defface iigrep-counts-face3
95 '((((class color)) (:foreground "orange"))
97 "*Face for iigrep counts."
99 (defface iigrep-counts-face4
100 '((((class color)) (:foreground "red"))
102 "*Face for iigrep counts."
104 (defface iigrep-counts-face5
105 '((((class color)) (:foreground "purple"))
107 "*Face for iigrep counts."
110 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
114 (interactive "Ddirectory: ")
116 (iigrep-with-grep-internal (iigrep-command-for-pattern-on-dir dir)
117 iigrep-default-show-what
118 (read-from-minibuffer (iigrep-prompt))))
120 (defun iigrep-command-for-pattern-on-dir (dir)
122 (list iigrep-command iigrep-option pattern
123 iigrep-recursive-option (expand-file-name "."))))
125 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
126 ;; main lib (iigrep-with-grep)
128 (defvar iigrep-command-for-pattern nil
130 (defvar iigrep-show-what nil
132 (defvar iigrep-target-minibuffer nil
135 (defmacro iigrep-with-grep (command-for-pattern show-what &rest body)
137 `(save-window-excursion
139 (iigrep-with-grep-internal ,command-for-pattern ,show-what
141 (let ((buf (iigrep-buffer nil t)))
142 (when buf (kill-buffer buf))))))
143 (defmacro iigrep-with-grep-internal (command-for-pattern show-what &rest body)
146 (let* ((iigrep-command-for-pattern ,command-for-pattern)
147 (iigrep-show-what ,show-what))
148 (if (or (null iigrep-command-for-pattern) (null iigrep-show-what))
150 (iigrep-setup-window t (not (eq iigrep-show-what 'counts)))
152 (minibuffer-with-setup-hook #'iigrep-minibuffer-setup
154 (iigrep-minibuffer-cleanup))))))
156 (defun iigrep-target-minibuffer-p ()
157 (eq (current-buffer) iigrep-target-minibuffer))
158 (defun iigrep-minibuffer-setup ()
159 (when iigrep-target-minibuffer
160 (message "iigrep: terminated previos session")
161 (iigrep-minibuffer-cleanup))
162 (setq iigrep-target-minibuffer (current-buffer))
163 (add-hook 'after-change-functions #'iigrep-update))
164 (defun iigrep-minibuffer-cleanup ()
165 (let* ((buf iigrep-target-minibuffer)
166 (alivep (and (bufferp buf) (buffer-name buf))))
168 (with-current-buffer buf
169 (remove-hook 'after-change-functions #'iigrep-update))))
170 (setq iigrep-target-minibuffer nil))
172 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
175 (defun iigrep-setup-window (&optional create show-p)
176 (let* ((buf (iigrep-buffer create))
177 (win (get-buffer-window buf)))
178 (cond (win (select-window win))
179 (show-p (pop-to-buffer buf))
180 (t (set-buffer buf)))))
182 (defun iigrep-buffer (&optional create silent)
184 (iigrep-get-buffer-create)
185 (or (get-buffer iigrep-buffer-name)
187 (error "iigrep buffer is lost.")))))
189 (defun iigrep-get-buffer-create ()
190 (let ((buf (get-buffer-create iigrep-buffer-name)))
191 (with-current-buffer buf
192 (setq buffer-read-only t)
193 (compilation-mode iigrep-mode-name))
196 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
199 ;; In the minibuffer...
201 (defun iigrep-update (&rest dummy)
202 (let ((pattern (and (iigrep-target-minibuffer-p)
203 (minibuffer-contents-no-properties))))
205 (iigrep-convert-call pattern #'iigrep-search))))
207 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
210 (defvar iigrep-last-pattern nil
212 (defvar iigrep-last-command nil
215 (defun iigrep-search (pattern)
216 (setq iigrep-last-pattern pattern)
217 (when (not (string= pattern ""))
218 (save-window-excursion
219 (iigrep-setup-window)
220 (iigrep-kill-process)
221 (iigrep-erase-buffer)
222 (iigrep-grep pattern))))
224 (defun iigrep-grep (pattern)
225 (let* ((args (funcall iigrep-command-for-pattern pattern))
226 (p (apply #'start-process iigrep-process-name nil args)))
227 (setq iigrep-last-command args)
228 (set-process-filter p iigrep-process-filter)
229 (set-process-sentinel p #'iigrep-sentinel)
230 (set-process-query-on-exit-flag p nil)))
232 (defvar iigrep-process-filter #'iigrep-process-filter
233 "Filter for grep process.
234 This value is also used for identification of iigrep processes.")
236 (defun iigrep-process-filter (p output)
237 (let ((buf (iigrep-buffer nil t)))
238 (when buf ;; Don't accept output from previous search
239 (with-current-buffer buf
240 (if (> (point-max) iigrep-maximum-output)
241 (iigrep-exceed-limit p)
242 (iigrep-append-output output))))))
244 (defun iigrep-append-output (output)
245 (let ((buffer-read-only nil))
246 (goto-char (point-max))
248 (goto-char (point-min))
249 (when (eq iigrep-show-what 'contents)
251 (set-buffer-modified-p nil)))
253 (defun iigrep-hide-paths ()
256 (goto-char (point-min))
257 (while (re-search-forward "^.*?:[0-9]+:" nil t)
258 (put-text-property (match-beginning 0) (match-end 0) 'invisible t))))
260 (defun iigrep-get-counts-face (hits)
261 (let* ((filtered (mapcan (lambda (pair)
262 (and (<= hits (car pair)) (list pair)))
263 iigrep-counts-face-rules))
264 (face (cdr-safe (car-safe filtered))))
267 (defun iigrep-erase-buffer ()
268 (let ((buffer-read-only nil))
271 (defun iigrep-exceed-limit (p)
272 (iigrep-kill-process)
273 (iigrep-append-output "\nSize limit exceeded."))
275 (defvar *iigrep-post-sentinel* nil)
277 (defun iigrep-sentinel (proc msg)
278 (let ((stat (process-status proc))
279 (buf (iigrep-buffer nil t)))
280 (when (and buf (member stat '(exit signal)))
281 (with-current-buffer buf
282 (let* ((hits (count-lines (point-min) (point-max)))
283 (s (format "%s" hits)))
285 (put-text-property 0 (length s) 'face (iigrep-get-counts-face hits) s)
286 (let ((message-log-max nil))
287 (message "%s hits" s))
288 (when (and *iigrep-post-sentinel* (eq stat 'exit))
289 (funcall *iigrep-post-sentinel* hits iigrep-last-pattern))))))))
291 (defun iigrep-kill-process ()
293 (when (iigrep-process-p p)
294 (set-process-buffer p nil)
298 (defun iigrep-process-p (p)
299 (eq (process-filter p) iigrep-process-filter))
301 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
304 ;; Continuation passing style is used partially
305 ;; so that we can easily control when to call grep, and we can accept
306 ;; fast typing while getting pattern from migemo.
307 ;; In normal search, (iigrep-convert-call pat func) is just (func pat).
309 (defvar *iigrep-convert-call* #'iigrep-identity-converter
311 (defvar *iigrep-prompt* "grep: "
314 (defun iigrep-identity-converter (pattern continuation)
315 (funcall continuation pattern))
317 (defun iigrep-convert-call (pattern continuation)
318 (funcall *iigrep-convert-call* pattern continuation))
320 (defun iigrep-prompt () *iigrep-prompt*)
322 (defmacro iigrep-with-converter (func prompt &rest body)
324 `(let ((*iigrep-convert-call* (or ,func *iigrep-convert-call*))
325 (*iigrep-prompt* (or ,prompt *iigrep-prompt*)))
328 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
331 ;; Use continuation to avoid slow response
333 (defun iigrep-migemo (dir)
335 (interactive "Ddirectory: ")
336 (iigrep-with-converter #'iigrep-migemo-converter "migemo: "
339 (defvar iigrep-migemo-process nil)
340 ;; (defvar iigrep-migemo-options '("-q" "--emacs" "--nonewline") ;; for GNU grep
341 (defvar iigrep-migemo-options '("-q")
342 "*Options for migemo command for iigrep.
343 The default value is for cmigemo.
344 Use '(\"-S\" \"migemo\" \"-t\" \"egrep\") for the original migemo.")
345 (defmacro iigrep-with-our-migemo (&rest body)
347 `(let ((iigrep-original-migemo-process migemo-process)
348 (migemo-process iigrep-migemo-process)
349 (migemo-options iigrep-migemo-options))
353 (setq iigrep-migemo-process migemo-process
354 migemo-process iigrep-original-migemo-process))))
356 (defun iigrep-migemo-converter (roma continuation)
357 (iigrep-with-our-migemo
358 (iigrep-migemo-search roma continuation)))
360 ;; copied and modified from migemo-get-pattern in migemo.el (migemo-0.32)
362 (defun iigrep-migemo-search (word continuation)
364 (set-process-filter migemo-process
365 (iigrep-migemo-filter continuation))
366 (let ((orig-buffer (current-buffer)))
368 (set-buffer (process-buffer migemo-process))
369 (delete-region (point-min) (point-max))
370 (process-send-string migemo-process (concat word "\n")))))
372 (defvar iigrep-migemo-last-pattern nil
374 (defvar iigrep-migemo-last-buffer nil
376 (defun iigrep-migemo-filter (continuation)
377 `(lambda (process message)
378 (let ((orig-buffer (current-buffer)))
380 (set-buffer (process-buffer process))
382 (when (and (> (point-max) 1)
383 (eq (char-after (1- (point-max))) ?\n))
385 ;; I don't understand this.
386 ;; Observe iigrep-migemo-last-pattern and iigrep-migemo-last-buffer
387 ;; after typing keys fast.
388 (goto-char (point-min))
389 (skip-chars-forward "\n")
390 (let ((pattern (buffer-substring (point) (line-end-position))))
391 (setq iigrep-migemo-last-pattern pattern)
392 (setq iigrep-migemo-last-buffer
393 (buffer-substring (point-min) (point-max)))
395 (funcall (function ,continuation)
398 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
401 ;; backward compatibility for my old .emacs
402 (defun iigrep-define-key-for (command &optional force map))
404 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
409 ;;; iigrep.el ends here