1 ;;; igrep.el --- An improved interface to `grep` and `find`
2 ;;; -*-coding: iso-latin-1;-*-
4 ;; Copyright © 1993-1998,2000-2005 Kevin Rodgers
6 ;; Author: Kevin Rodgers <ihs_4664@yahoo.com>
7 ;; Created: 22 Jun 1993
9 ;; Keywords: tools, processes, search
10 ;; SCCS: @(#)igrep.el 2.113
12 ;; This program is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation; either version 2 of
15 ;; the License, or (at your option) any later version.
17 ;; This program is distributed in the hope that it will be
18 ;; useful, but WITHOUT ANY WARRANTY; without even the implied
19 ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
20 ;; PURPOSE. See the GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public
23 ;; License along with this program; if not, write to the Free
24 ;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
29 ;; The `igrep' command is like `grep' except that it takes three
30 ;; required arguments (PROGRAM, REGEX, and FILES) and an optional
31 ;; argument (OPTIONS) instead of just one argument (COMMAND). The
32 ;; analogous `egrep' and `fgrep' commands are also defined for
35 ;; The `igrep-find' command is like `igrep' except that it uses `find`
36 ;; to recursively `grep` a directory. The analogous `egrep-find' and
37 ;; `fgrep-find' commands are also defined for convenience.
39 ;; When called interactively, `igrep' and `igrep-find' (and their
40 ;; analogues) provide defaults for the REGEX and FILES arguments based
41 ;; on the current word and the visited file name (if the `igrep-regex-
42 ;; default' and `igrep-files-default' options are set, respectively).
43 ;; The `igrep-insert-default-key' option allows the default value to be
44 ;; inserted into the minibuffer for editing; since Emacs 20 provides
45 ;; that via the minibuffer history, it's only enabled for older
46 ;; versions by default. Other options that control the user interface
47 ;; are `igrep-insert-default-directory', `igrep-read-options', `igrep-
48 ;; read-multiple-files', `igrep-verbose-prompts', `igrep-save-buffers',
49 ;; and `igrep-menu-bar'.
51 ;; Besides the basic `igrep-program' and `igrep-find-program' global
52 ;; variables, other variables control the syntax of the `grep` and
53 ;; `find` shell commands that are executed: `igrep-options', `igrep-
54 ;; regex-option', `igrep-case-fold-search', `igrep-find-prune-clause',
55 ;; `igrep-find-file-clause', and `igrep-find-use-xargs'.
57 ;; The `igrep-use-zgrep' user option controls whether the corresponding
58 ;; GNU (gzip) "zPROGRAM" script is used, to `grep` compressed files.
59 ;; Special minibuffer history lists are maintained for the REGEX and
62 ;; The `agrep' and `agrep-find' commands are interfaces to the
63 ;; approximate `grep` utility, which is distributed with the `glimpse'
64 ;; indexing and query tool (available from http://www.tgries.de/agrep/).
66 ;; `grep' itself can be advised to provide the `igrep' interface when
67 ;; it is invoked interactively (but still use the original argument
68 ;; list when it is called from Emacs Lisp), via the `igrep-insinuate'
69 ;; command. `igrep-insinuate' also defines `grep-find' as an alias for
70 ;; `igrep-find', `dired-do-grep' and `dired-do-grep-find' as aliases
71 ;; for `dired-do-igrep' and `dired-do-igrep-find', and `Buffer-menu-
72 ;; grep' as an alias for `Buffer-menu-igrep'.
74 ;; When run interactively from Dired mode, the various `igrep' commands
75 ;; provide defaults for the REGEX and FILES arguments that are based on
76 ;; the visited directory (including any inserted subdirectories) and
77 ;; the current file. The alternative `dired-do-igrep' and `dired-do-
78 ;; igrep-find' commands respect the `dired-do-*' command conventions: a
79 ;; prefix argument is interpreted as the number of succeeding files to
80 ;; `grep`, otherwise all the marked files are `grep`ed.
82 ;; The `igrep-visited-files' command provides a simple way to `grep`
83 ;; just those files that are being visited in buffers. The `Buffer-
84 ;; menu-igrep' command does the same thing, for buffers marked for
85 ;; selection in Buffer Menu mode.
89 ;; 1. Put this file in a directory that is a member of load-path, and
90 ;; byte-compile it (e.g. with `M-x byte-compile-file') for better
91 ;; performance. You can ignore any warnings about references to free
92 ;; variables and "not known to be defined" functions.
93 ;; 2. Put these forms in default.el or ~/.emacs:
94 ;; (autoload 'igrep "igrep"
95 ;; "*Run `grep` PROGRAM to match REGEX in FILES..." t)
96 ;; (autoload 'igrep-find "igrep"
97 ;; "*Run `grep` via `find`..." t)
98 ;; (autoload 'igrep-visited-files "igrep"
99 ;; "*Run `grep` ... on all visited files." t)
100 ;; (autoload 'dired-do-igrep "igrep"
101 ;; "*Run `grep` on the marked (or next prefix ARG) files." t)
102 ;; (autoload 'dired-do-igrep-find "igrep"
103 ;; "*Run `grep` via `find` on the marked (or next prefix ARG) directories." t)
104 ;; (autoload 'Buffer-menu-igrep "igrep"
105 ;; "*Run `grep` on the files visited in buffers marked with '>'." t)
106 ;; (autoload 'igrep-insinuate "igrep"
107 ;; "Define `grep' aliases for the corresponding `igrep' commands." t)
108 ;; 2. a. For completeness, you can add these forms as well:
109 ;; (autoload 'grep "igrep"
110 ;; "*Run `grep` PROGRAM to match REGEX in FILES..." t)
111 ;; (autoload 'egrep "igrep"
112 ;; "*Run `egrep`..." t)
113 ;; (autoload 'fgrep "igrep"
114 ;; "*Run `fgrep`..." t)
115 ;; (autoload 'agrep "igrep"
116 ;; "*Run `agrep`..." t)
117 ;; (autoload 'grep-find "igrep"
118 ;; "*Run `grep` via `find`..." t)
119 ;; (autoload 'egrep-find "igrep"
120 ;; "*Run `egrep` via `find`..." t)
121 ;; (autoload 'fgrep-find "igrep"
122 ;; "*Run `fgrep` via `find`..." t)
123 ;; (autoload 'agrep-find "igrep"
124 ;; "*Run `agrep` via `find`..." t)
125 ;; 3. If you are running Windows 95/NT, you should install findutils
126 ;; and grep from release 17.1 (or higher) of the Cygnus GNU-Win32
127 ;; distribution (http://www.cygnus.com/misc/gnu-win32/).
131 ;; These igrep commands accept 1, 2, or 3 `C-u' prefix arguments:
132 ;; M-x igrep M-x igrep-find
133 ;; M-x grep M-x grep-find [after `M-x igrep-insinuate']
134 ;; M-x egrep M-x egrep-find
135 ;; M-x fgrep M-x fgrep-find
136 ;; M-x agrep M-x agrep-find
138 ;; These igrep commands accept a single `C-u' prefix argument:
139 ;; M-x igrep-visited-files
140 ;; M-x Buffer-menu-igrep [in the *Buffer List* buffer]
142 ;; These igrep commands interpret a prefix argument like the Emacs
143 ;; `dired-do-*' commands:
144 ;; M-x dired-do-igrep M-x dired-do-igrep-find
145 ;; M-x dired-do-grep M-x dired-do-grep-find [after `M-x
148 ;; These Emacs commands can be used after any igrep command:
149 ;; C-x ` (M-x next-error)
150 ;; C-c C-c (M-x compile-goto-error) [in the *igrep* buffer]
152 ;; Customization examples:
154 ;; To ignore case by default:
155 ;; (setq igrep-options "-i")
157 ;; (setq igrep-case-fold-search t)
158 ;; To search subdirectories by default:
159 ;; (setq igrep-find t)
160 ;; To search files with the GNU (gzip) zgrep script:
161 ;; (setq igrep-use-zgrep t)
162 ;; or define new igrep commands (this works for zegrep and zfgrep as well):
163 ;; (igrep-define zgrep) ; M-x zgrep
164 ;; (igrep-find-define zgrep) ; M-x zgrep-find
165 ;; To search "*.[ch]" files by default in C mode:
166 ;; (put 'igrep-files-default 'c-mode
167 ;; (lambda () "*.[ch]"))
168 ;; To disable the default search regex and/or files pattern, except for
170 ;; (setq igrep-regex-default 'ignore)
171 ;; (setq igrep-files-default 'ignore)
172 ;; To avoid exceeding some shells' limit on command argument length
173 ;; (this only searches files in the current directory):
174 ;; (setq igrep-find t
175 ;; igrep-find-prune-clause "-type d \\! -name .")
178 ;; 1. Replace igrep-options with a table that maps igrep-program
179 ;; to the appropriate options, and/or support POSIX (egrep -> `grep -E`).
180 ;; 2. Generalize support for the -prune find clause (e.g. -fstype nfs).
181 ;; 3. Provide support for `glimpse`.
185 ;; Package interface:
187 (require 'custom) ; defgroup, defcustom
189 (require 'easymenu) ; easy-menu-define, easy-menu-add-item
191 (or (condition-case nil
192 (require 'grep) ; Emacs 22
194 (require 'compile)) ; compile-internal, grep-regexp-alist,
198 (require 'dired) ; dired-directory,
199 ; dired-get-filename,
200 ; dired-current-directory,
201 ; dired-get-marked-files,
202 ; dired-mark-get-files
203 (or (featurep 'ange-ftp)
206 (load-library "ange-ftp") ; ange-ftp-ftp-name
209 (load-library "efs") ; efs-ftp-path
213 (defconst igrep-version "2.113"
214 "This version of igrep.el.")
217 "An improved interface to `grep` and `find`."
222 (defcustom igrep-options nil
223 "*The options passed by `\\[igrep]' to `igrep-program', or nil.
225 \"-n\" will automatically be passed to `igrep-program', to generate the
226 output expected by `\\[next-error]' and `\\[compile-goto-error]'.
227 \"-e\" will automatically be passed to `igrep-program', if it supports
230 :type '(choice (const nil) (string)))
231 (put 'igrep-options 'variable-interactive
232 "xOptions (\"-xyz\" or nil): ")
234 (defcustom igrep-case-fold-search nil
235 "*If non-nil, `\\[igrep]' ignores case unless REGEX has uppercase letters."
238 (put 'igrep-case-fold-search 'variable-interactive
239 "XIgnore case? (t or nil): ")
241 (defcustom igrep-read-options nil
242 "*If non-nil, `\\[igrep]' always prompts for options;
243 otherwise, it only prompts when 1 or 3 `C-u's are given as a prefix arg."
246 (put 'igrep-read-options 'variable-interactive
247 "XAlways prompt for options? (t or nil): ")
249 (defcustom igrep-read-multiple-files nil
250 "*If non-nil, `\\[igrep]' always prompts for multiple-files;
251 otherwise, it only prompts when 2 or 3 `C-u's are given as a prefix arg."
254 (put 'igrep-read-multiple-files 'variable-interactive
255 "XAlways prompt for multiple files? (t or nil): ")
257 (defcustom igrep-regex-default 'current-word
258 "*If non-nil, a function that returns a default REGEX for `\\[igrep]'.
259 The function is called with no arguments and should return a string (or nil).
261 A different function can be specified for any particular mode by specifying
262 a value for that `major-mode' property; for example:
263 (put 'igrep-regex-default 'dired-mode
264 'igrep-dired-file-current-word)"
266 :type '(choice (const nil) (function)))
267 (put 'igrep-regex-default 'variable-interactive
268 "SProvide a default regex? (function or nil): ")
269 (put 'igrep-regex-default 'dired-mode
270 'igrep-dired-file-current-word)
272 (defcustom igrep-files-default 'igrep-buffer-file-name-pattern
273 "*If non-nil, a function that returns the default FILES for `\\[igrep]'.
274 The function is called with no arguments and should return a string,
275 or a list of strings (or nil).
277 A different function can be specified for any particular mode by specifying
278 a value for that `major-mode' property; for example:
279 (put 'igrep-files-default 'dired-mode
280 'igrep-dired-directory-file-pattern)"
282 :type '(choice (const nil) (function)))
283 (put 'igrep-files-default 'variable-interactive
284 "SProvide a default file name pattern? (function or nil): ")
285 (put 'igrep-files-default 'dired-mode
286 'igrep-dired-directory-file-pattern)
288 (defcustom igrep-verbose-prompts t
289 "*If t, `\\[igrep]' prompts for arguments verbosely;
290 if not t but non-nil, `\\[igrep]' prompts for arguments semi-verbosely;
291 if nil, `\\[igrep]' prompts for arguments tersely."
293 :type '(choice (const :tag "Verbose" t)
294 (other :tag "Semi-verbose" semi)
295 (const :tag "Terse" nil)))
296 (put 'igrep-verbose-prompts 'variable-interactive
297 "XPrompt verbosely? (t, 'semi, or nil): ")
299 (defcustom igrep-insert-default-directory nil
300 "*The value of `insert-default-directory' for `\\[igrep]'."
303 (put 'igrep-insert-default-directory 'variable-interactive
304 "XPrompt with directory in the minibuffer? (t or nil): ")
306 (defcustom igrep-insert-default-key
307 (if (< emacs-major-version 20) "\C-c\C-e")
308 "*The key used to insert the default argument in the minibuffer.
309 In Emacs 20, the default is available via the minibuffer history \
310 \(\\<minibuffer-local-map>\\[next-history-element])."
312 :type '(choice (const nil) (string) (vector))) ; key-binding
313 (put 'igrep-insert-default-key 'variable-interactive
314 "kSet key to insert the default `\\[igrep]' argument in the minibuffer: ")
316 (defcustom igrep-save-buffers 'query
317 "*If t, `\\[igrep]' first saves each modified file buffer;
318 if not t but non-nil, `\\[igrep]' offers to save each modified file buffer."
320 :type '(choice (const :tag "Save" t)
321 (other :tag "Query" query)
322 (const :tag "Don't Save" nil)))
323 (put 'igrep-save-buffers 'variable-interactive
324 "XSave modified buffers? (t, 'query, or nil): ")
326 (defcustom igrep-menu-bar t
327 "*If non-nil, enable the `igrep-menu' submenu on the \"Tools\" menu bar."
330 (put 'igrep-menu-bar 'variable-interactive
331 "XEnable menu bar? (t or nil): ")
335 (defsubst igrep-easy-menu-item (name callback help-keyword help-text)
336 "Return a [NAME CALLBACK HELP-KEYWORD HELP-TEXT] menu item.
337 See `easy-menu-define'."
338 (if (featurep 'xemacs) ; no :help keyword
339 (vector name callback)
340 (vector name callback help-keyword help-text)))
343 (unless (and (fboundp 'keywordp) (keywordp :help))
344 (defvar :help ':help))) ; Emacs 19
346 (defvar igrep-easy-menu
347 `("Search Files and Directories (igrep)"
348 ,@(cond ((featurep 'xemacs) '(:included igrep-menu-bar))
349 ((>= emacs-major-version 20) '(:active igrep-menu-bar))
352 ,(igrep-easy-menu-item "`grep` files..." 'igrep
353 :help "Search files for basic regex(5)s")
354 ,(igrep-easy-menu-item "`egrep` files..." 'egrep
355 :help "Search files for extended regex(5)s")
356 ,(igrep-easy-menu-item "`fgrep` files..." 'fgrep
357 :help "Search files for strings"))
358 ("Search directories"
359 ,(igrep-easy-menu-item "`find | grep` directories..." 'igrep-find
360 :help "Search directories for basic regex(5)s")
361 ,(igrep-easy-menu-item "`find | egrep` directories..." 'egrep-find
362 :help "Search directories for extended regex(5)s")
363 ,(igrep-easy-menu-item "`find | fgrep` directories..." 'fgrep-find
364 :help "Search directories for strings"))
366 ,(igrep-easy-menu-item "Search visited files..." 'igrep-visited-files
367 :help "Search visited files for basic regex(5)s"))
368 "If non-nil, the menu bar submenu of `igrep' commands.
369 See `easy-menu-define'.")
371 (defvar igrep-null-device
372 (cond ((boundp 'null-device) null-device) ; Emacs 20
373 ((boundp 'grep-null-device) grep-null-device)) ; Emacs 19
374 "The system null device.")
376 (defvar igrep-program "grep"
377 "The default program run by `\\[igrep]' and `\\[igrep-find]'.
378 It must accept a `grep` regex argument and one or more file names, plus
379 the \"-n\" option. If nil, `\\[igrep]' prompts for the program to run.")
381 (defvar igrep-regex-option
382 (if (equal (call-process igrep-program nil nil nil
383 "-e" "foo" igrep-null-device)
386 "If non-nil, the option used to specify the REGEX argument to `\\[igrep]'.
387 This protects an initial \"-\" from option processing.")
389 (defvar igrep-program-table ; referenced by igrep-use-zgrep
390 (let ((exec-directories exec-path)
391 (program-obarray (make-vector 11 0)))
392 (while exec-directories
393 (if (and (car exec-directories)
394 (file-directory-p (car exec-directories))
395 (file-readable-p (car exec-directories)))
397 (directory-files (car exec-directories)
398 nil "grep\\(\\.exe\\)?\\'")))
400 ;; Check `(file-executable-p (car grep-programs))'?
402 (string-match "\\.exe\\'" (car grep-programs)))
403 (intern (substring (car grep-programs) 0 -4) program-obarray)
404 (intern (car grep-programs) program-obarray))
405 (setq grep-programs (cdr grep-programs)))))
406 (setq exec-directories (cdr exec-directories)))
408 "An obarray of available `grep` programs.
409 This is passed by `igrep-read-program' to `completing-read' when
410 `igrep-program' is nil.")
412 (defvar igrep-use-zgrep
413 (if (intern-soft "zgrep" igrep-program-table)
415 "If t, `\\[igrep]' searches files using the GNU (gzip) `zPROGRAM` script;
416 If not t but non-nil, `\\[igrep]' searches compressed FILES using `zPROGRAM`;
417 if nil, `\\[igrep]' searches files with `PROGRAM`.")
419 (defvar igrep-find nil
420 "If non-nil, `\\[igrep]' searches directories using `find`.
423 (defvar igrep-find-program "find"
424 "The program run by `\\[igrep-find]'.")
426 (defvar igrep-find-prune-clause
427 (if (equal (call-process igrep-find-program nil nil nil
428 igrep-null-device "-prune")
430 (format "-type d %s -name RCS -o -name CVS -o -name SCCS -o -name .svn -o -name .hg -o -name .git %s"
431 (shell-quote-argument "(")
432 (shell-quote-argument ")")))
433 "The `find` clause used to prune directories, or nil;
436 (defvar igrep-find-file-clause
437 (format "-type f %s -name %s %s -name %s %s -name %s %s -name %s" ; -type l
438 (shell-quote-argument "!")
439 (shell-quote-argument "*~") ; Emacs backup
440 (shell-quote-argument "!")
441 (shell-quote-argument "*,v") ; RCS file
442 (shell-quote-argument "!")
443 (shell-quote-argument "s.*") ; SCCS file
444 (shell-quote-argument "!")
445 (shell-quote-argument ".#*")) ; CVS file
446 "The `find` clause used to filter files passed to `grep`, or nil;
449 (defvar igrep-find-use-xargs
450 (cond ((not (equal (call-process "xargs" nil nil nil "-e") 0))
452 ((equal (call-process igrep-find-program nil nil nil
453 igrep-null-device "-print0")
457 "Whether `\\[igrep-find]' uses the `xargs` program or not.
458 If `gnu', it executes
459 `find ... -print0 | xargs -0 -e grep ...`;
460 if not `gnu' but non-nil, it executes
461 `find ... -print | xargs -e grep ...`;
463 `find ... -exec grep ...`.")
465 (defvar igrep-program-default "grep"
466 "The default `grep` program.
467 This is passed by `igrep-read-program' to `completing-read' when
468 `igrep-program' is nil.")
470 ;; Internal variables:
472 (defvar igrep-regex-history '()
473 "The minibuffer history list for `\\[igrep]'s REGEX argument.")
475 (defvar igrep-files-history '()
476 "The minibuffer history list for `\\[igrep]'s FILES argument.")
481 (defun igrep-insinuate (&optional override)
482 "Define `grep' aliases for the corresponding `igrep' commands.
483 With a prefix arg, OVERRIDE the current `grep' command definitions."
486 (defalias 'grep 'igrep)
487 (defadvice grep (around igrep-interactive first (&rest command-args)
489 "If called interactively, use the `\\[igrep]' interface instead,
490 where COMMAND-ARGS is (PROGRAM REGEX FILES [OPTIONS]); if called
491 programmatically, COMMAND-ARGS is still (COMMAND)."
492 (interactive (igrep-read-args))
494 (apply 'igrep command-args)
496 (if (or (not (fboundp 'grep-find))
498 (defalias 'grep-find 'igrep-find))
499 (if (or (not (fboundp 'dired-do-grep))
501 (defalias 'dired-do-grep 'dired-do-igrep))
502 (if (or (not (fboundp 'dired-do-grep-find))
504 (defalias 'dired-do-grep-find 'dired-do-igrep-find))
505 (if (or (not (fboundp 'Buffer-menu-grep))
507 (defalias 'Buffer-menu-grep 'Buffer-menu-igrep)))
509 (defsubst igrep-quote-file-name (file)
510 "Quote FILE name pattern for `shell-file-name'."
511 (if (fboundp 'shell-quote-wildcard-pattern) ; Emacs 21
512 (shell-quote-wildcard-pattern file)
513 (shell-quote-argument file)))
516 (defun igrep (program regex files &optional options)
517 "*Run `grep` PROGRAM to match REGEX in FILES.
518 The output is displayed in the *igrep* buffer, which `\\[next-error]' and
519 `\\[compile-goto-error]' parse to find each line of matched text.
521 PROGRAM may be nil, in which case it defaults to `igrep-program'.
523 REGEX is automatically quoted by `shell-quote-argument'.
525 FILES is either a file name pattern (automatically quoted by
526 `shell-quote-wildcard-pattern', then expanded by the `shell-file-name' shell),
527 or a list of file name patterns.
529 Optional OPTIONS is also passed to PROGRAM; it defaults to `igrep-options'.
531 If a prefix argument \
532 \(`\\[universal-argument]') \
533 is given when called interactively,
534 or if `igrep-read-options' is set, OPTIONS is read from the minibuffer.
536 If two prefix arguments \
537 \(`\\[universal-argument] \\[universal-argument]') \
538 are given when called interactively,
539 or if `igrep-read-multiple-files' is set, FILES is read from the minibuffer
542 If three prefix arguments \
543 \(`\\[universal-argument] \\[universal-argument] \\[universal-argument]') \
544 are given when called interactively,
545 or if `igrep-read-options' and `igrep-read-multiple-files' are set,
546 OPTIONS is read and FILES is read multiple times.
548 If `igrep-find' is non-nil, the directory or directories
549 containing FILES is recursively searched for files whose name matches
550 the file name component of FILES (and whose contents match REGEX)."
554 (setq program (or igrep-program "grep")))
556 (setq options igrep-options))
557 (if (not (listp files)) ; (stringp files)
558 (setq files (list files)))
559 (if (and (member ?~ (mapcar 'string-to-char files))
561 (string-match "\\`[rj]?sh\\(\\.exe\\)?\\'"
562 (file-name-nondirectory shell-file-name))))
563 ;; (restricted, job-control, or standard) Bourne shell doesn't expand ~:
565 (mapcar 'expand-file-name files)))
566 (let* ((use-zgrep (cond ((eq igrep-use-zgrep t))
570 (while (and files (not compressed-p))
572 (string-match "\\.g?[zZ]\\'" (car files)))
573 (setq compressed-p t))
574 (setq files (cdr files)))
577 (command (format "%s -n %s %s %s %s %s"
580 (not (string-match "\\`z" program))))
581 (setq program (concat "z" program))
584 (and igrep-case-fold-search
585 (equal regex (downcase regex))
588 (or igrep-regex-option
591 (string-match "\\`-" regex))
592 (setq regex (concat "\\" regex)))
594 (shell-quote-argument regex)
596 (if igrep-find-use-xargs
598 (shell-quote-argument "{}"))
599 (mapconcat (lambda (file)
600 (let ((dir (file-name-directory file)))
603 (file-name-nondirectory file)
604 (igrep-quote-file-name dir))
610 (igrep-format-find-command command files)))
611 (cond ((eq igrep-save-buffers t) (save-some-buffers t))
612 (igrep-save-buffers (save-some-buffers)))
613 (if (fboundp 'compilation-start) ; Emacs 22
614 (let ((compilation-process-setup-function 'grep-process-setup))
615 (or (fboundp 'igrep-mode)
616 (define-derived-mode igrep-mode grep-mode "Igrep"))
617 (compilation-start command
620 (cond ((eq compilation-highlight-regexp t))
621 (compilation-highlight-regexp
622 (if (eq program "fgrep")
625 (compile-internal command (format "No more %s matches" program)
626 "Igrep" nil grep-regexp-alist))))
628 ;; Analogue commands:
630 (defmacro igrep-define (analogue-command &rest igrep-bindings)
631 "Define ANALOGUE-COMMAND as an `igrep' analogue command.
632 Optional (VARIABLE VALUE) arguments specify the temporary IGREP-BINDINGS
634 ;; (interactive "SCommand: ") ; C-u => read bindings?
635 (let ((analogue-program (symbol-name analogue-command)))
636 `(defun ,analogue-command (&rest igrep-args)
637 ,(format "*Run `%s` via `\\[igrep]'.
638 All arguments (including prefix arguments, when called interactively)
639 are handled by `igrep'."
642 (let ((igrep-program (if igrep-program ,analogue-program))
643 (igrep-program-default ,analogue-program))
645 (let (,@ igrep-bindings)
647 (cond ((interactive-p) (car igrep-args))
649 (t ,analogue-program))
650 (cdr igrep-args))))))
655 (igrep-use-zgrep nil)
656 (igrep-regex-option "-e"))
658 ;; Recursive (`find`) commands:
661 (defun igrep-find (&rest igrep-args)
662 "*Run `grep` via `find`; see `igrep' and `igrep-find'.
663 All IGREP-ARGS (including prefix arguments, when called interactively)
664 are handled by `igrep'."
666 (let ((igrep-find t))
668 (let ((igrep-find t))
669 (apply 'igrep igrep-args)))
671 ;; Analogue recursive (`find`) commands:
673 (defmacro igrep-find-define (analogue-command &rest igrep-bindings)
674 "Define ANALOGUE-COMMAND-find as an `igrep' analogue `find` command.
675 Optional (VARIABLE VALUE) arguments specify the temporary IGREP-BINDINGS
677 ;; (interactive "SCommand: ") ; C-u => read bindings?
678 (let ((analogue-program (symbol-name analogue-command)))
679 (setq analogue-command
680 (intern (format "%s-find" analogue-command)))
681 `(defun ,analogue-command (&rest igrep-args)
682 ,(format "*Run `%s` via `\\[igrep-find]'.
683 All arguments (including prefix arguments, when called interactively)
684 are handled by `igrep'."
687 (let ((igrep-program (if igrep-program ,analogue-program))
688 (igrep-program-default ,analogue-program)
691 (let (,@ igrep-bindings)
693 (cond ((interactive-p) (car igrep-args))
695 (t ,analogue-program))
696 (cdr igrep-args))))))
698 (igrep-find-define egrep)
699 (igrep-find-define fgrep)
700 (igrep-find-define agrep
701 (igrep-use-zgrep nil)
702 (igrep-regex-option "-e"))
705 (defun igrep-visited-files (program regex &optional options)
706 "*Run `grep` PROGRAM to match REGEX (with optional OPTIONS) \
707 on all visited files.
710 (let ((igrep-args (igrep-read-args 'no-files)))
712 (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
715 (let ((directory-abbrev-alist
716 (cons (cons (regexp-quote (expand-file-name default-directory))
718 directory-abbrev-alist)))
719 (mapcar 'abbreviate-file-name
721 (mapcar (lambda (buffer)
722 (let ((file (buffer-file-name buffer)))
724 (cond ((featurep 'ange-ftp)
725 (not (ange-ftp-ftp-name file)))
727 (not (efs-ftp-path file)))
729 ;; (file-exists-p file)
738 (defun dired-do-igrep (program regex &optional options arg)
739 "*Search the marked (or next prefix ARG) files.
740 See `\\[igrep]' for a description of PROGRAM, REGEX, and OPTIONS."
743 (let ((current-prefix-arg nil))
744 (igrep-read-args 'no-files))))
746 (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
748 (nconc igrep-args (list current-prefix-arg))))
750 (funcall (cond ((fboundp 'dired-get-marked-files) ; GNU Emacs
751 'dired-get-marked-files)
752 ((fboundp 'dired-mark-get-files) ; XEmacs
753 'dired-mark-get-files))
757 ;; Dired recursive (`find`) commands:
760 (defun dired-do-igrep-find (program regex &optional options arg)
761 "*Run `grep` on the marked (or next prefix ARG) directories.
762 See `\\[igrep]' for a description of PROGRAM, REGEX, and OPTIONS."
765 (let ((current-prefix-arg nil)
767 (igrep-read-args 'no-files))))
769 (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
771 (nconc igrep-args (list current-prefix-arg))))
772 (let ((igrep-find t))
773 (dired-do-igrep program regex options arg)))
775 ;; Buffer menu commands:
778 (defun Buffer-menu-igrep (program regex &optional options)
779 "*Run `grep` on the files visited in buffers marked with '>'.
780 See `\\[igrep]' for a description of PROGRAM, REGEX, and OPTIONS."
782 (let ((igrep-args (igrep-read-args 'no-files)))
784 (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
786 ;; See Buffer-menu-select:
787 (let ((marked-files '())
790 (goto-char (point-min))
791 (while (search-forward "\n>" nil t)
792 (setq marked-buffer (Buffer-menu-buffer t)
793 file (buffer-file-name marked-buffer))
796 (cond ((featurep 'ange-ftp)
797 (not (ange-ftp-ftp-name file)))
799 (not (efs-ftp-path file)))
801 (setq marked-files (cons file marked-files)))
802 ;;; (let ((buffer-read-only nil))
806 (setq marked-files (nreverse marked-files))
808 (let ((directory-abbrev-alist
809 (cons (cons (regexp-quote (expand-file-name default-directory))
811 directory-abbrev-alist)))
812 (mapcar 'abbreviate-file-name marked-files))
817 (defun igrep-dired-file-current-word ()
818 "Return the current word in the file on this line, if it is visible;
819 else, return the file name on this line, if there is one;
820 otherwise, return the current word."
822 (dired-get-filename t t))
825 (get-file-buffer (expand-file-name dired-file))))
826 (dired-file-buffer-window
827 (if dired-file-buffer
828 (get-buffer-window dired-file-buffer))))
829 (cond (dired-file-buffer-window (save-excursion
830 (set-buffer dired-file-buffer)
833 (t (current-word)))))
835 (defun igrep-buffer-file-name-pattern ()
836 "Return a shell file name pattern based on the visited file name.
837 If the `buffer-file-name' variable is nil, return \"*\"."
838 ;; (Based on other-possibly-interesting-files in ~/as-is/unix.el, by
839 ;; Wolfgang Rupprecht <wolfgang@mgm.mit.edu>.)
841 (let ((file-name (file-name-nondirectory buffer-file-name)))
844 (if (string-match "\\.[^.]+\\(\\.g?[zZ]\\)?\\'"
846 (substring file-name (match-beginning 0)
850 (defun igrep-dired-directory-file-pattern ()
851 "Return a shell file name pattern based on `dired-directory', or \"*\"."
852 (cond ((stringp dired-directory)
853 (if (file-directory-p dired-directory)
855 (file-name-nondirectory dired-directory))) ; wildcard
856 ((consp dired-directory) ; (DIR FILE ...)
857 (mapconcat 'identity (cdr dired-directory) " "))))
861 (defsubst igrep-file-directory (name)
862 "Return the directory component of NAME, or \".\" if it has none."
863 (directory-file-name (or (file-name-directory name)
864 (file-name-as-directory "."))))
866 (defsubst igrep-file-pattern (name)
867 "Return the file component of NAME, or \"*\" if it has none."
868 (let ((pattern (file-name-nondirectory name)))
869 (if (string= pattern "")
873 (defun igrep-format-find-command (command files)
874 "Format `grep` COMMAND to be invoked via `find` on FILES."
875 (let ((directories '())
878 (let ((dir (igrep-file-directory (car files)))
879 (pat (igrep-file-pattern (car files))))
880 (if (and (not (string= dir "."))
881 (file-symlink-p dir))
882 (setq dir (concat dir "/.")))
883 (if (not (member dir directories))
884 (setq directories (cons dir directories)))
885 (cond ((equal pat "*")
887 ((and (listp patterns)
888 (not (member pat patterns)))
889 (setq patterns (cons pat patterns)))))
890 (setq files (cdr files)))
891 (format (cond ((eq igrep-find-use-xargs 'gnu)
893 "%s %s %s %s %s -print0 | xargs -0 -e %s")
894 (igrep-find-use-xargs
896 "%s %s %s %s %s -print | xargs -e %s")
898 "%s %s %s %s %s -exec %s %s"))
900 (mapconcat 'igrep-quote-file-name (nreverse directories)
902 (if igrep-find-prune-clause
903 (format "%s -prune -o" igrep-find-prune-clause)
905 (or igrep-find-file-clause "")
907 (if (cdr patterns) ; (> (length patterns) 1)
909 (shell-quote-argument "(")
910 (mapconcat (lambda (pat)
912 (shell-quote-argument pat)))
915 (shell-quote-argument ")"))
916 (format "-name %s" (shell-quote-argument (car patterns))))
919 (shell-quote-argument ";")
922 (defmacro igrep-default-arg (variable)
923 "Return the default arg based on VARIABLE."
925 (cond ((get (quote ,variable) major-mode)
926 (funcall (get (quote ,variable) major-mode)))
927 (t (funcall ,variable)))))
929 (defun igrep-default-regex ()
930 "Return the default REGEX for `\\[igrep]'."
931 (let ((default-regex (igrep-default-arg igrep-regex-default)))
932 (if (not (equal default-regex ""))
935 (defun igrep-default-files ()
936 "Return the default FILES for `\\[igrep]'."
937 (let* ((dired-subdirectory (if (cond ((fboundp 'derived-mode-p) ; Emacs 21
938 (derived-mode-p 'dired-mode))
939 (t (eq major-mode 'dired-mode)))
940 (dired-current-directory t)))
941 (default-files (igrep-default-arg igrep-files-default)))
942 (if (not (listp default-files)) ; stringp
943 (setq default-files (list default-files)))
944 (if dired-subdirectory
945 (mapcar (lambda (file)
946 (concat dired-subdirectory file))
950 (defsubst igrep-prefix (prefix string &rest strings)
951 "Concatenate PREFIX (if non-nil), STRING, and any other STRINGS."
952 (if (or prefix strings)
953 (apply 'concat prefix string strings)
956 (defun igrep-read-args (&optional no-files)
957 "Read and return a list: (PROGRAM REGEX FILES OPTIONS).
958 If NO-FILES is non-nil, then FILES is not read and nil is returned
960 (let* ((pre-prefix (if (and igrep-find (eq igrep-verbose-prompts t))
963 (igrep-read-program pre-prefix))
964 (prefix (if (and program (eq igrep-verbose-prompts t))
965 (igrep-prefix pre-prefix program " ")
968 (igrep-read-options prefix))
969 (post-prefix (if (and options (eq igrep-verbose-prompts t))
970 (igrep-prefix prefix options " ")
973 (igrep-read-regex post-prefix)
975 (igrep-read-files post-prefix))
978 (defun igrep-read-program (&optional prompt-prefix)
979 "Read and return a `grep` program name from the minibuffer.
980 If `igrep-program' is non-nil, it.
982 Optional PROMPT-PREFIX is prepended to the \"Program: \" prompt."
984 (let ((prompt "Program: "))
985 (completing-read (igrep-prefix prompt-prefix prompt) igrep-program-table
986 nil t igrep-program-default))))
988 (defun igrep-read-options (&optional prompt-prefix)
989 "Read and return an options string from the minibuffer.
990 If `current-prefix-arg' is '(4) or '(64), return `igrep-options'.
992 Optional PROMPT-PREFIX is prepended to the \"Options: \" prompt."
993 (if (or igrep-read-options
994 (and (consp current-prefix-arg)
995 (memq (prefix-numeric-value current-prefix-arg)
997 (let ((prompt "Options: "))
998 (read-string (igrep-prefix prompt-prefix prompt)
999 (or igrep-options "-")))
1002 (defun igrep-read-regex (&optional prompt-prefix)
1003 "Read and return a `grep` regex(5) string from the minibuffer.
1004 Optional PROMPT-PREFIX is prepended to the \"Regex: \" prompt."
1005 (if igrep-insert-default-key
1006 (define-key minibuffer-local-map igrep-insert-default-key
1007 'igrep-insert-default-regex))
1008 (let* ((default-regex (igrep-default-regex))
1009 (prompt (igrep-prefix prompt-prefix
1011 (format "Regex [default: %s]: "
1014 (regex (cond ((featurep 'xemacs) ; incompatible
1015 ;; DEFAULT-VALUE is the 7th arg in 21.4 (but 21.1
1016 ;; only accepts 6 args):
1017 (read-from-minibuffer prompt
1019 'igrep-regex-history
1020 nil)) ; ABBREV-TABLE
1021 ((>= emacs-major-version 20)
1022 (read-from-minibuffer prompt
1024 'igrep-regex-history
1027 (read-from-minibuffer prompt
1029 'igrep-regex-history)))))
1030 (if (equal regex "")
1032 (or (equal default-regex (car igrep-regex-history))
1033 (setq igrep-regex-history
1034 (cons default-regex igrep-regex-history)))
1038 (defun igrep-insert-default-regex (&optional clear-minibuffer)
1039 "*Insert the default regex in the minibuffer.
1040 If a prefix argument is specified, CLEAR-MINIBUFFER contents first."
1042 (if clear-minibuffer
1043 (delete-region (if (fboundp 'minibuffer-prompt-end) ; Emacs 21
1044 (minibuffer-prompt-end)
1047 (insert (or (save-excursion
1048 (set-buffer (window-buffer minibuffer-scroll-window))
1049 (igrep-default-regex))
1052 (defun igrep-insert-default-files (&optional clear-minibuffer)
1053 "*Insert the default files in the minibuffer.
1054 If a prefix argument is specified, CLEAR-MINIBUFFER contents first."
1056 (if clear-minibuffer
1057 (delete-region (if (fboundp 'minibuffer-prompt-end) ; Emacs 21
1058 (minibuffer-prompt-end)
1061 (insert (mapconcat 'identity
1063 (set-buffer (window-buffer minibuffer-scroll-window))
1064 (igrep-default-files))
1067 (defsubst igrep-default-key (command &optional keymap key)
1068 "Return the key bound to COMMAND in KEYMAP, preferably KEY."
1070 (setq keymap (current-global-map)))
1072 (eq (lookup-key keymap key) command))
1074 (where-is-internal command keymap t)))
1076 (defun igrep-read-files (&optional prompt-prefix)
1077 "Read and return a file name pattern from the minibuffer.
1078 If `current-prefix-arg' is '(16) or '(64), read multiple file name
1079 patterns and return them in a list. Optional PROMPT-PREFIX is
1080 prepended to the \"File(s): \" prompt."
1081 (let* ((default-files (igrep-default-files))
1082 (default-files-string (mapconcat 'identity default-files " "))
1083 (insert-default-directory igrep-insert-default-directory)
1084 (file (igrep-read-file-name
1085 (igrep-prefix prompt-prefix
1087 (format "File(s) [default: %s]: "
1088 default-files-string)
1090 nil (if default-files default-files-string "") nil nil
1091 'igrep-files-history))
1092 (files (list file)))
1093 (if (or igrep-read-multiple-files
1094 (and (consp current-prefix-arg)
1095 (memq (prefix-numeric-value current-prefix-arg)
1097 (let* ((key (igrep-default-key 'exit-minibuffer
1098 minibuffer-local-completion-map
1101 (igrep-prefix prompt-prefix
1102 (if igrep-verbose-prompts
1103 (format "File(s): [Type `%s' when done] "
1104 (key-description key))
1106 (while (and (setq file
1107 (igrep-read-file-name prompt
1109 'igrep-files-history))
1110 (not (equal file "")))
1111 (setq files (cons file files)))))
1112 (mapcar (lambda (file)
1113 (if (file-directory-p file)
1114 ;; really should map expand-file-name over default-files:
1115 (expand-file-name (if default-files default-files-string "*")
1120 (defun igrep-read-file-name (prompt
1121 &optional directory default existing initial history)
1122 "Just like `read-file-name,' but with optional HISTORY."
1123 (if igrep-insert-default-key
1124 (define-key minibuffer-local-completion-map igrep-insert-default-key
1125 'igrep-insert-default-files))
1127 (let ((file-name-history (symbol-value history)))
1128 (prog1 (read-file-name prompt directory default existing initial)
1129 (set history file-name-history)))
1130 (read-file-name prompt directory default existing initial)))
1136 (easy-menu-define igrep-menu nil
1137 "Menu keymap for igrep."
1139 (cond ((fboundp 'add-submenu) ; XEmacs
1140 (add-submenu '("Tools") igrep-menu "Grep..."))
1141 ((fboundp 'easy-menu-add-item) ; Emacs 20
1142 (easy-menu-add-item menu-bar-tools-menu nil igrep-menu
1145 (define-key-after menu-bar-tools-menu [igrep]
1146 (cons (car igrep-easy-menu) igrep-menu)
1147 (and (lookup-key menu-bar-tools-menu [grep]) 'grep))))))
1149 ;;; Local Variables:
1150 ;;; eval: (put 'igrep-define 'lisp-indent-function 1)
1151 ;;; eval: (put 'igrep-find-define 'lisp-indent-function 1)
1152 ;;; eval: (put 'easy-menu-define 'lisp-indent-function 'defun)
1157 ;;; igrep.el ends here