Mercurial > hg > xemacs-beta
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/packages/igrep.el Mon Aug 13 08:51:03 2007 +0200 @@ -0,0 +1,800 @@ +;;;; igrep.el --- An improved interface to `grep`. + +;;; Description: +;;; +;;; Define the \[igrep] command, which is like \[grep] except that it +;;; takes three required arguments (PROGRAM, EXPRESSION, and FILES) and +;;; an optional argument (OPTIONS) instead of just one argument (COMMAND). +;;; Also define the analogous \[egrep] and \[fgrep] commands for convenience. +;;; +;;; Define the \[igrep-find] command, which is like \[igrep] except that +;;; it uses `find` to recursively `grep` a directory. Also define the +;;; analogous \[egrep-find] and \[fgrep-find] commands for convenience. +;;; +;;; \[igrep] and \[igrep-find] (and their analogues) provide defaults +;;; for the EXPRESSION and FILES arguments when called interactively, +;;; and there are global variables that control the syntax of the `grep` +;;; and `find` shell commands that are executed. A user option controls +;;; whether the corresponding GNU (gzip) "zPROGRAM" script is used, to +;;; `grep` compressed files. +;;; +;;; \[agrep] and \[agrep-find] are also defined as convenient interfaces +;;; to the approximate `grep` utility, which is distributed with the +;;; `glimpse' indexing and query tool (available from +;;; <URL:http://glimpse.cs.arizona.edu:1994/>). +;;; +;;; \[grep] itself is advised to provide the \[igrep] interface when +;;; called interactively (when called programmatically, it still uses +;;; the original argument list). \[grep-find] is defined as an alias +;;; for \[igrep-find]. +;;; +;;; When run interactively from Dired mode, the various \[igrep] +;;; commands provide defaults for the EXPRESSION and FILES arguments +;;; that are based on the visited directory (including any inserted +;;; subdirectories) and the current file. In addition, the +;;; \[dired-do-igrep] and \[dired-do-igrep-find] commands are defined +;;; that respect the `dired-do-*' command calling conventions: a prefix +;;; argument is interpreted as the number of succeeding files to `grep`, +;;; otherwise all the marked files are `grep`ed. \[dired-do-grep] and +;;; \[dired-do-grep-find] are defined as aliases for \[dired-do-igrep] +;;; and \[dired-do-igrep-find]. + +;;; Copyright: +;;; +;;; Copyright © 1994,1995,1996,1997 Kevin Rodgers +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2 of the License, or +;;; at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +;;; +;;; Neither my former nor current employer (Martin Marietta and +;;; Information Handling Services, respectively) has disclaimed any +;;; copyright interest in igrep.el. +;;; +;;; Kevin Rodgers <kevinr@ihs.com> Project Engineer +;;; Information Handling Services Electronic Systems Development +;;; 15 Inverness Way East, M/S A201 +;;; Englewood CO 80112 USA (303)397-2807[voice]/-2779[fax] + +;;; Installation: +;;; +;;; 1. Put this file in a directory that is a member of load-path, and +;;; byte-compile it (e.g. with `M-x byte-compile-file') for better +;;; performance. You can ignore any warnings about references to free +;;; variables and "not known to be defined" functions. +;;; 2. Put these forms in default.el or ~/.emacs: +;;; (autoload (function igrep) "igrep" +;;; "*Run `grep` PROGRAM to match EXPRESSION in FILES..." t) +;;; (autoload (function igrep-find) "igrep" +;;; "*Run `grep` via `find`..." t) +;;; (autoload (function dired-do-igrep) "igrep" +;;; "*Run `grep` on the marked (or next prefix ARG) files." t) +;;; (autoload (function dired-do-igrep-find) "igrep" +;;; "*Run `grep` via `find` on the marked (or next prefix ARG) directories." t) +;;; 2. a. For completeness, you can add these forms as well: +;;; (autoload (function grep) "igrep" +;;; "*Run `grep` PROGRAM to match EXPRESSION in FILES..." t) +;;; (autoload (function egrep) "igrep" +;;; "*Run `egrep`..." t) +;;; (autoload (function fgrep) "igrep" +;;; "*Run `fgrep`..." t) +;;; (autoload (function agrep) "igrep" +;;; "*Run `agrep`..." t) +;;; (autoload (function grep-find) "igrep" +;;; "*Run `grep` via `find`..." t) +;;; (autoload (function egrep-find) "igrep" +;;; "*Run `egrep` via `find`..." t) +;;; (autoload (function fgrep-find) "igrep" +;;; "*Run `fgrep` via `find`..." t) +;;; (autoload (function agrep-find) "igrep" +;;; "*Run `agrep` via `find`..." t) +;;; (autoload (function dired-do-grep) "igrep" +;;; "*Run `grep` on the marked (or next prefix ARG) files." t) +;;; (autoload (function dired-do-grep-find) "igrep" +;;; "*Run `grep` via `find` on the marked (or next prefix ARG) directories." t) +;;; 3. If you are running Windows 95/NT, you should install findutils +;;; and grep from release 17.1 (or higher) of the Cygnus GNU-Win32 +;;; distribution. See <URL:http://www.cygnus.com/misc/gnu-win32/>. + +;;; Usage: +;;; +;;; M-x igrep M-x igrep-find +;;; M-x grep M-x grep-find +;;; M-x egrep M-x egrep-find +;;; M-x fgrep M-x fgrep-find +;;; M-x agrep M-x agrep-find +;;; M-x dired-do-igrep M-x dired-do-igrep-find +;;; M-x dired-do-grep M-x dired-do-grep-find +;;; (Each of the 10 igrep commands accepts 1, 2, or 3 `C-u' prefix arguments. +;;; The 2 Dired commands interpret a prefix argument like the regular `dired-do' +;;; commands.) +;;; C-x ` (M-x next-error) C-c C-c (M-x compile-goto-error) [in *igrep*] + +;;; Customization examples: +;;; +;;; To ignore case by default: +;;; (setq igrep-options "-i") +;;; To search subdirectories by default: +;;; (setq igrep-find t) +;;; To search files with the GNU (gzip) zgrep script: +;;; (setq igrep-use-zgrep t) +;;; or define new igrep commands (this works for zegrep and zfgrep as well): +;;; (igrep-define zgrep) ; M-x zgrep +;;; (igrep-find-define zgrep) ; M-x zgrep-find +;;; To avoid exceeding some shells' limit on command argument length +;;; (this only works when grep'ing files in the current directory): +;;; (setq igrep-find t +;;; igrep-find-prune-option "\\! -name .") + +;;; To do: +;;; +;;; 1. Delete the *-program variables (except for igrep-program) and +;;; replace igrep-options with a table that maps igrep-program to the +;;; appropriate options. +;;; 2. Generalize support for the -prune find clause (e.g. -fstype nfs). +;;; 3. Provide support for `glimpse`. +;;; 4. Add a menu interface. + +;;; LCD Archive Entry: +;;; +;;; igrep|Kevin Rodgers|kevinr@ihs.com| +;;; An improved interface to grep/egrep/fgrep; plus recursive `find` versions.| +;;; 97/02/10|2.56|~/misc/igrep.el.Z| + + +;;; Package interface: + +(provide 'igrep) + +(require 'backquote) ; igrep-with-default-in-history +(require 'compile) ; compile-internal and grep-regexp- + ; alist + +(defconst igrep-version "2.56" + "Version of igrep.el") + + +;;; User options: + +(defvar igrep-options nil + "*The options passed by \\[igrep] to `igrep-program', or nil. + +`-n' will automatically be passed to `igrep-program', to generate the +output expected by \\[next-error] and \\[compile-goto-error]. +`-e' will automatically be passed to `igrep-program', if it supports +that option.") + +(defvar igrep-read-options nil + "*If non-nil, `\\[igrep]' always prompts for options; +otherwise, it only prompts when 1 or 3 `C-u's are given as a prefix arg.") + +(defvar igrep-read-multiple-files nil + "*If non-nil, `\\[igrep]' always prompts for multiple-files; +otherwise, it only prompts when 2 or 3 `C-u's are given as a prefix arg.") + +(defvar igrep-verbose-prompts t + "*If t, \\[igrep] prompts for arguments verbosely; +if not t but non-nil, \\[igrep] prompts for arguments semi-verbosely; +if nil, \\[igrep] prompts for arguments tersely.") + +(defvar igrep-save-buffers 'query + "*If t, \\[igrep] first saves each modified file buffer; +if not t but non-nil, \\[igrep] offers to save each modified file buffer.") + +(defvar igrep-program-table ; referenced by igrep-use-zgrep + (let ((exec-directories exec-path) + (program-obarray (make-vector 11 0))) + (while exec-directories + (if (and (car exec-directories) + (file-directory-p (car exec-directories))) + (let ((grep-programs + (directory-files (car exec-directories) + nil "grep\\(\\.exe\\)?\\'"))) + (while grep-programs + ;; Check `(file-executable-p (car grep-programs))'? + (if (save-match-data + (string-match "\\.exe\\'" (car grep-programs))) + (intern (substring (car grep-programs) 0 -4) program-obarray) + (intern (car grep-programs) program-obarray)) + (setq grep-programs (cdr grep-programs))))) + (setq exec-directories (cdr exec-directories))) + program-obarray) + "An obarray of available `grep` programs, passed by `igrep-read-program' +to `completing-read' when `igrep-program' is nil.") + +(defvar igrep-use-zgrep + (if (intern-soft "zgrep" igrep-program-table) + 'files) + "If t, \\[igrep] searches files using the GNU (gzip) `zPROGRAM` script; +If not t but non-nil, \\[igrep] searches compressed FILES using `zPROGRAM`; +if nil, \\[igrep] searches files with `PROGRAM`.") + + +;;; User variables: + +(defvar igrep-program "grep" + "The default shell program run by \\[igrep] and \\[igrep-find]. +It must take a `grep` expression argument and one or more file names. +If nil, \\[igrep] prompts for the program to run.") + +(defvar igrep-expression-quote-char + (if (memq system-type '(ms-dos windows-95 windows-nt emx)) + ?\" + ?') + "The character used to delimit the EXPRESSION argument to \\[igrep], +to protect it from shell filename expansion.") + +(defvar igrep-expression-option + (if (or (eq system-type 'berkeley-unix) + (save-match-data + (string-match "-sco" system-configuration))) + "-e") + "If non-nil, the option used to specify the EXPRESSION argument to \\[igrep], +to protect an initial `-' from option processing.") + +(defvar igrep-parenthesis-escape-char + (if (memq system-type '(ms-dos windows-95 windows-nt emx)) + nil + ?\\) + "If non-nil, the character used by \\[igrep] to escape parentheses, +to protect them from shell interpretation.") + +(defvar igrep-find nil + "If non-nil, \\[igrep] searches directories using `find`. +See \\[igrep-find].") + +(defvar igrep-find-prune-options + (if (not (save-match-data + (string-match "-sco" system-configuration))) + "-name SCCS -o -name RCS") + "The `find` clause used to prune directories, or nil; +see \\[igrep-find].") + +(defvar igrep-find-file-options "-type f -o -type l" + "The `find` clause used to filter files passed to `grep`, or nil; +see \\[igrep-find].") + +(defvar igrep-find-use-xargs + (if (equal (call-process "find" nil nil nil + (if (boundp 'grep-null-device) + grep-null-device + "/dev/null") + "-print0") + 0) + 'gnu) + "If `gnu', \\[igrep-find] executes + `find ... -print0 | xargs -0 -e grep ...`; +if not `gnu' but non-nil, \\[igrep-find] executes + `find ... -print | xargs -e grep ...`; +if nil, \\[igrep-find] executes + `find ... -exec grep ...`.") + +(defvar igrep-program-default nil + "The default `grep` program, passed by `igrep-read-program' +to `completing-read' when `igrep-program' is nil.") + +(defvar igrep-expression-history '() + "The minibuffer history list for \\[igrep]'s EXPRESSION argument.") + +(defvar igrep-files-history '() + "The minibuffer history list for \\[igrep]'s FILES argument.") + + +;;; Commands: + +;; ;;;###autoload Not ready to replace compile.el's grep yet. -sb +(defadvice grep (around igrep-interface first (&rest grep-args) activate) + "If called interactively, use the \\[igrep] interface instead, +where GREP-ARGS is (PROGRAM EXPRESSION FILES OPTIONS); +if called programmatically, GREP-ARGS is still (COMMAND)." + (interactive (igrep-read-args)) + (if (interactive-p) + (apply (function igrep) grep-args) + ad-do-it)) + +(defalias 'grep-find 'igrep-find) + +;;;###autoload +(defun igrep (program expression files &optional options) + "*Run `grep` PROGRAM to match EXPRESSION in FILES. +The output is displayed in the *igrep* buffer, which \\[next-error] and +\\[compile-goto-error] parse to find each line of matched text. + +PROGRAM may be nil, in which case it defaults to `igrep-program'. + +EXPRESSION is automatically delimited by `igrep-expression-quote-char'. + +FILES is either a file name pattern (expanded by the shell named by +`shell-file-name') or a list of file name patterns. + +Optional OPTIONS is also passed to PROGRAM; it defaults to `igrep-options'. + +If a prefix argument \ +\(\\[universal-argument]\) \ +is given when called interactively, +or if `igrep-read-options' is set, OPTIONS is read from the minibuffer. + +If two prefix arguments \ +\(\\[universal-argument] \\[universal-argument]\) \ +are given when called interactively, +or if `igrep-read-multiple-files' is set, FILES is read from the minibuffer +multiple times. + +If three prefix arguments \ +\(\\[universal-argument] \\[universal-argument] \\[universal-argument]\) \ +are given when called interactively, +or if `igrep-read-options' and `igrep-read-multiple-files' are set, +OPTIONS is read and FILES is read multiple times. + +If `igrep-find' is non-nil, the directory or directories +containing FILES is recursively searched for files whose name matches +the file name component of FILES \(and whose contents match +EXPRESSION\)." + (interactive + (igrep-read-args)) + (if (null program) + (setq program (or igrep-program "grep"))) + (if (null options) + (setq options igrep-options)) + (if (not (listp files)) ; (stringp files) + (setq files (list files))) + (if (string-match "^[rj]?sh$" (file-name-nondirectory shell-file-name)) + ;; (restricted, job-control, or standard) Bourne shell doesn't expand ~: + (setq files + (mapcar 'expand-file-name files))) + (let* ((win32-quote-process-args nil) ; work around NT Emacs hack + (use-zgrep (cond ((eq igrep-use-zgrep t)) + (igrep-use-zgrep + (let ((files files) + (compressed-p nil)) + (while (and files (not compressed-p)) + (if (save-match-data + (string-match "\\.g?[zZ]\\'" (car files))) + (setq compressed-p t)) + (setq files (cdr files))) + compressed-p)) + (t nil))) + (command (format "%s -n %s %s %c%s%c %s %s" + (if (and use-zgrep + (save-match-data + (not (string-match "\\`z" program)))) + (setq program (concat "z" program)) + program) + (or options "") + (or igrep-expression-option + (progn + (if (save-match-data + (string-match "\\`-" expression)) + (setq expression (concat "\\" expression))) + "")) + igrep-expression-quote-char + expression + igrep-expression-quote-char + (if igrep-find + (if igrep-find-use-xargs + "" + "\"{}\"") + (mapconcat (function identity) files " ")) + (if (boundp 'grep-null-device) + grep-null-device + "/dev/null")))) + (if igrep-find + (setq command + (igrep-format-find-command command files))) + (cond ((eq igrep-save-buffers t) (save-some-buffers t)) + (igrep-save-buffers (save-some-buffers))) + (compile-internal command + (format "No more %c%s%c matches" + igrep-expression-quote-char + program + igrep-expression-quote-char) + "igrep" nil grep-regexp-alist))) + +;; Analogue commands: + +;;;###autoload +(defmacro igrep-define (analogue-command &rest igrep-bindings) + "Define ANALOGUE-COMMAND as an `igrep' analogue command. +Optional (VARIABLE VALUE) arguments specify temporary bindings for the command." +;;; (interactive "SCommand: ") ; C-u => read bindings? + (let ((analogue-program (symbol-name analogue-command))) + (` (defun (, analogue-command) (&rest igrep-args) + (, (format "*Run `%s` via \\[igrep]. +All arguments \(including prefix arguments, when called interactively\) +are handled by `igrep'." + analogue-program)) + (interactive + (let ((igrep-program (if igrep-program (, analogue-program))) + (igrep-program-default (, analogue-program))) + (igrep-read-args))) + (let ( (,@ igrep-bindings)) + (apply (function igrep) + (cond ((interactive-p) (car igrep-args)) + ((car igrep-args)) + (t (, analogue-program))) + (cdr igrep-args))))))) + +(igrep-define egrep) +(igrep-define fgrep) +(igrep-define agrep + (igrep-use-zgrep nil) + (igrep-expression-option "-e")) + + +;; Recursive (`find`) commands: + +;;;###autoload +(defun igrep-find (&rest igrep-args) + "*Run `grep` via `find`; see \\[igrep] and `igrep-find'. +All arguments \(including prefix arguments, when called interactively\) +are handled by `igrep'." + (interactive + (let ((igrep-find t)) + (igrep-read-args))) + (let ((igrep-find t)) + (apply (function igrep) igrep-args))) + +;; Analogue recursive (`find`) commands: + +;;;###autoload +(defmacro igrep-find-define (analogue-command &rest igrep-bindings) + "Define ANALOGUE-COMMAND-find as an `igrep' analogue `find` command. +Optional (VARIABLE VALUE) arguments specify temporary bindings for the command." +;;; (interactive "SCommand: ") ; C-u => read bindings? + (let ((analogue-program (symbol-name analogue-command))) + (setq analogue-command + (intern (format "%s-find" analogue-command))) + (` (defun (, analogue-command) (&rest igrep-args) + (, (format "*Run `%s` via \\[igrep-find]. +All arguments \(including prefix arguments, when called interactively\) +are handled by `igrep'." + analogue-program)) + (interactive + (let ((igrep-program (if igrep-program (, analogue-program))) + (igrep-program-default (, analogue-program)) + (igrep-find t)) + (igrep-read-args))) + (let ( (,@ igrep-bindings)) + (apply (function igrep-find) + (cond ((interactive-p) (car igrep-args)) + ((car igrep-args)) + (t (, analogue-program))) + (cdr igrep-args))))))) + +(igrep-find-define egrep) +(igrep-find-define fgrep) +(igrep-find-define agrep + (igrep-use-zgrep nil) + (igrep-expression-option "-e")) + + +;; Dired commands: + +;;;###autoload +(defun dired-do-igrep (program expression &optional options arg) + "*Run `grep` PROGRAM to match EXPRESSION (with optional OPTIONS) +on the marked (or next prefix ARG) files." + (interactive + (let* ((current-prefix-arg nil) + (igrep-args (igrep-read-args t))) + ;; Delete FILES: + (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args)) + ;; Append ARG: + (nconc igrep-args (list current-prefix-arg)))) + (igrep program + expression + (funcall (cond ((fboundp 'dired-get-marked-files) ; GNU Emacs + 'dired-get-marked-files) + ((fboundp 'dired-mark-get-files) ; XEmacs + 'dired-mark-get-files)) + t arg) + options)) + +;;;###autoload +(defalias 'dired-do-grep 'dired-do-igrep) + +;; Dired recursive (`find`) commands: + +;;;###autoload +(defun dired-do-igrep-find (program expression &optional options arg) + "*Run `grep` PROGRAM to match EXPRESSION (with optional OPTIONS) +on the marked (or next prefix ARG) directories." + (interactive + (let* ((current-prefix-arg nil) + (igrep-find t) + (igrep-args (igrep-read-args t))) + ;; Delete FILES: + (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args)) + ;; Append ARG: + (nconc igrep-args (list current-prefix-arg)))) + (let ((igrep-find t)) + (dired-do-igrep program expression options arg))) + +;;;###autoload +(defalias 'dired-do-grep-find 'dired-do-igrep-find) + + +;;; Utilities: + +(defsubst igrep-file-directory (name) + ;; Return the directory component of NAME, or "." if it has no + ;; directory component. + (directory-file-name (or (file-name-directory name) + (file-name-as-directory ".")))) + +(defsubst igrep-file-pattern (name) + ;; Return the file component of NAME, or "*" if it has no file + ;; component. + (let ((pattern (file-name-nondirectory name))) + (if (string= pattern "") + "*" + pattern))) + +(defun igrep-format-find-command (command files) + ;; Format `grep` COMMAND to be invoked via `find` on FILES. + (let ((directories '()) + (patterns '())) + (while files + (let ((dir (igrep-file-directory (car files))) + (pat (igrep-file-pattern (car files)))) + (if (and (not (string= dir ".")) + (file-symlink-p dir)) + (setq dir (concat dir "/."))) + (if (not (member dir directories)) + (setq directories (cons dir directories))) + (cond ((equal pat "*") + (setq patterns t)) + ((and (listp patterns) + (not (member pat patterns))) + (setq patterns (cons pat patterns))))) + (setq files (cdr files))) + (format (cond ((eq igrep-find-use-xargs 'gnu) + ;; | \\\n + "find %s %s %s %s -print0 | xargs -0 -e %s") + (igrep-find-use-xargs + ;; | \\\n + "find %s %s %s %s -print | xargs -e %s") +;;; ((memq system-type '(ms-dos windows-95 windows-nt emx)) +;;; "find %s %s %s %s -exec %s ;") + (t + "find %s %s %s %s -exec %s \\;")) + (mapconcat (function identity) (nreverse directories) + " ") + (if igrep-find-prune-options + (format "-type d %c( %s %c) -prune -o" + (or igrep-parenthesis-escape-char ? ) + igrep-find-prune-options + (or igrep-parenthesis-escape-char ? )) + "") + (if igrep-find-file-options + (format "%c( %s %c)" + (or igrep-parenthesis-escape-char ? ) + igrep-find-file-options + (or igrep-parenthesis-escape-char ? )) + "") + (if (listp patterns) + (if (cdr patterns) ; (> (length patterns) 1) + (format "%c( %s %c)" + (or igrep-parenthesis-escape-char " ") + (mapconcat (function (lambda (pat) + (format "-name \"%s\"" pat))) + (nreverse patterns) + " -o ") + (or igrep-parenthesis-escape-char " ")) + (format "-name \"%s\"" (car patterns))) + "") + command))) + +(defun igrep-read-args (&optional no-files) + ;; Read and return a list: (PROGRAM EXPRESSION FILES OPTIONS). + ;; If NO-FILES is non-nil, then FILES is not read and nil is returned + ;; in its place. + (let* ((program (igrep-read-program (if igrep-verbose-prompts + (if igrep-find + "[find] ")))) + (prompt-prefix (if igrep-verbose-prompts + (apply (function concat) + (if igrep-find + "[find] ") + (if (eq igrep-verbose-prompts t) + (list program " "))))) + (options (igrep-read-options prompt-prefix))) + (if (eq igrep-verbose-prompts t) + (setq prompt-prefix + (concat prompt-prefix options " "))) + (list program + (igrep-read-expression prompt-prefix) + (if (not no-files) + (igrep-read-files prompt-prefix)) + options))) + +(defsubst igrep-prefix (prefix string) + ;; If PREFIX is non-nil, concatenate it and STRING; otherwise, return STRING. + (if prefix + (concat prefix string) + string)) + +(defun igrep-read-program (&optional prompt-prefix) + ;; If igrep-program is nil, read and return a program name from the + ;; minibuffer; otherwise, return igrep-program. + ;; Optional PROMPT-PREFIX is prepended to the "Program: " prompt. + (or igrep-program + (let ((prompt "Program: ")) + (completing-read (igrep-prefix prompt-prefix prompt) igrep-program-table + nil t (or igrep-program-default "grep"))))) + +(defun igrep-read-options (&optional prompt-prefix) + ;; If current-prefix-arg is '(4) or '(64), read and return an options + ;; string from the minibuffer; otherwise, return igrep-options. + ;; Optional PROMPT-PREFIX is prepended to the "Options: " prompt. + (if (or igrep-read-options + (and (consp current-prefix-arg) + (memq (prefix-numeric-value current-prefix-arg) + '(4 64)))) + (let ((prompt "Options: ")) + (read-string (igrep-prefix prompt-prefix prompt) + (or igrep-options "-"))) + igrep-options)) + +(defun igrep-read-expression (&optional prompt-prefix) + ;; Read and return a `grep` expression string from the minibuffer. + ;; Optional PROMPT-PREFIX is prepended to the "Expression: " prompt. + (let ((default-expression (igrep-default-expression))) + (if (string= default-expression "") + (read-from-minibuffer (igrep-prefix prompt-prefix "Expression: ") + nil nil nil 'igrep-expression-history) + (let ((expression + (igrep-read-string-with-default-in-history + (igrep-prefix prompt-prefix (format "Expression (default %s): " + default-expression)) + default-expression + 'igrep-expression-history))) + (if (string= expression "") + default-expression + expression))))) + +(defun igrep-default-expression () + (if (eq major-mode 'dired-mode) + (let ((dired-file (dired-get-filename nil t))) + (save-excursion + (set-buffer (or (and dired-file (get-file-buffer dired-file)) + (other-buffer (current-buffer) t))) + (current-word))) + (current-word))) + +(defsubst igrep-default-key () + ;; Return the key bound to `exit-minibuffer', preferably "\r". + (if (eq (lookup-key minibuffer-local-completion-map "\r") + (function exit-minibuffer)) + "\r" + (where-is-internal (function exit-minibuffer) + minibuffer-local-completion-map + t))) + +(defun igrep-read-files (&optional prompt-prefix) + ;; Read and return a file name pattern from the minibuffer. If + ;; current-prefix-arg is '(16) or '(64), read multiple file name + ;; patterns and return them in a list. Optional PROMPT-PREFIX is + ;; prepended to the "File(s): " prompt. + (let* ((dired-subdirectory (if (eq major-mode 'dired-mode) + (dired-current-directory t))) + (default-files (concat dired-subdirectory + (igrep-default-file-pattern))) + (prompt (format "File(s) (default %s): " default-files)) + (insert-default-directory nil) ; use relative path names + (file (igrep-read-file-name-with-default-in-history + (igrep-prefix prompt-prefix prompt) + default-files + nil + 'igrep-files-history))) + (if (or igrep-read-multiple-files + (and (consp current-prefix-arg) + (memq (prefix-numeric-value current-prefix-arg) + '(16 64)))) + (let ((files (list file))) + (setq prompt + (igrep-prefix prompt-prefix + (if igrep-verbose-prompts + (format "File(s): [Type `%s' when done] " + (key-description (igrep-default-key))) + "File(s): "))) + (while (not (string= (setq file + (igrep-read-file-name prompt + nil "" nil nil + 'igrep-files-history)) + "")) + (setq files (cons file files))) + (nreverse files)) + file))) + +(defmacro igrep-with-default-in-history (default history &rest body) + ;; Temporarily append DEFAULT to HISTORY, and execute BODY forms. + (` (progn + ;; Append default to history: + (set history + (cons (, default) + (if (boundp (, history)) + (symbol-value (, history)) + '()))) + (unwind-protect ; Make sure the history is restored. + ;; Execute body: + (progn (,@ body)) + ;; Delete default from history (undo the append above): + (setcdr (symbol-value (, history)) + (nthcdr 2 (symbol-value (, history)))))))) + +(defun igrep-read-string-with-default-in-history (prompt default history) + ;; Read a string from the minibuffer, prompting with string PROMPT. + ;; DEFAULT can be inserted into the minibuffer with `previous- + ;; history-element'; HISTORY is a symbol whose value (if bound) is a + ;; list of previous results, most recent first. + (let ((string (igrep-with-default-in-history default history + (read-from-minibuffer prompt nil nil nil history)))) + ;; Replace empty string in history with default: + (if (string= string "") + (setcar (symbol-value history) default)) + string)) + +(defun igrep-read-file-name-with-default-in-history + (prompt &optional default initial history) + ;; Read a file name from the minibuffer, prompting with string PROMPT. + ;; DEFAULT can be inserted into the minibuffer with `previous- + ;; history-element'; HISTORY is a symbol whose value (if any) is a + ;; list of previous results, most recent first. + (igrep-with-default-in-history default history + (igrep-read-file-name prompt nil default nil initial history))) + +(defun igrep-read-file-name (prompt + &optional directory default existing initial history) + ;; Just like read-file-name, but with optional HISTORY. + ;; Also: convert DIRECTORY to DIRECTORY/* file name pattern. + (let ((file-name + (if history + (let ((file-name-history (symbol-value history))) + (prog1 (read-file-name prompt directory default existing initial) + (set history file-name-history))) + (read-file-name prompt directory default existing initial)))) + (if (and (not (string-equal file-name "")) + (file-directory-p file-name)) + (expand-file-name "*" file-name) + file-name))) + +(defun igrep-default-file-pattern () + ;; Return a shell file name pattern that matches files with the same + ;; extension as the file being visited in the current buffer. + ;; (Based on other-possibly-interesting-files in ~/as-is/unix.el, by + ;; Wolfgang Rupprecht <wolfgang@mgm.mit.edu>.) + (if (eq major-mode 'dired-mode) + (cond ((stringp dired-directory) + (if (file-directory-p dired-directory) + "*" + (file-name-nondirectory dired-directory))) ; wildcard + ((consp dired-directory) ; (DIR FILE ...) + (mapconcat 'identity (cdr dired-directory) " "))) + (if buffer-file-name + (let ((file-name (file-name-nondirectory buffer-file-name))) + (concat "*" + (save-match-data + (if (string-match "\\.[^.]+\\(\\.g?[zZ]\\)?$" file-name) + (substring file-name + (match-beginning 0) (match-end 0)))))) + "*"))) + +;;; Local Variables: +;;; eval: (put 'igrep-with-default-in-history 'lisp-indent-hook 2) +;;; eval: (put 'igrep-define 'lisp-indent-hook 1) +;;; eval: (put 'igrep-find-define 'lisp-indent-hook 1) +;;; End: + +;;;; igrep.el ends here +