1 ;;; anything-grep.el --- search refinement of grep result with anything
2 ;; $Id: anything-grep.el,v 1.27 2010-03-21 11:31:04 rubikitch Exp $
4 ;; Copyright (C) 2008, 2009, 2010 rubikitch
6 ;; Author: rubikitch <rubikitch@ruby-lang.org>
7 ;; Keywords: convenience, unix
8 ;; URL: http://www.emacswiki.org/cgi-bin/wiki/download/anything-grep.el
10 ;; This file is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; This file is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
27 ;; Do grep in anything buffer. When we search information with grep,
28 ;; we often narrow the candidates. Let's use `anything' to do it.
32 ;; Below are complete command list:
35 ;; Run grep in `anything' buffer to narrow results.
36 ;; `anything-grep-by-name'
37 ;; Do `anything-grep' from predefined location.
38 ;; `anything-grep-by-name-reversed'
39 ;; Do `anything-grep' from predefined location.
41 ;;; Customizable Options:
43 ;; Below are customizable option list:
46 ;; `anything-grep' is simple interface to grep a query. It asks
47 ;; directory to grep. The grep process is synchronous process. You may
48 ;; have to wait when you grep the target for the first time. But once
49 ;; the target is on the disk cache, queries are grepped at lightning
50 ;; speed. Even if older Pentium4 computer, grepping from 180MB takes
51 ;; only 0.2s! GNU grep is amazingly fast.
53 ;; `anything-grep-by-name' asks query and predefined location. It is
54 ;; good idea to have ack (ack-grep), grep implemented in Perl, to
55 ;; exclude unneeded files. Such as RCS, .svn and so on.
57 ;; ack -- better than grep, a power search tool for programmers
58 ;; http://petdance.com/ack/
64 ;; $Log: anything-grep.el,v $
65 ;; Revision 1.27 2010-03-21 11:31:04 rubikitch
68 ;; Revision 1.26 2010/03/21 11:13:30 rubikitch
69 ;; `anything-grep' works asynchronously
71 ;; Revision 1.25 2010/03/21 06:34:25 rubikitch
72 ;; New function: `anything-grep-by-name-reversed'
74 ;; Revision 1.24 2010/03/21 06:28:42 rubikitch
77 ;; Revision 1.23 2010/03/21 06:28:32 rubikitch
80 ;; Revision 1.22 2009/12/28 08:56:56 rubikitch
81 ;; `anything-grep-by-name': INCOMPATIBLE!!! swap optional arguments
82 ;; `anything-grep-by-name' can utilize `repeat-complex-command'.
84 ;; Revision 1.21 2009/12/18 11:01:11 rubikitch
85 ;; `agrep-real-to-display': erase "nil" message
87 ;; Revision 1.20 2009/06/25 03:36:38 rubikitch
88 ;; `agrep-real-to-display': avoid error
91 ;; Revision 1.19 2009/02/03 21:06:49 rubikitch
92 ;; fontify file name and line number.
93 ;; New variable: `anything-grep-fontify-file-name'
95 ;; Revision 1.18 2009/02/03 20:48:12 rubikitch
96 ;; multi-line support.
97 ;; New variable: `anything-grep-multiline'
99 ;; Revision 1.17 2009/02/03 20:35:03 rubikitch
100 ;; Use `anything-quit-if-no-candidate' not to open *anything* buffer when no matches found.
102 ;; Revision 1.16 2009/01/20 09:56:19 rubikitch
103 ;; New variable: `anything-grep-filter-command'
105 ;; Revision 1.15 2009/01/03 07:04:30 rubikitch
108 ;; Revision 1.14 2009/01/02 16:00:07 rubikitch
109 ;; * Fixed invalid value of `anything-grep-alist'.
110 ;; * Implemented functionality to search all buffers with `buffer-file-name'.
111 ;; See `anything-grep-alist'.
113 ;; Revision 1.13 2008/12/29 09:43:59 rubikitch
115 ;; `agrep-goto-hook' => `anything-grep-goto-hook'
116 ;; `agrep-find-file-function' => `anything-grep-find-file-function'
118 ;; Revision 1.12 2008/12/29 09:40:23 rubikitch
121 ;; Revision 1.11 2008/12/29 07:58:37 rubikitch
124 ;; Revision 1.10 2008/10/21 18:02:02 rubikitch
125 ;; use *anything grep* buffer instead.
127 ;; Revision 1.9 2008/10/12 17:17:23 rubikitch
128 ;; `anything-grep-by-name': swapped query order
130 ;; Revision 1.8 2008/10/09 00:33:40 rubikitch
131 ;; New variable: `anything-grep-save-buffers-before-grep'
133 ;; Revision 1.7 2008/10/09 00:26:00 rubikitch
134 ;; `anything-grep-by-name': nil argument
136 ;; Revision 1.6 2008/10/05 15:43:09 rubikitch
137 ;; changed spec: `anything-grep-alist'
139 ;; Revision 1.5 2008/10/02 18:27:55 rubikitch
140 ;; Use original fontify code instead of font-lock.
141 ;; New variable: `agrep-find-file-function'
143 ;; Revision 1.4 2008/10/01 18:18:18 rubikitch
144 ;; use ack-grep command to select files for search.
146 ;; Revision 1.3 2008/10/01 17:18:59 rubikitch
147 ;; silence byte compiler
149 ;; Revision 1.2 2008/10/01 17:17:59 rubikitch
151 ;; New command: `anything-grep-by-name'
153 ;; Revision 1.1 2008/10/01 10:58:59 rubikitch
159 (defvar anything-grep-version
"$Id: anything-grep.el,v 1.27 2010-03-21 11:31:04 rubikitch Exp $")
163 (defvar anything-grep-save-buffers-before-grep nil
164 "Do `save-some-buffers' before performing `anything-grep'.")
166 (defvar anything-grep-goto-hook nil
167 "List of functions to be called after `agrep-goto' opens file.")
169 (defvar anything-grep-find-file-function
'find-file
170 "Function to visit a file with.
171 It takes one argument, a file name to visit.")
173 (defvar anything-grep-multiline t
174 "If non-nil, use multi-line display. It is prettier.
175 Use anything.el v1.147 or newer.")
177 (defvar anything-grep-fontify-file-name t
178 "If non-nil, fontify file name and line number of matches.")
180 (defvar anything-grep-alist
181 '(("buffers" ("egrep -Hin %s $buffers" "/"))
182 ("memo" ("ack-grep -af | xargs egrep -Hin %s" "~/memo"))
183 ("PostgreSQL" ("egrep -Hin %s *.txt" "~/doc/postgresql-74/"))
185 ("ack-grep -afG 'rb$' | xargs egrep -Hin %s" "~/ruby")
186 ("ack-grep -af | xargs egrep -Hin %s" "~/bin")))
187 "Mapping of location and command/pwd used by `anything-grep-by-name'.
188 The command is grep command line. Note that %s is replaced by query.
189 The command is typically \"ack-grep -af | xargs egrep -Hin %s\", which means
190 regexp/case-insensitive search for all files (including subdirectories)
191 except unneeded files.
192 The occurrence of $file in command is replaced with `buffer-file-name' of
195 The pwd is current directory to grep.
210 (defvar anything-grep-filter-command nil
211 "If non-nil, filter the result of grep command.
213 For example, normalizing many Japanese encodings to EUC-JP,
214 set this variable to \"ruby -rkconv -pe '$_.replace $_.toeuc'\".
215 The command is converting standard input to EUC-JP line by line. ")
219 (defvar anything-grep-sources nil
220 "`anything-sources' for last invoked `anything-grep'.")
221 (defun anything-grep-base (sources)
222 "Invoke `anything' for `anything-grep'."
223 (and anything-grep-save-buffers-before-grep
224 (save-some-buffers (not compilation-ask-about-save
) nil
))
225 (setq anything-grep-sources sources
)
226 (let ((anything-quit-if-no-candidate t
)
227 (anything-compile-source-functions
228 (cons 'anything-compile-source--agrep-init anything-compile-source-functions
)))
229 (anything sources nil nil nil nil
"*anything grep*")))
231 ;; (anything (list (agrep-source "grep -Hin agrep anything-grep.el" default-directory) (agrep-source "grep -Hin pwd anything-grep.el" default-directory)))
233 (defun agrep-source (command pwd
)
234 "Anything Source of `anything-grep'."
235 `((command .
,command
)
237 (name .
,(format "%s [%s]" command pwd
))
238 (action . agrep-goto
)
240 (candidate-number-limit .
9999)
243 (candidates-in-buffer)
244 (get-line . buffer-substring
)
245 ,@(when anything-grep-multiline
247 (real-to-display . agrep-real-to-display
)))))
249 (defun anything-compile-source--agrep-init (source)
250 (if (assq 'anything-grep source
)
251 (append '((init . agrep-init
)
252 (candidates)) source
)
256 (agrep-create-buffer (anything-attr 'command
) (anything-attr 'pwd
)))
258 (defun agrep-real-to-display (file-line-content)
259 (if (string-match ":\\([0-9]+\\):" file-line-content
)
261 (substring file-line-content
0 (match-beginning 0))
262 (match-string 1 file-line-content
)
263 (substring file-line-content
(match-end 0)))
266 (defvar agrep-source-local nil
)
267 (defvar agrep-waiting-source nil
268 "`anything' sources to get together in `agrep-sentinel'.")
269 (defun agrep-do-grep (command pwd
)
270 "Insert result of COMMAND. The current directory is PWD.
271 GNU grep is expected for COMMAND. The grep result is colorized."
272 (let ((process-environment process-environment
))
273 (when (eq grep-highlight-matches t
)
274 ;; Modify `process-environment' locally bound in `call-process-shell-command'.
275 (setenv "GREP_OPTIONS" (concat (getenv "GREP_OPTIONS") " --color=always"))
276 ;; for GNU grep 2.5.1
277 (setenv "GREP_COLOR" "01;31")
278 ;; for GNU grep 2.5.1-cvs
279 (setenv "GREP_COLORS" "mt=01;31:fn=:ln=:bn=:se=:ml=:cx=:ne"))
280 (set (make-local-variable 'agrep-source-local
) (anything-get-current-source))
281 (add-to-list 'agrep-waiting-source agrep-source-local
)
282 (set-process-sentinel
283 (start-process-shell-command "anything-grep" (current-buffer)
284 (format "cd %s; %s" pwd command
))
287 (defun agrep-sentinel (proc stat
)
288 (with-current-buffer (process-buffer proc
)
289 (setq agrep-waiting-source
(delete agrep-source-local agrep-waiting-source
))
291 (unless agrep-waiting-source
293 (let ((anything-quit-if-no-candidate (lambda () (message "No matches"))))
294 (anything anything-grep-sources nil nil nil nil
"*anything grep*"))))
296 (defun agrep-fontify ()
297 "Fontify the result of `agrep-do-grep'."
300 (while (re-search-forward "\\(\033\\[01;31m\\)\\(.*?\\)\\(\033\\[[0-9]*m\\)" nil t
)
301 (put-text-property (match-beginning 2) (match-end 2) 'face grep-match-face
)
302 (replace-match "" t t nil
1)
303 (replace-match "" t t nil
3))
304 ;; Delete other escape sequences.
306 (while (re-search-forward "\\(\033\\[[0-9;]*[mK]\\)" nil t
)
307 (replace-match "" t t nil
0))
308 (when anything-grep-fontify-file-name
310 (while (re-search-forward ":\\([0-9]+\\):" nil t
)
311 (put-text-property (point-at-bol) (match-beginning 0) 'face compilation-info-face
)
312 (put-text-property (match-beginning 1) (match-end 1) 'face compilation-line-face
)
314 ;; (anything-grep "grep -n grep *.el" "~/emacs/init.d")
316 (defun agrep-create-buffer (command pwd
)
317 "Create candidate buffer for `anything-grep'.
318 Its contents is fontified grep result."
319 (with-current-buffer (anything-candidate-buffer 'global
)
320 (setq default-directory pwd
)
321 (agrep-do-grep command pwd
)
323 ;; (display-buffer (agrep-create-buffer "grep --color=always -Hin agrep anything-grep.el" default-directory))
324 ;; (anything '(((name . "test") (init . (lambda () (anything-candidate-buffer (get-buffer " *anything grep:grep --color=always -Hin agrep anything-grep.el*")) )) (candidates-in-buffer) (get-line . buffer-substring))))
326 (defun agrep-goto (file-line-content)
327 "Visit the source for the grep result at point."
328 (string-match ":\\([0-9]+\\):" file-line-content
)
330 (funcall anything-grep-find-file-function
331 (expand-file-name (substring file-line-content
332 0 (match-beginning 0))
333 (anything-attr 'pwd
))))
334 (goto-line (string-to-number (match-string 1 file-line-content
)))
335 (run-hooks 'anything-grep-goto-hook
))
337 ;; (@* "simple grep interface")
338 (defun anything-grep (command pwd
)
339 "Run grep in `anything' buffer to narrow results.
340 It asks COMMAND for grep command line and PWD for current directory."
343 (grep-compute-defaults)
344 (let ((default (grep-default-command)))
345 (list (read-from-minibuffer "Run grep (like this): "
346 (if current-prefix-arg
347 default grep-command
)
348 nil nil
'grep-history
349 (if current-prefix-arg nil default
))
350 (read-directory-name "Directory: " default-directory default-directory t
)))))
351 (anything-grep-base (list (agrep-source (agrep-preprocess-command command
) pwd
))))
352 ;; (anything-grep "grep -Hin agrep anything-grep.el" default-directory)
354 (defun agrep-preprocess-command (command)
358 (when (search-forward "$buffers" nil t
)
359 (delete-region (match-beginning 0) (match-end 0))
360 (insert (mapconcat 'shell-quote-argument
361 (delq nil
(mapcar 'buffer-file-name
(buffer-list))) " ")))
362 (when anything-grep-filter-command
363 (goto-char (point-max))
364 (insert "|" anything-grep-filter-command
))
367 ;; (@* "grep in predefined files")
368 (defvar agbn-last-name nil
369 "The last used name by `anything-grep-by-name'.")
371 (defun agrep-by-name-read-info (&rest kinds
)
372 (let ((result (mapcar (lambda (kind)
374 ('query
(read-string "Grep query: "))
375 ('name
(completing-read
378 nil t nil nil agbn-last-name
))))
380 (if (cdr result
) ; length >= 1
384 (defun anything-grep-by-name (&optional query name
)
385 "Do `anything-grep' from predefined location.
386 It asks NAME for location name and QUERY."
387 (interactive (agrep-by-name-read-info 'query
'name
))
388 (setq query
(or query
(agrep-by-name-read-info 'query
)))
389 (setq name
(or name
(agrep-by-name-read-info 'name
)))
390 (setq agbn-last-name name
)
391 (anything-aif (assoc-default name anything-grep-alist
)
393 (grep-compute-defaults)
395 (mapcar (lambda (args)
396 (destructuring-bind (cmd dir
) args
397 (agrep-source (format (agrep-preprocess-command cmd
)
398 (shell-quote-argument query
)) dir
)))
400 (error "no such name %s" name
)))
402 (defun anything-grep-by-name-reversed (&optional name query
)
403 "Do `anything-grep' from predefined location.
404 It asks QUERY and NAME for location name.
406 Difference with `anything-grep-by-name' is prompt order."
407 (interactive (agrep-by-name-read-info (quote name
) (quote query
)))
408 (anything-grep-by-name query name
))
411 ;; (install-elisp "http://www.emacswiki.org/cgi-bin/wiki/download/el-expectations.el")
412 ;; (install-elisp "http://www.emacswiki.org/cgi-bin/wiki/download/el-mock.el")
414 (when (fboundp 'expectations
)
416 (desc "agrep-by-name-read-info")
418 (stub read-string
=> "query1")
419 (agrep-by-name-read-info 'query
))
421 (stub completing-read
=> "elinit")
422 (agrep-by-name-read-info 'name
))
423 (expect '("query1" "elinit")
424 (stub read-string
=> "query1")
425 (stub completing-read
=> "elinit")
426 (agrep-by-name-read-info 'query
'name
))
427 (expect '("elinit" "query1")
428 (stub read-string
=> "query1")
429 (stub completing-read
=> "elinit")
430 (agrep-by-name-read-info 'name
'query
))
433 (provide 'anything-grep
)
435 ;; How to save (DO NOT REMOVE!!)
436 ;; (emacswiki-post "anything-grep.el")
437 ;;; anything-grep.el ends here