OSDN Git Service

update version strings in generated files
[howm/howm.git] / iigrep.el
1 ;;; iigrep.el - incremental interactive grep
2 ;;; Copyright (C) 2004, 2005-2022
3 ;;;   HIRAOKA Kazuyuki <khi@users.osdn.me>
4 ;;;
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)
8 ;;; any later version.
9 ;;;
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.
14 ;;;
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,
18 ;;; USA.
19 ;;;--------------------------------------------------------------------
20
21 ;;; Commentary:
22
23 ;; examples
24 ;; 
25 ;; Search the directry ~/foo incrementally.
26 ;;     M-x iigrep RET ~/foo RET
27 ;; 
28 ;; Search the directry ~/foo with migemo incrementally.
29 ;;     M-x iigrep-migemo RET ~/foo RET
30
31 ;; links
32 ;; 
33 ;; The original (obsolete) iigrep.el
34 ;; http://howm.sourceforge.jp/cgi-bin/hiki/hiki.cgi?IncrementalGrep
35 ;; 
36 ;; migemo
37 ;; http://0xcc.net/migemo/
38 ;; http://www.kaoriya.net/software/cmigemo/
39
40 ;; brief history
41 ;; 
42 ;; [2022-08-23] export to howm
43 ;; [2004-12-01] rename to iigrep.el
44 ;; [2004-11-30] prototype ingrep.el
45
46 ;;; Code:
47
48 (require 'compile)
49
50 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
51 ;; customize
52
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.")
59
60 (defvar iigrep-counts-face-rules
61   '(
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)
68     ))
69
70 (defvar iigrep-buffer-name "*iigrep*")
71 (defvar iigrep-process-name "iigrep")
72 (defvar iigrep-mode-name "iigrep")
73
74 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
75 ;; faces
76
77 (defgroup iigrep nil
78   "Incremental grep"
79   :group 'applications)
80
81 (defface iigrep-counts-face1
82   '((((class color) (background light)) (:foreground "blue"))
83     (((class color) (background dark)) (:foreground "cyan"))
84     (t ()))
85   "*Face for iigrep counts."
86   :group 'iigrep)
87
88 (defface iigrep-counts-face2
89   '((((class color) (background light)) (:foreground "dark green"))
90     (((class color) (background dark)) (:foreground "green"))
91     (t ()))
92   "*Face for iigrep counts."
93   :group 'iigrep)
94 (defface iigrep-counts-face3
95   '((((class color)) (:foreground "orange"))
96     (t ()))
97   "*Face for iigrep counts."
98   :group 'iigrep)
99 (defface iigrep-counts-face4
100   '((((class color)) (:foreground "red"))
101     (t ()))
102   "*Face for iigrep counts."
103   :group 'iigrep)
104 (defface iigrep-counts-face5
105   '((((class color)) (:foreground "purple"))
106     (t ()))
107   "*Face for iigrep counts."
108   :group 'iigrep)
109
110 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
111 ;; main command
112
113 (defun iigrep (dir)
114   (interactive "Ddirectory: ")
115   (cd dir)
116   (iigrep-with-grep-internal (iigrep-command-for-pattern-on-dir dir)
117       iigrep-default-show-what
118     (read-from-minibuffer (iigrep-prompt))))
119
120 (defun iigrep-command-for-pattern-on-dir (dir)
121   (lambda (pattern)
122     (list iigrep-command iigrep-option pattern
123           iigrep-recursive-option (expand-file-name "."))))
124
125 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
126 ;; main lib (iigrep-with-grep)
127
128 (defvar iigrep-command-for-pattern nil
129   "For internal use")
130 (defvar iigrep-show-what nil
131   "For internal use")
132 (defvar iigrep-target-minibuffer nil
133   "For internal use")
134
135 (defmacro iigrep-with-grep (command-for-pattern show-what &rest body)
136   (declare (indent 2))
137   `(save-window-excursion
138      (unwind-protect
139          (iigrep-with-grep-internal ,command-for-pattern ,show-what
140            ,@body)
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)
144   (declare (indent 2))
145   `(save-excursion
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))
149            (progn ,@body)
150          (iigrep-setup-window t (not (eq iigrep-show-what 'counts)))
151          (unwind-protect
152              (minibuffer-with-setup-hook #'iigrep-minibuffer-setup
153                ,@body)
154            (iigrep-minibuffer-cleanup))))))
155
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))))
167     (when alivep
168       (with-current-buffer buf
169         (remove-hook 'after-change-functions #'iigrep-update))))
170   (setq iigrep-target-minibuffer nil))
171
172 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
173 ;; buf & win
174
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)))))
181
182 (defun iigrep-buffer (&optional create silent)
183   (if create
184       (iigrep-get-buffer-create)
185     (or (get-buffer iigrep-buffer-name)
186         (if silent nil
187           (error "iigrep buffer is lost.")))))
188
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))
194     buf))
195
196 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
197 ;; update
198
199 ;; In the minibuffer...
200
201 (defun iigrep-update (&rest dummy)
202   (let ((pattern (and (iigrep-target-minibuffer-p)
203                       (minibuffer-contents-no-properties))))
204     (when pattern
205       (iigrep-convert-call pattern #'iigrep-search))))
206
207 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
208 ;; search
209
210 (defvar iigrep-last-pattern nil
211   "For internal use.")
212 (defvar iigrep-last-command nil
213   "For debug.")
214
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))))
223
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)))
231
232 (defvar iigrep-process-filter #'iigrep-process-filter
233   "Filter for grep process.
234 This value is also used for identification of iigrep processes.")
235
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))))))
243
244 (defun iigrep-append-output (output)
245   (let ((buffer-read-only nil))
246     (goto-char (point-max))
247     (insert output)
248     (goto-char (point-min))
249     (when (eq iigrep-show-what 'contents)
250       (iigrep-hide-paths))
251     (set-buffer-modified-p nil)))
252
253 (defun iigrep-hide-paths ()
254   (font-lock-mode -1)
255   (save-excursion
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))))
259
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))))
265     face))
266
267 (defun iigrep-erase-buffer ()
268   (let ((buffer-read-only nil))
269     (erase-buffer)))
270
271 (defun iigrep-exceed-limit (p)
272   (iigrep-kill-process)
273   (iigrep-append-output "\nSize limit exceeded."))
274
275 (defvar *iigrep-post-sentinel* nil)
276
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)))
284           (when (> hits 0)
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))))))))
290
291 (defun iigrep-kill-process ()
292   (mapcar (lambda (p)
293             (when (iigrep-process-p p)
294               (set-process-buffer p nil)
295               (kill-process p)))
296           (process-list)))
297
298 (defun iigrep-process-p (p)
299   (eq (process-filter p) iigrep-process-filter))
300
301 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
302 ;; converter
303
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).
308
309 (defvar *iigrep-convert-call* #'iigrep-identity-converter
310   "For internal use.")
311 (defvar *iigrep-prompt* "grep: "
312   "For internal use.")
313
314 (defun iigrep-identity-converter (pattern continuation)
315   (funcall continuation pattern))
316
317 (defun iigrep-convert-call (pattern continuation)
318   (funcall *iigrep-convert-call* pattern continuation))
319
320 (defun iigrep-prompt () *iigrep-prompt*)
321
322 (defmacro iigrep-with-converter (func prompt &rest body)
323   (declare (indent 2))
324   `(let ((*iigrep-convert-call* (or ,func *iigrep-convert-call*))
325          (*iigrep-prompt* (or ,prompt *iigrep-prompt*)))
326      ,@body))
327
328 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
329 ;; migemo
330
331 ;; Use continuation to avoid slow response
332
333 (defun iigrep-migemo (dir)
334   (require 'migemo)
335   (interactive "Ddirectory: ")
336   (iigrep-with-converter #'iigrep-migemo-converter "migemo: "
337     (iigrep dir)))
338
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)
346   (declare (indent 0))
347   `(let ((iigrep-original-migemo-process migemo-process)
348          (migemo-process iigrep-migemo-process)
349          (migemo-options iigrep-migemo-options))
350      (unwind-protect
351          (progn
352            ,@body)
353        (setq iigrep-migemo-process migemo-process
354              migemo-process iigrep-original-migemo-process))))
355
356 (defun iigrep-migemo-converter (roma continuation)
357   (iigrep-with-our-migemo
358     (iigrep-migemo-search roma continuation)))
359
360 ;; copied and modified from migemo-get-pattern in migemo.el (migemo-0.32)
361
362 (defun iigrep-migemo-search (word continuation)
363   (migemo-init)
364   (set-process-filter migemo-process
365                       (iigrep-migemo-filter continuation))
366   (let ((orig-buffer (current-buffer)))
367     (save-excursion
368       (set-buffer (process-buffer migemo-process))
369       (delete-region (point-min) (point-max))
370       (process-send-string migemo-process (concat word "\n")))))
371
372 (defvar iigrep-migemo-last-pattern nil
373   "For internal use.")
374 (defvar iigrep-migemo-last-buffer nil
375   "For debug.")
376 (defun iigrep-migemo-filter (continuation)
377   `(lambda (process message)
378      (let ((orig-buffer (current-buffer)))
379        (save-excursion
380          (set-buffer (process-buffer process))
381          (insert message)
382          (when (and (> (point-max) 1)
383                     (eq (char-after (1- (point-max))) ?\n))
384            ;; AD HOC!
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)))
394              (erase-buffer)
395              (funcall (function ,continuation)
396                       pattern)))))))
397
398 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
399 ;; key binding
400
401 ;; backward compatibility for my old .emacs
402 (defun iigrep-define-key-for (command &optional force map))
403
404 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
405 ;; provide
406
407 (provide 'iigrep)
408
409 ;;; iigrep.el ends here