gnu-emacs-sources
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

igrep.el 2.110


From: Kevin Rodgers
Subject: igrep.el 2.110
Date: Mon, 17 May 2004 14:30:01 -0600
User-agent: Mozilla/5.0 (X11; U; SunOS i86pc; en-US; rv:0.9.4.1) Gecko/20020406 Netscape6/6.2.2

This is just a bug fix release, no new features.  Note that CVS Emacs
(21.3.50) may not work without loading the new progmodes/grep.el
library.

Changes since igrep.el 2.108:
* Terminate loop when igrep-read-multiple-files is set.
* Default igrep-find-use-xargs to nil on MacOS (Darwin).

Thanks to:
* Scott Frazer

igrep.el 2.110 is available on the Emacs Wiki:
* http://emacswiki.org/elisp/igrep.el

--
Kevin Rodgers
;;; igrep.el --- An improved interface to `grep` and `find`
;;; -*-unibyte: t;-*-

;; Copyright © 1993-1998,2000-2003 Kevin Rodgers

;; Author: Kevin Rodgers <address@hidden>
;; Created:  22 Jun 1993
;; Version: 2.110
;; Keywords: tools, processes, search
;; SCCS: @(#)igrep.el   2.110

;; 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., 59 Temple Place, Suite 330, Boston,
;; MA 02111-1307 USA

;;; Commentary:

;; The `igrep' command is like `grep' except that it takes three
;; required arguments (PROGRAM, REGEX, and FILES) and an optional
;; argument (OPTIONS) instead of just one argument (COMMAND).  The
;; analogous `egrep' and `fgrep' commands are also defined for
;; convenience.
;; 
;; The `igrep-find' command is like `igrep' except that it uses `find`
;; to recursively `grep` a directory.  The analogous `egrep-find' and
;; `fgrep-find' commands are also defined for convenience.
;; 
;; When called interactively, `igrep' and `igrep-find' (and their
;; analogues) provide defaults for the REGEX and FILES arguments based
;; on the current word and the visited file name (if the `igrep-regex-
;; default' and `igrep-files-default' options are set, respectively).
;; The `igrep-insert-default-key' option allows the default value to be
;; inserted into the minibuffer for editing; since Emacs 20 provides
;; that via the minibuffer history, it's only enabled for older
;; versions by default. Other options that control the user interface
;; are `igrep-insert-default-directory', `igrep-read-options', `igrep-
;; read-multiple-files', `igrep-verbose-prompts', `igrep-save-buffers',
;; and `igrep-menu-bar'.
;; 
;; Besides the basic `igrep-program' and `igrep-find-program' global
;; variables, other variables control the syntax of the `grep` and
;; `find` shell commands that are executed: `igrep-options', `igrep-
;; regex-option', `igrep-case-fold-search', `igrep-find-prune-clause',
;; `igrep-find-file-clause', and `igrep-find-use-xargs'.
;; 
;; The `igrep-use-zgrep' user option controls whether the corresponding
;; GNU (gzip) "zPROGRAM" script is used, to `grep` compressed files.
;; Special minibuffer history lists are maintained for the REGEX and
;; FILES arguments.
;; 
;; The `agrep' and `agrep-find' commands are interfaces to the
;; approximate `grep` utility, which is distributed with the `glimpse'
;; indexing and query tool (available from http://www.tgries.de/agrep/).
;; 
;; `grep' itself can be advised to provide the `igrep' interface when
;; it is invoked interactively (but still use the original argument
;; list when it is called from Emacs Lisp), via the `igrep-insinuate'
;; command.  `igrep-insinuate' also defines `grep-find' as an alias for
;; `igrep-find', `dired-do-grep' and `dired-do-grep-find' as aliases
;; for `dired-do-igrep' and `dired-do-igrep-find', and `Buffer-menu-
;; grep' as an alias for `Buffer-menu-igrep'.
;; 
;; When run interactively from Dired mode, the various `igrep' commands
;; provide defaults for the REGEX and FILES arguments that are based on
;; the visited directory (including any inserted subdirectories) and
;; the current file.  The alternative `dired-do-igrep' and `dired-do-
;; igrep-find' commands respect the `dired-do-*' command conventions: a
;; prefix argument is interpreted as the number of succeeding files to
;; `grep`, otherwise all the marked files are `grep`ed.
;; 
;; The `igrep-visited-files' command provides a simple way to `grep`
;; just those files that are being visited in buffers.  The `Buffer-
;; menu-igrep' command does the same thing, for buffers marked for
;; selection in Buffer Menu mode.

;; 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 'igrep "igrep"
;;       "*Run `grep` PROGRAM to match REGEX in FILES..." t)
;;    (autoload 'igrep-find "igrep"
;;       "*Run `grep` via `find`..." t)
;;    (autoload 'igrep-visited-files "igrep"
;;       "*Run `grep` ... on all visited files." t)
;;    (autoload 'dired-do-igrep "igrep"
;;       "*Run `grep` on the marked (or next prefix ARG) files." t)
;;    (autoload 'dired-do-igrep-find "igrep"
;;       "*Run `grep` via `find` on the marked (or next prefix ARG) 
directories." t)
;;    (autoload 'Buffer-menu-igrep "igrep"
;;      "*Run `grep` on the files visited in buffers marked with '>'." t)
;;    (autoload 'igrep-insinuate "igrep"
;;      "Define `grep' aliases for the corresponding `igrep' commands." t)
;; 2. a. For completeness, you can add these forms as well:
;;    (autoload 'grep "igrep"
;;       "*Run `grep` PROGRAM to match REGEX in FILES..." t)
;;    (autoload 'egrep "igrep"
;;       "*Run `egrep`..." t)
;;    (autoload 'fgrep "igrep"
;;       "*Run `fgrep`..." t)
;;    (autoload 'agrep "igrep"
;;       "*Run `agrep`..." t)
;;    (autoload 'grep-find "igrep"
;;       "*Run `grep` via `find`..." t)
;;    (autoload 'egrep-find "igrep"
;;       "*Run `egrep` via `find`..." t)
;;    (autoload 'fgrep-find "igrep"
;;       "*Run `fgrep` via `find`..." t)
;;    (autoload 'agrep-find "igrep"
;;       "*Run `agrep` via `find`..." 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 (http://www.cygnus.com/misc/gnu-win32/).

;; Usage:
;; 
;; These igrep commands accept 1, 2, or 3 `C-u' prefix arguments:
;;      M-x igrep               M-x igrep-find
;;      M-x  grep               M-x  grep-find  [after `M-x igrep-insinuate']
;;      M-x egrep               M-x egrep-find
;;      M-x fgrep               M-x fgrep-find
;;      M-x agrep               M-x agrep-find
;; 
;; These igrep commands accept a single `C-u' prefix argument:
;;      M-x igrep-visited-files
;;      M-x Buffer-menu-igrep   [in the *Buffer List* buffer]
;; 
;; These igrep commands interpret a prefix argument like the Emacs
;; `dired-do-*' commands:
;;      M-x dired-do-igrep      M-x dired-do-igrep-find
;;      M-x  dired-do-grep      M-x  dired-do-grep-find [after `M-x
;;                                                       igrep-insinuate']
;; 
;; These Emacs commands can be used after any igrep command:
;;      C-x ` (M-x next-error)
;;      C-c C-c (M-x compile-goto-error)        [in the *igrep* buffer]

;; Customization examples:
;; 
;; To ignore case by default:
;;      (setq igrep-options "-i")
;; or:
;;      (setq igrep-case-fold-search t)
;; 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 search "*.[ch]" files by default in C mode:
;;      (put 'igrep-files-default 'c-mode
;;           (lambda () "*.[ch]"))
;; To disable the default search regex and/or files pattern, except for
;; specific modes:
;;      (setq igrep-regex-default 'ignore)
;;      (setq igrep-files-default 'ignore)
;; To avoid exceeding some shells' limit on command argument length
;; (this only searches files in the current directory):
;;      (setq igrep-find t
;;            igrep-find-prune-clause "-type d \\! -name .")

;; To do:
;; 1. Replace igrep-options with a table that maps igrep-program
;;    to the appropriate options, and/or support POSIX (egrep -> `grep -E`).
;; 2. Generalize support for the -prune find clause (e.g. -fstype nfs).
;; 3. Provide support for `glimpse`.

;;; Code:

;; Package interface:

(require 'custom)                       ; defgroup, defcustom

(require 'easymenu)                     ; easy-menu-define, easy-menu-add-item

(require 'compile)                      ; compile-internal, grep-regexp-alist,
                                        ; grep-null-device

(eval-when-compile
  (require 'dired)                      ; dired-directory,
                                        ; dired-get-filename,
                                        ; dired-current-directory,
                                        ; dired-get-marked-files,
                                        ; dired-mark-get-files
  (or (featurep 'ange-ftp)
      (featurep 'efs)
      (condition-case nil
          (load-library "ange-ftp")     ; ange-ftp-ftp-name
        (error nil))
      (condition-case nil
          (load-library "efs")          ; efs-ftp-path
        (error nil)))
  )

(defconst igrep-version "2.110"
  "This version of igrep.el.")

(defgroup igrep nil
  "An improved interface to `grep` and `find`."
  :group 'compilation)

;; User options:

(defcustom 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."
  :group 'igrep
  :type '(choice (const nil) (string)))
(put 'igrep-options 'variable-interactive
     "xOptions (\"-xyz\" or nil): ")

(defcustom igrep-case-fold-search nil
  "*If non-nil, `\\[igrep]' ignores case unless REGEX has uppercase letters."
  :group 'igrep
  :type '(boolean))
(put 'igrep-case-fold-search 'variable-interactive
     "XIgnore case? (t or nil): ")

(defcustom 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."
  :group 'igrep
  :type '(boolean))
(put 'igrep-read-options 'variable-interactive
     "XAlways prompt for options? (t or nil): ")

(defcustom 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."
  :group 'igrep
  :type '(boolean))
(put 'igrep-read-multiple-files 'variable-interactive
     "XAlways prompt for multiple files? (t or nil): ")

(defcustom igrep-regex-default 'current-word
  "*If non-nil, a function that returns a default REGEX for `\\[igrep]'.
The function is called with no arguments and should return a string (or nil).

A different function can be specified for any particular mode by specifying
a value for that `major-mode' property; for example:
        (put 'igrep-regex-default 'dired-mode
             'igrep-dired-file-current-word)"
  :group 'igrep
  :type '(choice (const nil) (function)))
(put 'igrep-regex-default 'variable-interactive
     "SProvide a default regex? (function or nil): ")
(put 'igrep-regex-default 'dired-mode
     'igrep-dired-file-current-word)

(defcustom igrep-files-default 'igrep-buffer-file-name-pattern
  "*If non-nil, a function that returns the default FILES for `\\[igrep]'.
The function is called with no arguments and should return a string,
or a list of strings (or nil).

A different function can be specified for any particular mode by specifying
a value for that `major-mode' property; for example:
        (put 'igrep-files-default 'dired-mode
             'igrep-dired-directory-file-pattern)"
  :group 'igrep
  :type '(choice (const nil) (function)))
(put 'igrep-files-default 'variable-interactive
     "SProvide a default file name pattern? (function or nil): ")
(put 'igrep-files-default 'dired-mode
     'igrep-dired-directory-file-pattern)

(defcustom 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."
  :group 'igrep
  :type '(choice (const :tag "Verbose" t)
                 (other :tag "Semi-verbose" semi)
                 (const :tag "Terse" nil)))
(put 'igrep-verbose-prompts 'variable-interactive
     "XPrompt verbosely? (t, 'semi, or nil): ")

(defcustom igrep-insert-default-directory nil
  "*The value of `insert-default-directory' for `\\[igrep]'."
  :group 'igrep
  :type '(boolean))
(put 'igrep-insert-default-directory 'variable-interactive
     "XPrompt with directory in the minibuffer? (t or nil): ")

(defcustom igrep-insert-default-key
  (if (< emacs-major-version 20) "\C-c\C-e")
  "*The key used to insert the default argument in the minibuffer.
In Emacs 20, the default is available via the minibuffer history \
\(\\<minibuffer-local-map>\\[next-history-element])."
  :group 'igrep
  :type '(choice (const nil) (string) (vector))) ; key-binding
(put 'igrep-insert-default-key 'variable-interactive
     "kSet key to insert the default `\\[igrep]' argument in the minibuffer: ")

(defcustom 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."
  :group 'igrep
  :type '(choice (const :tag "Save" t)
                 (other :tag "Query" query)
                 (const :tag "Don't Save" nil)))
(put 'igrep-save-buffers 'variable-interactive
     "XSave modified buffers? (t, 'query, or nil): ")

(defcustom igrep-menu-bar t
  "*If non-nil, enable the `igrep-menu' submenu on the \"Tools\" menu bar."
  :group 'igrep
  :type '(boolean))
(put 'igrep-menu-bar 'variable-interactive
     "XEnable menu bar? (t or nil): ")

;; User variables:

(defsubst igrep-easy-menu-item (name callback help-keyword help-text)
  "Return a [NAME CALLBACK HELP-KEYWORD HELP-TEXT] menu item.
See `easy-menu-define'."
  (if (featurep 'xemacs)                ; no :help keyword
      (vector name callback)
    (vector name callback help-keyword help-text)))

(defvar :help ':help)                   ; Emacs 19

(defvar igrep-easy-menu
  `("Search Files and Directories (igrep)"
    ,@(cond ((featurep 'xemacs) '(:included igrep-menu-bar))
            ((>= emacs-major-version 20) '(:active igrep-menu-bar))
            (t ()))
    ("Search files"
     ,(igrep-easy-menu-item "`grep` files..." 'igrep
                            :help "Search files for basic regex(5)s")
     ,(igrep-easy-menu-item "`egrep` files..." 'egrep
                            :help "Search files for extended regex(5)s")
     ,(igrep-easy-menu-item "`fgrep` files..." 'fgrep
                            :help "Search files for strings"))
    ("Search directories"
     ,(igrep-easy-menu-item "`find | grep` directories..." 'igrep-find
                            :help "Search directories for basic regex(5)s")
     ,(igrep-easy-menu-item "`find | egrep` directories..." 'egrep-find
                            :help "Search directories for extended regex(5)s")
     ,(igrep-easy-menu-item "`find | fgrep` directories..." 'fgrep-find
                            :help "Search directories for strings"))
    "--"
    ,(igrep-easy-menu-item "Search visited files..." 'igrep-visited-files
                           :help "Search visited files for basic regex(5)s"))
  "If non-nil, the menu bar submenu of `igrep' commands.
See `easy-menu-define'.")

(defvar igrep-null-device
  (cond ((boundp 'null-device) null-device) ; Emacs 20
        ((boundp 'grep-null-device) grep-null-device)) ; Emacs 19
  "The system null device.")

(defvar igrep-program "grep"
  "The default program run by `\\[igrep]' and `\\[igrep-find]'.
It must accept a `grep` regex argument and one or more file names, plus
the \"-n\" option.  If nil, `\\[igrep]' prompts for the program to run.")

(defvar igrep-regex-option
  (if (equal (call-process igrep-program nil nil nil
                           "-e" "foo" igrep-null-device)
             1)
      "-e")
  "If non-nil, the option used to specify the REGEX argument to `\\[igrep]'.
This protects an initial \"-\" from option processing.")

(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))
               (file-readable-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.
This is 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`.")

(defvar igrep-find nil
  "If non-nil, `\\[igrep]' searches directories using `find`.
See `igrep-find'.")

(defvar igrep-find-program "find"
  "The program run by `\\[igrep-find]'.")

(defvar igrep-find-prune-clause
  (if (equal (call-process igrep-find-program nil nil nil
                           igrep-null-device "-prune")
             0)
      (format "-type d %s -name RCS -o -name CVS -o -name SCCS %s"
              (shell-quote-argument "(")
              (shell-quote-argument ")")))
  "The `find` clause used to prune directories, or nil;
see `igrep-find'.")

(defvar igrep-find-file-clause
  (format "-type f %s -name %s %s -name %s %s -name %s %s -name %s" ; -type l
          (shell-quote-argument "!")
          (shell-quote-argument "*~")   ; Emacs backup
          (shell-quote-argument "!")
          (shell-quote-argument "*,v")  ; RCS file
          (shell-quote-argument "!")
          (shell-quote-argument "s.*")  ; SCCS file
          (shell-quote-argument "!")
          (shell-quote-argument ".#*")) ; CVS file
  "The `find` clause used to filter files passed to `grep`, or nil;
see `igrep-find'.")

(defvar igrep-find-use-xargs
  (cond ((equal (call-process igrep-find-program nil nil nil
                              igrep-null-device "-print0")
                0)
         'gnu)
        ((not (equal system-type 'darwin)))) ; not MacOS
  "Whether `\\[igrep-find]' uses the `xargs` program or not.
If `gnu', it executes
        `find ... -print0 | xargs -0 -e grep ...`;
if not `gnu' but non-nil, it executes
        `find ... -print | xargs -e grep ...`;
if nil, it executes
        `find ... -exec grep ...`.")

(defvar igrep-program-default "grep"
  "The default `grep` program.
This is passed by `igrep-read-program' to `completing-read' when
`igrep-program' is nil.")

;; Internal variables:

(defvar igrep-regex-history '()
  "The minibuffer history list for `\\[igrep]'s REGEX argument.")

(defvar igrep-files-history '()
  "The minibuffer history list for `\\[igrep]'s FILES argument.")

;; Commands:

;;;###autoload
(defun igrep-insinuate (&optional override)
  "Define `grep' aliases for the corresponding `igrep' commands.
With a prefix arg, OVERRIDE the current `grep' command definitions."
  (interactive "P")
  (if override
      (defalias 'grep 'igrep)
    (defadvice grep (around igrep-interactive first (&rest command-args)
                            activate)
      "If called interactively, use the `\\[igrep]' interface instead,
where COMMAND-ARGS is (PROGRAM REGEX FILES [OPTIONS]); if called
programmatically, COMMAND-ARGS is still (COMMAND)."
      (interactive (igrep-read-args))
      (if (interactive-p)
          (apply 'igrep command-args)
        ad-do-it)))
  (if (or (not (fboundp 'grep-find))
          override)
      (defalias 'grep-find 'igrep-find))
  (if (or (not (fboundp 'dired-do-grep))
          override)
      (defalias 'dired-do-grep 'dired-do-igrep))
  (if (or (not (fboundp 'dired-do-grep-find))
          override)
      (defalias 'dired-do-grep-find 'dired-do-igrep-find))
  (if (or (not (fboundp 'Buffer-menu-grep))
          override)
      (defalias 'Buffer-menu-grep 'Buffer-menu-igrep)))

(defsubst igrep-quote-file-name (file)
  "Quote FILE name pattern for `shell-file-name'."
  (if (fboundp 'shell-quote-wildcard-pattern) ; Emacs 21
      (shell-quote-wildcard-pattern file)
    (shell-quote-argument file)))

;;;###autoload
(defun igrep (program regex files &optional options)
  "*Run `grep` PROGRAM to match REGEX 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'.

REGEX is automatically quoted by `shell-quote-argument'.

FILES is either a file name pattern (automatically quoted by
`shell-quote-wildcard-pattern', then expanded by the `shell-file-name' shell),
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 REGEX)."
  (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 (and (member ?~ (mapcar 'string-to-char files))
           (save-match-data
             (string-match "\\`[rj]?sh\\(\\.exe\\)?\\'"
                           (file-name-nondirectory shell-file-name))))
      ;; (restricted, job-control, or standard) Bourne shell doesn't expand ~:
      (setq files
            (mapcar 'expand-file-name files)))
  (let* ((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 %s %s %s"
                          (if (and use-zgrep
                                   (save-match-data
                                     (not (string-match "\\`z" program))))
                              (setq program (concat "z" program))
                            program)
                          (or options
                              (and igrep-case-fold-search
                                   (equal regex (downcase regex))
                                   "-i")
                              "")
                          (or igrep-regex-option
                              (progn
                                (if (save-match-data
                                      (string-match "\\`-" regex))
                                    (setq regex (concat "\\" regex)))
                                ""))
                          (shell-quote-argument regex)
                          (if igrep-find
                              (if igrep-find-use-xargs
                                  ""
                                (shell-quote-argument "{}"))
                            (mapconcat (lambda (file)
                                         (let ((dir (file-name-directory file)))
                                           (if dir
                                               (expand-file-name
                                                (file-name-nondirectory file)
                                                (igrep-quote-file-name dir))
                                             file)))
                                       files " "))
                          igrep-null-device)))
    (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 %s matches" program)
                      "igrep" nil grep-regexp-alist)))

;; Analogue commands:

(defmacro igrep-define (analogue-command &rest igrep-bindings)
  "Define ANALOGUE-COMMAND as an `igrep' analogue command.
Optional (VARIABLE VALUE) arguments specify the temporary IGREP-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 '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-regex-option "-e"))

;; Recursive (`find`) commands:

;;;###autoload
(defun igrep-find (&rest igrep-args)
  "*Run `grep` via `find`; see `igrep' and `igrep-find'.
All IGREP-ARGS (including prefix arguments, when called interactively)
are handled by `igrep'."
  (interactive
   (let ((igrep-find t))
     (igrep-read-args)))
  (let ((igrep-find t))
    (apply 'igrep igrep-args)))

;; Analogue recursive (`find`) commands:

(defmacro igrep-find-define (analogue-command &rest igrep-bindings)
  "Define ANALOGUE-COMMAND-find as an `igrep' analogue `find` command.
Optional (VARIABLE VALUE) arguments specify the temporary IGREP-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 '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-regex-option "-e"))

;;;###autoload
(defun igrep-visited-files (program regex &optional options)
  "*Run `grep` PROGRAM to match REGEX (with optional OPTIONS) \
on all visited files.
See `\\[igrep]'."
  (interactive
   (let ((igrep-args (igrep-read-args 'no-files)))
     ;; Delete FILES:
     (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
     igrep-args))
  (igrep program regex
         (let ((directory-abbrev-alist
                (cons (cons (regexp-quote (expand-file-name default-directory))
                            "./")       ; or even ""
                      directory-abbrev-alist)))
           (mapcar 'abbreviate-file-name
                   (apply 'nconc
                          (mapcar (lambda (buffer)
                                    (let ((file (buffer-file-name buffer)))
                                      (if (and file
                                               (cond ((featurep 'ange-ftp)
                                                      (not (ange-ftp-ftp-name 
file)))
                                                     ((featurep 'efs)
                                                      (not (efs-ftp-path file)))
                                                     (t t))
                                               ;; (file-exists-p file)
                                               )
                                          (list file))))
                                  (buffer-list)))))
         options))

;; Dired commands:

;;;###autoload
(defun dired-do-igrep (program regex &optional options arg)
  "*Search the marked (or next prefix ARG) files.
See `\\[igrep]' for a description of PROGRAM, REGEX, and OPTIONS."
  (interactive
   (let ((igrep-args
          (let ((current-prefix-arg nil))
            (igrep-read-args 'no-files))))
     ;; Delete FILES:
     (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
     ;; Append ARG:
     (nconc igrep-args (list current-prefix-arg))))
  (igrep program regex
         (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))

;; Dired recursive (`find`) commands:

;;;###autoload
(defun dired-do-igrep-find (program regex &optional options arg)
  "*Run `grep` on the marked (or next prefix ARG) directories.
See `\\[igrep]' for a description of PROGRAM, REGEX, and OPTIONS."
  (interactive
   (let ((igrep-args
          (let ((current-prefix-arg nil)
                (igrep-find t))
            (igrep-read-args 'no-files))))
     ;; 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 regex options arg)))

;; Buffer menu commands:

;;;###autoload
(defun Buffer-menu-igrep (program regex &optional options)
  "*Run `grep` on the files visited in buffers marked with '>'.
See `\\[igrep]' for a description of PROGRAM, REGEX, and OPTIONS."
  (interactive
   (let ((igrep-args (igrep-read-args 'no-files)))
     ;; Delete FILES:
     (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
     igrep-args))
  ;; See Buffer-menu-select:
  (let ((marked-files '())
        marked-buffer
        file)
    (goto-char (point-min))
    (while (search-forward "\n>" nil t)
      (setq marked-buffer (Buffer-menu-buffer t)
            file (buffer-file-name marked-buffer))
      (if (and file
               ;; local:
               (cond ((featurep 'ange-ftp)
                      (not (ange-ftp-ftp-name file)))
                     ((featurep 'efs)
                      (not (efs-ftp-path file)))
                     (t t)))
          (setq marked-files (cons file marked-files)))
;;;    (let ((buffer-read-only nil))
;;;      (delete-char -1)
;;;      (insert ?\ ))
      )
    (setq marked-files (nreverse marked-files))
    (igrep program regex
           (let ((directory-abbrev-alist
                  (cons (cons (regexp-quote (expand-file-name 
default-directory))
                              "./")     ; or even ""
                        directory-abbrev-alist)))
             (mapcar 'abbreviate-file-name marked-files))
           options)))

;; User functions:

(defun igrep-dired-file-current-word ()
  "Return the current word in the file on this line, if it is visible;
else, return the file name on this line, if there is one;
otherwise, return the current word."
  (let* ((dired-file
          (dired-get-filename t t))
         (dired-file-buffer
          (if dired-file
              (get-file-buffer (expand-file-name dired-file))))
         (dired-file-buffer-window
          (if dired-file-buffer
              (get-buffer-window dired-file-buffer))))
    (cond (dired-file-buffer-window (save-excursion
                                      (set-buffer dired-file-buffer)
                                      (current-word)))
          (dired-file)
          (t (current-word)))))

(defun igrep-buffer-file-name-pattern ()
  "Return a shell file name pattern based on the visited file name.
If the `buffer-file-name' variable is nil, return \"*\"."
  ;; (Based on other-possibly-interesting-files in ~/as-is/unix.el, by
  ;; Wolfgang Rupprecht <address@hidden>.)
  (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))))))
    "*"))

(defun igrep-dired-directory-file-pattern ()
  "Return a shell file name pattern based on `dired-directory', or \"*\"."
  (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) " "))))

;; Utilities:

(defsubst igrep-file-directory (name)
  "Return the directory component of NAME, or \".\" if it has none."
  (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 none."
  (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
                   "%s %s %s %s %s -print0 | xargs -0 -e %s")
                  (igrep-find-use-xargs
                   ;; | \\\n
                   "%s %s %s %s %s -print | xargs -e %s")
                  (t
                   "%s %s %s %s %s -exec %s %s"))
            igrep-find-program
            (mapconcat 'igrep-quote-file-name (nreverse directories)
                       " ")
            (if igrep-find-prune-clause
                (format "%s -prune -o" igrep-find-prune-clause)
              "")
            (or igrep-find-file-clause "")
            (if (listp patterns)
                (if (cdr patterns)      ; (> (length patterns) 1)
                    (format "%s %s %s"
                            (shell-quote-argument "(")
                            (mapconcat (lambda (pat)
                                         (format "-name %s"
                                                 (shell-quote-argument pat)))
                                       (nreverse patterns)
                                       " -o ")
                            (shell-quote-argument ")"))
                  (format "-name %s" (shell-quote-argument (car patterns))))
              "")
            command
            (shell-quote-argument ";")
            )))

(defmacro igrep-default-arg (variable)
  "Return the default arg based on VARIABLE."
  `(if ,variable
       (cond ((get (quote ,variable) major-mode)
              (funcall (get (quote ,variable) major-mode)))
             (t (funcall ,variable)))))

(defun igrep-default-regex ()
  "Return the default REGEX for `\\[igrep]'."
  (let ((default-regex (igrep-default-arg igrep-regex-default)))
    (if (not (equal default-regex ""))
        default-regex)))

(defun igrep-default-files ()
  "Return the default FILES for `\\[igrep]'."
  (let* ((dired-subdirectory (if (cond ((fboundp 'derived-mode-p) ; Emacs 21
                                        (derived-mode-p 'dired-mode))
                                       (t (eq major-mode 'dired-mode)))
                                 (dired-current-directory t)))
         (default-files (igrep-default-arg igrep-files-default)))
    (if (not (listp default-files))     ; stringp
        (setq default-files (list default-files)))
    (if dired-subdirectory
        (mapcar (lambda (file)
                  (concat dired-subdirectory file))
                default-files)
      default-files)))

(defsubst igrep-prefix (prefix string &rest strings)
  "Concatenate PREFIX (if non-nil), STRING, and any other STRINGS."
  (if (or prefix strings)
      (apply 'concat prefix string strings)
    string))

(defun igrep-read-args (&optional no-files)
  "Read and return a list: (PROGRAM REGEX FILES OPTIONS).
If NO-FILES is non-nil, then FILES is not read and nil is returned
in its place."
  (let* ((pre-prefix (if (and igrep-find (eq igrep-verbose-prompts t))
                         "[find] "))
         (program
          (igrep-read-program pre-prefix))
         (prefix (if (and program (eq igrep-verbose-prompts t))
                     (igrep-prefix pre-prefix program " ")
                   pre-prefix))
         (options
          (igrep-read-options prefix))
         (post-prefix (if (and options (eq igrep-verbose-prompts t))
                            (igrep-prefix prefix options " ")
                          prefix)))
    (list program
          (igrep-read-regex post-prefix)
          (if (not no-files)
              (igrep-read-files post-prefix))
          options)))

(defun igrep-read-program (&optional prompt-prefix)
  "Read and return a `grep` program name from the minibuffer.
If `igrep-program' is non-nil, it.

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 igrep-program-default))))

(defun igrep-read-options (&optional prompt-prefix)
  "Read and return an options string from the minibuffer.
If `current-prefix-arg' is '(4) or '(64), 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-regex (&optional prompt-prefix)
  "Read and return a `grep` regex(5) string from the minibuffer.
Optional PROMPT-PREFIX is prepended to the \"Regex: \" prompt."
  (if igrep-insert-default-key
      (define-key minibuffer-local-map igrep-insert-default-key
        'igrep-insert-default-regex))
  (let* ((default-regex (igrep-default-regex))
         (prompt (igrep-prefix prompt-prefix
                               (if default-regex
                                   (format "Regex [default: %s]: "
                                           default-regex)
                                 "Regex: ")))
         (regex (cond ((featurep 'xemacs) ; incompatible
                       ;; DEFAULT-VALUE is the 7th arg in 21.4 (but 21.1
                       ;; only accepts 6 args):
                       (read-from-minibuffer prompt
                                             nil nil nil
                                             'igrep-regex-history
                                             nil)) ; ABBREV-TABLE
                      ((>= emacs-major-version 20)
                       (read-from-minibuffer prompt
                                             nil nil nil
                                             'igrep-regex-history
                                             default-regex))
                      (t
                       (read-from-minibuffer prompt
                                             nil nil nil
                                             'igrep-regex-history)))))
    (if (equal regex "")
        (progn
          (or (equal default-regex (car igrep-regex-history))
              (setq igrep-regex-history
                    (cons default-regex igrep-regex-history)))
          default-regex)
      regex)))

(defun igrep-insert-default-regex (&optional clear-minibuffer)
  "*Insert the default regex in the minibuffer.
If a prefix argument is specified, CLEAR-MINIBUFFER contents first."
  (interactive "P")
  (if clear-minibuffer
      (delete-region (if (fboundp 'minibuffer-prompt-end) ; Emacs 21
                         (minibuffer-prompt-end)
                       (point-min))
                     (point-max)))
  (insert (or (save-excursion
                (set-buffer (window-buffer minibuffer-scroll-window))
                (igrep-default-regex))
              "")))

(defun igrep-insert-default-files (&optional clear-minibuffer)
  "*Insert the default files in the minibuffer.
If a prefix argument is specified, CLEAR-MINIBUFFER contents first."
  (interactive "P")
  (if clear-minibuffer
      (delete-region (if (fboundp 'minibuffer-prompt-end) ; Emacs 21
                         (minibuffer-prompt-end)
                       (point-min))
                     (point-max)))
  (insert (mapconcat 'identity
                     (save-excursion
                       (set-buffer (window-buffer minibuffer-scroll-window))
                       (igrep-default-files))
                     " ")))

(defsubst igrep-default-key (command &optional keymap key)
  "Return the key bound to COMMAND in KEYMAP, preferably KEY."
  (if (null keymap)
      (setq keymap (current-global-map)))
  (if (and key
           (eq (lookup-key keymap key) command))
      key
    (where-is-internal command keymap 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* ((default-files (igrep-default-files))
         (default-files-string (mapconcat 'identity default-files " "))
         (insert-default-directory igrep-insert-default-directory)
         (file (igrep-read-file-name
                (igrep-prefix prompt-prefix
                              (if default-files
                                  (format "File(s) [default: %s]: "
                                          default-files-string)
                                "File(s): "))
                nil (if default-files default-files-string "") nil nil
                'igrep-files-history))
         (files (cond ((equal file default-files-string)
                       (setq file default-files))
                      ((not (listp file))
                       (setq file (list file))))))
    (if (or igrep-read-multiple-files
            (and (consp current-prefix-arg)
                 (memq (prefix-numeric-value current-prefix-arg)
                       '(16 64))))
        (let* ((key (igrep-default-key 'exit-minibuffer
                                       minibuffer-local-completion-map
                                       "\r"))
               (prompt
                (igrep-prefix prompt-prefix
                              (if igrep-verbose-prompts
                                  (format "File(s): [Type `%s' when done] "
                                          (key-description key))
                                "File(s): "))))
          (while (and (setq file
                            (igrep-read-file-name prompt
                                                  nil "" nil nil
                                                  'igrep-files-history))
                      (not (equal file "")))
            (setq files (cons file files)))
          (nreverse files))
      files)))

(defun igrep-read-file-name (prompt
  &optional directory default existing initial history)
  "Just like `read-file-name,' but with optional HISTORY."
  (if igrep-insert-default-key
      (define-key minibuffer-local-completion-map igrep-insert-default-key
        'igrep-insert-default-files))
  (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)))

;; Menu bar:

(if igrep-easy-menu
    (progn
      (easy-menu-define igrep-menu nil
        "Menu keymap for igrep."
        igrep-easy-menu)
      (cond ((fboundp 'add-submenu)     ; XEmacs
             (add-submenu '("Tools") igrep-menu "Grep..."))
            ((fboundp 'easy-menu-add-item) ; Emacs 20
             (easy-menu-add-item menu-bar-tools-menu nil igrep-menu
                                 'grep))
            (t                          ; Emacs 19
             (define-key-after menu-bar-tools-menu [igrep]
               (cons (car igrep-easy-menu) igrep-menu)
               (and (lookup-key menu-bar-tools-menu [grep]) 'grep))))))

;;; Local Variables:
;;; eval: (put 'igrep-define 'lisp-indent-function 1)
;;; eval: (put 'igrep-find-define 'lisp-indent-function 1)
;;; eval: (put 'easy-menu-define 'lisp-indent-function 'defun)
;;; End:

(provide 'igrep)

;;; igrep.el ends here

reply via email to

[Prev in Thread] Current Thread [Next in Thread]