Mercurial > hg > xemacs-beta
comparison lisp/packages/igrep.el @ 24:4103f0995bd7 r19-15b95
Import from CVS: tag r19-15b95
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:51:03 +0200 |
parents | |
children | 34a5b81f86ba |
comparison
equal
deleted
inserted
replaced
23:0edd3412f124 | 24:4103f0995bd7 |
---|---|
1 ;;;; igrep.el --- An improved interface to `grep`. | |
2 | |
3 ;;; Description: | |
4 ;;; | |
5 ;;; Define the \[igrep] command, which is like \[grep] except that it | |
6 ;;; takes three required arguments (PROGRAM, EXPRESSION, and FILES) and | |
7 ;;; an optional argument (OPTIONS) instead of just one argument (COMMAND). | |
8 ;;; Also define the analogous \[egrep] and \[fgrep] commands for convenience. | |
9 ;;; | |
10 ;;; Define the \[igrep-find] command, which is like \[igrep] except that | |
11 ;;; it uses `find` to recursively `grep` a directory. Also define the | |
12 ;;; analogous \[egrep-find] and \[fgrep-find] commands for convenience. | |
13 ;;; | |
14 ;;; \[igrep] and \[igrep-find] (and their analogues) provide defaults | |
15 ;;; for the EXPRESSION and FILES arguments when called interactively, | |
16 ;;; and there are global variables that control the syntax of the `grep` | |
17 ;;; and `find` shell commands that are executed. A user option controls | |
18 ;;; whether the corresponding GNU (gzip) "zPROGRAM" script is used, to | |
19 ;;; `grep` compressed files. | |
20 ;;; | |
21 ;;; \[agrep] and \[agrep-find] are also defined as convenient interfaces | |
22 ;;; to the approximate `grep` utility, which is distributed with the | |
23 ;;; `glimpse' indexing and query tool (available from | |
24 ;;; <URL:http://glimpse.cs.arizona.edu:1994/>). | |
25 ;;; | |
26 ;;; \[grep] itself is advised to provide the \[igrep] interface when | |
27 ;;; called interactively (when called programmatically, it still uses | |
28 ;;; the original argument list). \[grep-find] is defined as an alias | |
29 ;;; for \[igrep-find]. | |
30 ;;; | |
31 ;;; When run interactively from Dired mode, the various \[igrep] | |
32 ;;; commands provide defaults for the EXPRESSION and FILES arguments | |
33 ;;; that are based on the visited directory (including any inserted | |
34 ;;; subdirectories) and the current file. In addition, the | |
35 ;;; \[dired-do-igrep] and \[dired-do-igrep-find] commands are defined | |
36 ;;; that respect the `dired-do-*' command calling conventions: a prefix | |
37 ;;; argument is interpreted as the number of succeeding files to `grep`, | |
38 ;;; otherwise all the marked files are `grep`ed. \[dired-do-grep] and | |
39 ;;; \[dired-do-grep-find] are defined as aliases for \[dired-do-igrep] | |
40 ;;; and \[dired-do-igrep-find]. | |
41 | |
42 ;;; Copyright: | |
43 ;;; | |
44 ;;; Copyright © 1994,1995,1996,1997 Kevin Rodgers | |
45 ;;; | |
46 ;;; This program is free software; you can redistribute it and/or modify | |
47 ;;; it under the terms of the GNU General Public License as published by | |
48 ;;; the Free Software Foundation; either version 2 of the License, or | |
49 ;;; at your option) any later version. | |
50 ;;; | |
51 ;;; This program is distributed in the hope that it will be useful, | |
52 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
53 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
54 ;;; GNU General Public License for more details. | |
55 ;;; | |
56 ;;; You should have received a copy of the GNU General Public License | |
57 ;;; along with this program; if not, write to the Free Software | |
58 ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
59 ;;; | |
60 ;;; Neither my former nor current employer (Martin Marietta and | |
61 ;;; Information Handling Services, respectively) has disclaimed any | |
62 ;;; copyright interest in igrep.el. | |
63 ;;; | |
64 ;;; Kevin Rodgers <kevinr@ihs.com> Project Engineer | |
65 ;;; Information Handling Services Electronic Systems Development | |
66 ;;; 15 Inverness Way East, M/S A201 | |
67 ;;; Englewood CO 80112 USA (303)397-2807[voice]/-2779[fax] | |
68 | |
69 ;;; Installation: | |
70 ;;; | |
71 ;;; 1. Put this file in a directory that is a member of load-path, and | |
72 ;;; byte-compile it (e.g. with `M-x byte-compile-file') for better | |
73 ;;; performance. You can ignore any warnings about references to free | |
74 ;;; variables and "not known to be defined" functions. | |
75 ;;; 2. Put these forms in default.el or ~/.emacs: | |
76 ;;; (autoload (function igrep) "igrep" | |
77 ;;; "*Run `grep` PROGRAM to match EXPRESSION in FILES..." t) | |
78 ;;; (autoload (function igrep-find) "igrep" | |
79 ;;; "*Run `grep` via `find`..." t) | |
80 ;;; (autoload (function dired-do-igrep) "igrep" | |
81 ;;; "*Run `grep` on the marked (or next prefix ARG) files." t) | |
82 ;;; (autoload (function dired-do-igrep-find) "igrep" | |
83 ;;; "*Run `grep` via `find` on the marked (or next prefix ARG) directories." t) | |
84 ;;; 2. a. For completeness, you can add these forms as well: | |
85 ;;; (autoload (function grep) "igrep" | |
86 ;;; "*Run `grep` PROGRAM to match EXPRESSION in FILES..." t) | |
87 ;;; (autoload (function egrep) "igrep" | |
88 ;;; "*Run `egrep`..." t) | |
89 ;;; (autoload (function fgrep) "igrep" | |
90 ;;; "*Run `fgrep`..." t) | |
91 ;;; (autoload (function agrep) "igrep" | |
92 ;;; "*Run `agrep`..." t) | |
93 ;;; (autoload (function grep-find) "igrep" | |
94 ;;; "*Run `grep` via `find`..." t) | |
95 ;;; (autoload (function egrep-find) "igrep" | |
96 ;;; "*Run `egrep` via `find`..." t) | |
97 ;;; (autoload (function fgrep-find) "igrep" | |
98 ;;; "*Run `fgrep` via `find`..." t) | |
99 ;;; (autoload (function agrep-find) "igrep" | |
100 ;;; "*Run `agrep` via `find`..." t) | |
101 ;;; (autoload (function dired-do-grep) "igrep" | |
102 ;;; "*Run `grep` on the marked (or next prefix ARG) files." t) | |
103 ;;; (autoload (function dired-do-grep-find) "igrep" | |
104 ;;; "*Run `grep` via `find` on the marked (or next prefix ARG) directories." t) | |
105 ;;; 3. If you are running Windows 95/NT, you should install findutils | |
106 ;;; and grep from release 17.1 (or higher) of the Cygnus GNU-Win32 | |
107 ;;; distribution. See <URL:http://www.cygnus.com/misc/gnu-win32/>. | |
108 | |
109 ;;; Usage: | |
110 ;;; | |
111 ;;; M-x igrep M-x igrep-find | |
112 ;;; M-x grep M-x grep-find | |
113 ;;; M-x egrep M-x egrep-find | |
114 ;;; M-x fgrep M-x fgrep-find | |
115 ;;; M-x agrep M-x agrep-find | |
116 ;;; M-x dired-do-igrep M-x dired-do-igrep-find | |
117 ;;; M-x dired-do-grep M-x dired-do-grep-find | |
118 ;;; (Each of the 10 igrep commands accepts 1, 2, or 3 `C-u' prefix arguments. | |
119 ;;; The 2 Dired commands interpret a prefix argument like the regular `dired-do' | |
120 ;;; commands.) | |
121 ;;; C-x ` (M-x next-error) C-c C-c (M-x compile-goto-error) [in *igrep*] | |
122 | |
123 ;;; Customization examples: | |
124 ;;; | |
125 ;;; To ignore case by default: | |
126 ;;; (setq igrep-options "-i") | |
127 ;;; To search subdirectories by default: | |
128 ;;; (setq igrep-find t) | |
129 ;;; To search files with the GNU (gzip) zgrep script: | |
130 ;;; (setq igrep-use-zgrep t) | |
131 ;;; or define new igrep commands (this works for zegrep and zfgrep as well): | |
132 ;;; (igrep-define zgrep) ; M-x zgrep | |
133 ;;; (igrep-find-define zgrep) ; M-x zgrep-find | |
134 ;;; To avoid exceeding some shells' limit on command argument length | |
135 ;;; (this only works when grep'ing files in the current directory): | |
136 ;;; (setq igrep-find t | |
137 ;;; igrep-find-prune-option "\\! -name .") | |
138 | |
139 ;;; To do: | |
140 ;;; | |
141 ;;; 1. Delete the *-program variables (except for igrep-program) and | |
142 ;;; replace igrep-options with a table that maps igrep-program to the | |
143 ;;; appropriate options. | |
144 ;;; 2. Generalize support for the -prune find clause (e.g. -fstype nfs). | |
145 ;;; 3. Provide support for `glimpse`. | |
146 ;;; 4. Add a menu interface. | |
147 | |
148 ;;; LCD Archive Entry: | |
149 ;;; | |
150 ;;; igrep|Kevin Rodgers|kevinr@ihs.com| | |
151 ;;; An improved interface to grep/egrep/fgrep; plus recursive `find` versions.| | |
152 ;;; 97/02/10|2.56|~/misc/igrep.el.Z| | |
153 | |
154 | |
155 ;;; Package interface: | |
156 | |
157 (provide 'igrep) | |
158 | |
159 (require 'backquote) ; igrep-with-default-in-history | |
160 (require 'compile) ; compile-internal and grep-regexp- | |
161 ; alist | |
162 | |
163 (defconst igrep-version "2.56" | |
164 "Version of igrep.el") | |
165 | |
166 | |
167 ;;; User options: | |
168 | |
169 (defvar igrep-options nil | |
170 "*The options passed by \\[igrep] to `igrep-program', or nil. | |
171 | |
172 `-n' will automatically be passed to `igrep-program', to generate the | |
173 output expected by \\[next-error] and \\[compile-goto-error]. | |
174 `-e' will automatically be passed to `igrep-program', if it supports | |
175 that option.") | |
176 | |
177 (defvar igrep-read-options nil | |
178 "*If non-nil, `\\[igrep]' always prompts for options; | |
179 otherwise, it only prompts when 1 or 3 `C-u's are given as a prefix arg.") | |
180 | |
181 (defvar igrep-read-multiple-files nil | |
182 "*If non-nil, `\\[igrep]' always prompts for multiple-files; | |
183 otherwise, it only prompts when 2 or 3 `C-u's are given as a prefix arg.") | |
184 | |
185 (defvar igrep-verbose-prompts t | |
186 "*If t, \\[igrep] prompts for arguments verbosely; | |
187 if not t but non-nil, \\[igrep] prompts for arguments semi-verbosely; | |
188 if nil, \\[igrep] prompts for arguments tersely.") | |
189 | |
190 (defvar igrep-save-buffers 'query | |
191 "*If t, \\[igrep] first saves each modified file buffer; | |
192 if not t but non-nil, \\[igrep] offers to save each modified file buffer.") | |
193 | |
194 (defvar igrep-program-table ; referenced by igrep-use-zgrep | |
195 (let ((exec-directories exec-path) | |
196 (program-obarray (make-vector 11 0))) | |
197 (while exec-directories | |
198 (if (and (car exec-directories) | |
199 (file-directory-p (car exec-directories))) | |
200 (let ((grep-programs | |
201 (directory-files (car exec-directories) | |
202 nil "grep\\(\\.exe\\)?\\'"))) | |
203 (while grep-programs | |
204 ;; Check `(file-executable-p (car grep-programs))'? | |
205 (if (save-match-data | |
206 (string-match "\\.exe\\'" (car grep-programs))) | |
207 (intern (substring (car grep-programs) 0 -4) program-obarray) | |
208 (intern (car grep-programs) program-obarray)) | |
209 (setq grep-programs (cdr grep-programs))))) | |
210 (setq exec-directories (cdr exec-directories))) | |
211 program-obarray) | |
212 "An obarray of available `grep` programs, passed by `igrep-read-program' | |
213 to `completing-read' when `igrep-program' is nil.") | |
214 | |
215 (defvar igrep-use-zgrep | |
216 (if (intern-soft "zgrep" igrep-program-table) | |
217 'files) | |
218 "If t, \\[igrep] searches files using the GNU (gzip) `zPROGRAM` script; | |
219 If not t but non-nil, \\[igrep] searches compressed FILES using `zPROGRAM`; | |
220 if nil, \\[igrep] searches files with `PROGRAM`.") | |
221 | |
222 | |
223 ;;; User variables: | |
224 | |
225 (defvar igrep-program "grep" | |
226 "The default shell program run by \\[igrep] and \\[igrep-find]. | |
227 It must take a `grep` expression argument and one or more file names. | |
228 If nil, \\[igrep] prompts for the program to run.") | |
229 | |
230 (defvar igrep-expression-quote-char | |
231 (if (memq system-type '(ms-dos windows-95 windows-nt emx)) | |
232 ?\" | |
233 ?') | |
234 "The character used to delimit the EXPRESSION argument to \\[igrep], | |
235 to protect it from shell filename expansion.") | |
236 | |
237 (defvar igrep-expression-option | |
238 (if (or (eq system-type 'berkeley-unix) | |
239 (save-match-data | |
240 (string-match "-sco" system-configuration))) | |
241 "-e") | |
242 "If non-nil, the option used to specify the EXPRESSION argument to \\[igrep], | |
243 to protect an initial `-' from option processing.") | |
244 | |
245 (defvar igrep-parenthesis-escape-char | |
246 (if (memq system-type '(ms-dos windows-95 windows-nt emx)) | |
247 nil | |
248 ?\\) | |
249 "If non-nil, the character used by \\[igrep] to escape parentheses, | |
250 to protect them from shell interpretation.") | |
251 | |
252 (defvar igrep-find nil | |
253 "If non-nil, \\[igrep] searches directories using `find`. | |
254 See \\[igrep-find].") | |
255 | |
256 (defvar igrep-find-prune-options | |
257 (if (not (save-match-data | |
258 (string-match "-sco" system-configuration))) | |
259 "-name SCCS -o -name RCS") | |
260 "The `find` clause used to prune directories, or nil; | |
261 see \\[igrep-find].") | |
262 | |
263 (defvar igrep-find-file-options "-type f -o -type l" | |
264 "The `find` clause used to filter files passed to `grep`, or nil; | |
265 see \\[igrep-find].") | |
266 | |
267 (defvar igrep-find-use-xargs | |
268 (if (equal (call-process "find" nil nil nil | |
269 (if (boundp 'grep-null-device) | |
270 grep-null-device | |
271 "/dev/null") | |
272 "-print0") | |
273 0) | |
274 'gnu) | |
275 "If `gnu', \\[igrep-find] executes | |
276 `find ... -print0 | xargs -0 -e grep ...`; | |
277 if not `gnu' but non-nil, \\[igrep-find] executes | |
278 `find ... -print | xargs -e grep ...`; | |
279 if nil, \\[igrep-find] executes | |
280 `find ... -exec grep ...`.") | |
281 | |
282 (defvar igrep-program-default nil | |
283 "The default `grep` program, passed by `igrep-read-program' | |
284 to `completing-read' when `igrep-program' is nil.") | |
285 | |
286 (defvar igrep-expression-history '() | |
287 "The minibuffer history list for \\[igrep]'s EXPRESSION argument.") | |
288 | |
289 (defvar igrep-files-history '() | |
290 "The minibuffer history list for \\[igrep]'s FILES argument.") | |
291 | |
292 | |
293 ;;; Commands: | |
294 | |
295 ;; ;;;###autoload Not ready to replace compile.el's grep yet. -sb | |
296 (defadvice grep (around igrep-interface first (&rest grep-args) activate) | |
297 "If called interactively, use the \\[igrep] interface instead, | |
298 where GREP-ARGS is (PROGRAM EXPRESSION FILES OPTIONS); | |
299 if called programmatically, GREP-ARGS is still (COMMAND)." | |
300 (interactive (igrep-read-args)) | |
301 (if (interactive-p) | |
302 (apply (function igrep) grep-args) | |
303 ad-do-it)) | |
304 | |
305 (defalias 'grep-find 'igrep-find) | |
306 | |
307 ;;;###autoload | |
308 (defun igrep (program expression files &optional options) | |
309 "*Run `grep` PROGRAM to match EXPRESSION in FILES. | |
310 The output is displayed in the *igrep* buffer, which \\[next-error] and | |
311 \\[compile-goto-error] parse to find each line of matched text. | |
312 | |
313 PROGRAM may be nil, in which case it defaults to `igrep-program'. | |
314 | |
315 EXPRESSION is automatically delimited by `igrep-expression-quote-char'. | |
316 | |
317 FILES is either a file name pattern (expanded by the shell named by | |
318 `shell-file-name') or a list of file name patterns. | |
319 | |
320 Optional OPTIONS is also passed to PROGRAM; it defaults to `igrep-options'. | |
321 | |
322 If a prefix argument \ | |
323 \(\\[universal-argument]\) \ | |
324 is given when called interactively, | |
325 or if `igrep-read-options' is set, OPTIONS is read from the minibuffer. | |
326 | |
327 If two prefix arguments \ | |
328 \(\\[universal-argument] \\[universal-argument]\) \ | |
329 are given when called interactively, | |
330 or if `igrep-read-multiple-files' is set, FILES is read from the minibuffer | |
331 multiple times. | |
332 | |
333 If three prefix arguments \ | |
334 \(\\[universal-argument] \\[universal-argument] \\[universal-argument]\) \ | |
335 are given when called interactively, | |
336 or if `igrep-read-options' and `igrep-read-multiple-files' are set, | |
337 OPTIONS is read and FILES is read multiple times. | |
338 | |
339 If `igrep-find' is non-nil, the directory or directories | |
340 containing FILES is recursively searched for files whose name matches | |
341 the file name component of FILES \(and whose contents match | |
342 EXPRESSION\)." | |
343 (interactive | |
344 (igrep-read-args)) | |
345 (if (null program) | |
346 (setq program (or igrep-program "grep"))) | |
347 (if (null options) | |
348 (setq options igrep-options)) | |
349 (if (not (listp files)) ; (stringp files) | |
350 (setq files (list files))) | |
351 (if (string-match "^[rj]?sh$" (file-name-nondirectory shell-file-name)) | |
352 ;; (restricted, job-control, or standard) Bourne shell doesn't expand ~: | |
353 (setq files | |
354 (mapcar 'expand-file-name files))) | |
355 (let* ((win32-quote-process-args nil) ; work around NT Emacs hack | |
356 (use-zgrep (cond ((eq igrep-use-zgrep t)) | |
357 (igrep-use-zgrep | |
358 (let ((files files) | |
359 (compressed-p nil)) | |
360 (while (and files (not compressed-p)) | |
361 (if (save-match-data | |
362 (string-match "\\.g?[zZ]\\'" (car files))) | |
363 (setq compressed-p t)) | |
364 (setq files (cdr files))) | |
365 compressed-p)) | |
366 (t nil))) | |
367 (command (format "%s -n %s %s %c%s%c %s %s" | |
368 (if (and use-zgrep | |
369 (save-match-data | |
370 (not (string-match "\\`z" program)))) | |
371 (setq program (concat "z" program)) | |
372 program) | |
373 (or options "") | |
374 (or igrep-expression-option | |
375 (progn | |
376 (if (save-match-data | |
377 (string-match "\\`-" expression)) | |
378 (setq expression (concat "\\" expression))) | |
379 "")) | |
380 igrep-expression-quote-char | |
381 expression | |
382 igrep-expression-quote-char | |
383 (if igrep-find | |
384 (if igrep-find-use-xargs | |
385 "" | |
386 "\"{}\"") | |
387 (mapconcat (function identity) files " ")) | |
388 (if (boundp 'grep-null-device) | |
389 grep-null-device | |
390 "/dev/null")))) | |
391 (if igrep-find | |
392 (setq command | |
393 (igrep-format-find-command command files))) | |
394 (cond ((eq igrep-save-buffers t) (save-some-buffers t)) | |
395 (igrep-save-buffers (save-some-buffers))) | |
396 (compile-internal command | |
397 (format "No more %c%s%c matches" | |
398 igrep-expression-quote-char | |
399 program | |
400 igrep-expression-quote-char) | |
401 "igrep" nil grep-regexp-alist))) | |
402 | |
403 ;; Analogue commands: | |
404 | |
405 ;;;###autoload | |
406 (defmacro igrep-define (analogue-command &rest igrep-bindings) | |
407 "Define ANALOGUE-COMMAND as an `igrep' analogue command. | |
408 Optional (VARIABLE VALUE) arguments specify temporary bindings for the command." | |
409 ;;; (interactive "SCommand: ") ; C-u => read bindings? | |
410 (let ((analogue-program (symbol-name analogue-command))) | |
411 (` (defun (, analogue-command) (&rest igrep-args) | |
412 (, (format "*Run `%s` via \\[igrep]. | |
413 All arguments \(including prefix arguments, when called interactively\) | |
414 are handled by `igrep'." | |
415 analogue-program)) | |
416 (interactive | |
417 (let ((igrep-program (if igrep-program (, analogue-program))) | |
418 (igrep-program-default (, analogue-program))) | |
419 (igrep-read-args))) | |
420 (let ( (,@ igrep-bindings)) | |
421 (apply (function igrep) | |
422 (cond ((interactive-p) (car igrep-args)) | |
423 ((car igrep-args)) | |
424 (t (, analogue-program))) | |
425 (cdr igrep-args))))))) | |
426 | |
427 (igrep-define egrep) | |
428 (igrep-define fgrep) | |
429 (igrep-define agrep | |
430 (igrep-use-zgrep nil) | |
431 (igrep-expression-option "-e")) | |
432 | |
433 | |
434 ;; Recursive (`find`) commands: | |
435 | |
436 ;;;###autoload | |
437 (defun igrep-find (&rest igrep-args) | |
438 "*Run `grep` via `find`; see \\[igrep] and `igrep-find'. | |
439 All arguments \(including prefix arguments, when called interactively\) | |
440 are handled by `igrep'." | |
441 (interactive | |
442 (let ((igrep-find t)) | |
443 (igrep-read-args))) | |
444 (let ((igrep-find t)) | |
445 (apply (function igrep) igrep-args))) | |
446 | |
447 ;; Analogue recursive (`find`) commands: | |
448 | |
449 ;;;###autoload | |
450 (defmacro igrep-find-define (analogue-command &rest igrep-bindings) | |
451 "Define ANALOGUE-COMMAND-find as an `igrep' analogue `find` command. | |
452 Optional (VARIABLE VALUE) arguments specify temporary bindings for the command." | |
453 ;;; (interactive "SCommand: ") ; C-u => read bindings? | |
454 (let ((analogue-program (symbol-name analogue-command))) | |
455 (setq analogue-command | |
456 (intern (format "%s-find" analogue-command))) | |
457 (` (defun (, analogue-command) (&rest igrep-args) | |
458 (, (format "*Run `%s` via \\[igrep-find]. | |
459 All arguments \(including prefix arguments, when called interactively\) | |
460 are handled by `igrep'." | |
461 analogue-program)) | |
462 (interactive | |
463 (let ((igrep-program (if igrep-program (, analogue-program))) | |
464 (igrep-program-default (, analogue-program)) | |
465 (igrep-find t)) | |
466 (igrep-read-args))) | |
467 (let ( (,@ igrep-bindings)) | |
468 (apply (function igrep-find) | |
469 (cond ((interactive-p) (car igrep-args)) | |
470 ((car igrep-args)) | |
471 (t (, analogue-program))) | |
472 (cdr igrep-args))))))) | |
473 | |
474 (igrep-find-define egrep) | |
475 (igrep-find-define fgrep) | |
476 (igrep-find-define agrep | |
477 (igrep-use-zgrep nil) | |
478 (igrep-expression-option "-e")) | |
479 | |
480 | |
481 ;; Dired commands: | |
482 | |
483 ;;;###autoload | |
484 (defun dired-do-igrep (program expression &optional options arg) | |
485 "*Run `grep` PROGRAM to match EXPRESSION (with optional OPTIONS) | |
486 on the marked (or next prefix ARG) files." | |
487 (interactive | |
488 (let* ((current-prefix-arg nil) | |
489 (igrep-args (igrep-read-args t))) | |
490 ;; Delete FILES: | |
491 (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args)) | |
492 ;; Append ARG: | |
493 (nconc igrep-args (list current-prefix-arg)))) | |
494 (igrep program | |
495 expression | |
496 (funcall (cond ((fboundp 'dired-get-marked-files) ; GNU Emacs | |
497 'dired-get-marked-files) | |
498 ((fboundp 'dired-mark-get-files) ; XEmacs | |
499 'dired-mark-get-files)) | |
500 t arg) | |
501 options)) | |
502 | |
503 ;;;###autoload | |
504 (defalias 'dired-do-grep 'dired-do-igrep) | |
505 | |
506 ;; Dired recursive (`find`) commands: | |
507 | |
508 ;;;###autoload | |
509 (defun dired-do-igrep-find (program expression &optional options arg) | |
510 "*Run `grep` PROGRAM to match EXPRESSION (with optional OPTIONS) | |
511 on the marked (or next prefix ARG) directories." | |
512 (interactive | |
513 (let* ((current-prefix-arg nil) | |
514 (igrep-find t) | |
515 (igrep-args (igrep-read-args t))) | |
516 ;; Delete FILES: | |
517 (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args)) | |
518 ;; Append ARG: | |
519 (nconc igrep-args (list current-prefix-arg)))) | |
520 (let ((igrep-find t)) | |
521 (dired-do-igrep program expression options arg))) | |
522 | |
523 ;;;###autoload | |
524 (defalias 'dired-do-grep-find 'dired-do-igrep-find) | |
525 | |
526 | |
527 ;;; Utilities: | |
528 | |
529 (defsubst igrep-file-directory (name) | |
530 ;; Return the directory component of NAME, or "." if it has no | |
531 ;; directory component. | |
532 (directory-file-name (or (file-name-directory name) | |
533 (file-name-as-directory ".")))) | |
534 | |
535 (defsubst igrep-file-pattern (name) | |
536 ;; Return the file component of NAME, or "*" if it has no file | |
537 ;; component. | |
538 (let ((pattern (file-name-nondirectory name))) | |
539 (if (string= pattern "") | |
540 "*" | |
541 pattern))) | |
542 | |
543 (defun igrep-format-find-command (command files) | |
544 ;; Format `grep` COMMAND to be invoked via `find` on FILES. | |
545 (let ((directories '()) | |
546 (patterns '())) | |
547 (while files | |
548 (let ((dir (igrep-file-directory (car files))) | |
549 (pat (igrep-file-pattern (car files)))) | |
550 (if (and (not (string= dir ".")) | |
551 (file-symlink-p dir)) | |
552 (setq dir (concat dir "/."))) | |
553 (if (not (member dir directories)) | |
554 (setq directories (cons dir directories))) | |
555 (cond ((equal pat "*") | |
556 (setq patterns t)) | |
557 ((and (listp patterns) | |
558 (not (member pat patterns))) | |
559 (setq patterns (cons pat patterns))))) | |
560 (setq files (cdr files))) | |
561 (format (cond ((eq igrep-find-use-xargs 'gnu) | |
562 ;; | \\\n | |
563 "find %s %s %s %s -print0 | xargs -0 -e %s") | |
564 (igrep-find-use-xargs | |
565 ;; | \\\n | |
566 "find %s %s %s %s -print | xargs -e %s") | |
567 ;;; ((memq system-type '(ms-dos windows-95 windows-nt emx)) | |
568 ;;; "find %s %s %s %s -exec %s ;") | |
569 (t | |
570 "find %s %s %s %s -exec %s \\;")) | |
571 (mapconcat (function identity) (nreverse directories) | |
572 " ") | |
573 (if igrep-find-prune-options | |
574 (format "-type d %c( %s %c) -prune -o" | |
575 (or igrep-parenthesis-escape-char ? ) | |
576 igrep-find-prune-options | |
577 (or igrep-parenthesis-escape-char ? )) | |
578 "") | |
579 (if igrep-find-file-options | |
580 (format "%c( %s %c)" | |
581 (or igrep-parenthesis-escape-char ? ) | |
582 igrep-find-file-options | |
583 (or igrep-parenthesis-escape-char ? )) | |
584 "") | |
585 (if (listp patterns) | |
586 (if (cdr patterns) ; (> (length patterns) 1) | |
587 (format "%c( %s %c)" | |
588 (or igrep-parenthesis-escape-char " ") | |
589 (mapconcat (function (lambda (pat) | |
590 (format "-name \"%s\"" pat))) | |
591 (nreverse patterns) | |
592 " -o ") | |
593 (or igrep-parenthesis-escape-char " ")) | |
594 (format "-name \"%s\"" (car patterns))) | |
595 "") | |
596 command))) | |
597 | |
598 (defun igrep-read-args (&optional no-files) | |
599 ;; Read and return a list: (PROGRAM EXPRESSION FILES OPTIONS). | |
600 ;; If NO-FILES is non-nil, then FILES is not read and nil is returned | |
601 ;; in its place. | |
602 (let* ((program (igrep-read-program (if igrep-verbose-prompts | |
603 (if igrep-find | |
604 "[find] ")))) | |
605 (prompt-prefix (if igrep-verbose-prompts | |
606 (apply (function concat) | |
607 (if igrep-find | |
608 "[find] ") | |
609 (if (eq igrep-verbose-prompts t) | |
610 (list program " "))))) | |
611 (options (igrep-read-options prompt-prefix))) | |
612 (if (eq igrep-verbose-prompts t) | |
613 (setq prompt-prefix | |
614 (concat prompt-prefix options " "))) | |
615 (list program | |
616 (igrep-read-expression prompt-prefix) | |
617 (if (not no-files) | |
618 (igrep-read-files prompt-prefix)) | |
619 options))) | |
620 | |
621 (defsubst igrep-prefix (prefix string) | |
622 ;; If PREFIX is non-nil, concatenate it and STRING; otherwise, return STRING. | |
623 (if prefix | |
624 (concat prefix string) | |
625 string)) | |
626 | |
627 (defun igrep-read-program (&optional prompt-prefix) | |
628 ;; If igrep-program is nil, read and return a program name from the | |
629 ;; minibuffer; otherwise, return igrep-program. | |
630 ;; Optional PROMPT-PREFIX is prepended to the "Program: " prompt. | |
631 (or igrep-program | |
632 (let ((prompt "Program: ")) | |
633 (completing-read (igrep-prefix prompt-prefix prompt) igrep-program-table | |
634 nil t (or igrep-program-default "grep"))))) | |
635 | |
636 (defun igrep-read-options (&optional prompt-prefix) | |
637 ;; If current-prefix-arg is '(4) or '(64), read and return an options | |
638 ;; string from the minibuffer; otherwise, return igrep-options. | |
639 ;; Optional PROMPT-PREFIX is prepended to the "Options: " prompt. | |
640 (if (or igrep-read-options | |
641 (and (consp current-prefix-arg) | |
642 (memq (prefix-numeric-value current-prefix-arg) | |
643 '(4 64)))) | |
644 (let ((prompt "Options: ")) | |
645 (read-string (igrep-prefix prompt-prefix prompt) | |
646 (or igrep-options "-"))) | |
647 igrep-options)) | |
648 | |
649 (defun igrep-read-expression (&optional prompt-prefix) | |
650 ;; Read and return a `grep` expression string from the minibuffer. | |
651 ;; Optional PROMPT-PREFIX is prepended to the "Expression: " prompt. | |
652 (let ((default-expression (igrep-default-expression))) | |
653 (if (string= default-expression "") | |
654 (read-from-minibuffer (igrep-prefix prompt-prefix "Expression: ") | |
655 nil nil nil 'igrep-expression-history) | |
656 (let ((expression | |
657 (igrep-read-string-with-default-in-history | |
658 (igrep-prefix prompt-prefix (format "Expression (default %s): " | |
659 default-expression)) | |
660 default-expression | |
661 'igrep-expression-history))) | |
662 (if (string= expression "") | |
663 default-expression | |
664 expression))))) | |
665 | |
666 (defun igrep-default-expression () | |
667 (if (eq major-mode 'dired-mode) | |
668 (let ((dired-file (dired-get-filename nil t))) | |
669 (save-excursion | |
670 (set-buffer (or (and dired-file (get-file-buffer dired-file)) | |
671 (other-buffer (current-buffer) t))) | |
672 (current-word))) | |
673 (current-word))) | |
674 | |
675 (defsubst igrep-default-key () | |
676 ;; Return the key bound to `exit-minibuffer', preferably "\r". | |
677 (if (eq (lookup-key minibuffer-local-completion-map "\r") | |
678 (function exit-minibuffer)) | |
679 "\r" | |
680 (where-is-internal (function exit-minibuffer) | |
681 minibuffer-local-completion-map | |
682 t))) | |
683 | |
684 (defun igrep-read-files (&optional prompt-prefix) | |
685 ;; Read and return a file name pattern from the minibuffer. If | |
686 ;; current-prefix-arg is '(16) or '(64), read multiple file name | |
687 ;; patterns and return them in a list. Optional PROMPT-PREFIX is | |
688 ;; prepended to the "File(s): " prompt. | |
689 (let* ((dired-subdirectory (if (eq major-mode 'dired-mode) | |
690 (dired-current-directory t))) | |
691 (default-files (concat dired-subdirectory | |
692 (igrep-default-file-pattern))) | |
693 (prompt (format "File(s) (default %s): " default-files)) | |
694 (insert-default-directory nil) ; use relative path names | |
695 (file (igrep-read-file-name-with-default-in-history | |
696 (igrep-prefix prompt-prefix prompt) | |
697 default-files | |
698 nil | |
699 'igrep-files-history))) | |
700 (if (or igrep-read-multiple-files | |
701 (and (consp current-prefix-arg) | |
702 (memq (prefix-numeric-value current-prefix-arg) | |
703 '(16 64)))) | |
704 (let ((files (list file))) | |
705 (setq prompt | |
706 (igrep-prefix prompt-prefix | |
707 (if igrep-verbose-prompts | |
708 (format "File(s): [Type `%s' when done] " | |
709 (key-description (igrep-default-key))) | |
710 "File(s): "))) | |
711 (while (not (string= (setq file | |
712 (igrep-read-file-name prompt | |
713 nil "" nil nil | |
714 'igrep-files-history)) | |
715 "")) | |
716 (setq files (cons file files))) | |
717 (nreverse files)) | |
718 file))) | |
719 | |
720 (defmacro igrep-with-default-in-history (default history &rest body) | |
721 ;; Temporarily append DEFAULT to HISTORY, and execute BODY forms. | |
722 (` (progn | |
723 ;; Append default to history: | |
724 (set history | |
725 (cons (, default) | |
726 (if (boundp (, history)) | |
727 (symbol-value (, history)) | |
728 '()))) | |
729 (unwind-protect ; Make sure the history is restored. | |
730 ;; Execute body: | |
731 (progn (,@ body)) | |
732 ;; Delete default from history (undo the append above): | |
733 (setcdr (symbol-value (, history)) | |
734 (nthcdr 2 (symbol-value (, history)))))))) | |
735 | |
736 (defun igrep-read-string-with-default-in-history (prompt default history) | |
737 ;; Read a string from the minibuffer, prompting with string PROMPT. | |
738 ;; DEFAULT can be inserted into the minibuffer with `previous- | |
739 ;; history-element'; HISTORY is a symbol whose value (if bound) is a | |
740 ;; list of previous results, most recent first. | |
741 (let ((string (igrep-with-default-in-history default history | |
742 (read-from-minibuffer prompt nil nil nil history)))) | |
743 ;; Replace empty string in history with default: | |
744 (if (string= string "") | |
745 (setcar (symbol-value history) default)) | |
746 string)) | |
747 | |
748 (defun igrep-read-file-name-with-default-in-history | |
749 (prompt &optional default initial history) | |
750 ;; Read a file name from the minibuffer, prompting with string PROMPT. | |
751 ;; DEFAULT can be inserted into the minibuffer with `previous- | |
752 ;; history-element'; HISTORY is a symbol whose value (if any) is a | |
753 ;; list of previous results, most recent first. | |
754 (igrep-with-default-in-history default history | |
755 (igrep-read-file-name prompt nil default nil initial history))) | |
756 | |
757 (defun igrep-read-file-name (prompt | |
758 &optional directory default existing initial history) | |
759 ;; Just like read-file-name, but with optional HISTORY. | |
760 ;; Also: convert DIRECTORY to DIRECTORY/* file name pattern. | |
761 (let ((file-name | |
762 (if history | |
763 (let ((file-name-history (symbol-value history))) | |
764 (prog1 (read-file-name prompt directory default existing initial) | |
765 (set history file-name-history))) | |
766 (read-file-name prompt directory default existing initial)))) | |
767 (if (and (not (string-equal file-name "")) | |
768 (file-directory-p file-name)) | |
769 (expand-file-name "*" file-name) | |
770 file-name))) | |
771 | |
772 (defun igrep-default-file-pattern () | |
773 ;; Return a shell file name pattern that matches files with the same | |
774 ;; extension as the file being visited in the current buffer. | |
775 ;; (Based on other-possibly-interesting-files in ~/as-is/unix.el, by | |
776 ;; Wolfgang Rupprecht <wolfgang@mgm.mit.edu>.) | |
777 (if (eq major-mode 'dired-mode) | |
778 (cond ((stringp dired-directory) | |
779 (if (file-directory-p dired-directory) | |
780 "*" | |
781 (file-name-nondirectory dired-directory))) ; wildcard | |
782 ((consp dired-directory) ; (DIR FILE ...) | |
783 (mapconcat 'identity (cdr dired-directory) " "))) | |
784 (if buffer-file-name | |
785 (let ((file-name (file-name-nondirectory buffer-file-name))) | |
786 (concat "*" | |
787 (save-match-data | |
788 (if (string-match "\\.[^.]+\\(\\.g?[zZ]\\)?$" file-name) | |
789 (substring file-name | |
790 (match-beginning 0) (match-end 0)))))) | |
791 "*"))) | |
792 | |
793 ;;; Local Variables: | |
794 ;;; eval: (put 'igrep-with-default-in-history 'lisp-indent-hook 2) | |
795 ;;; eval: (put 'igrep-define 'lisp-indent-hook 1) | |
796 ;;; eval: (put 'igrep-find-define 'lisp-indent-hook 1) | |
797 ;;; End: | |
798 | |
799 ;;;; igrep.el ends here | |
800 |