* contrib/anything-grep.el: abbreviate buffer-file-name in $buffers
[anything-config.git] / contrib / anything-grep.el
blobe0d6e75332d27103de61873e8848d0471203208e
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)
13 ;; any later version.
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.
25 ;;; Commentary:
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.
30 ;;; Commands:
32 ;; Below are complete command list:
34 ;; `anything-grep'
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/
62 ;;; History:
64 ;; $Log: anything-grep.el,v $
65 ;; Revision 1.27 2010-03-21 11:31:04 rubikitch
66 ;; Resume bug fix
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
75 ;; update copyright
77 ;; Revision 1.23 2010/03/21 06:28:32 rubikitch
78 ;; refactoring
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
89 ;; auto-document
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
106 ;; copyright
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
114 ;; Rename variables:
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
119 ;; document
121 ;; Revision 1.11 2008/12/29 07:58:37 rubikitch
122 ;; refactoring
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
150 ;; many bug fix
151 ;; New command: `anything-grep-by-name'
153 ;; Revision 1.1 2008/10/01 10:58:59 rubikitch
154 ;; Initial revision
157 ;;; Code:
159 (defvar anything-grep-version "$Id: anything-grep.el,v 1.27 2010-03-21 11:31:04 rubikitch Exp $")
160 (require 'anything-config)
161 (require 'grep)
162 (require 'format-spec)
164 (defvar anything-grep-save-buffers-before-grep nil
165 "Do `save-some-buffers' before performing `anything-grep'.")
167 (defvar anything-grep-goto-hook nil
168 "List of functions to be called after `agrep-goto' opens file.")
170 (defvar anything-grep-find-file-function 'find-file
171 "Function to visit a file with.
172 It takes one argument, a file name to visit.")
174 (defvar anything-grep-multiline t
175 "If non-nil, use multi-line display. It is prettier.
176 Use anything.el v1.147 or newer.")
178 (defvar anything-grep-fontify-file-name t
179 "If non-nil, fontify file name and line number of matches.")
181 (defvar anything-grep-sh-program
182 (or (executable-find "zsh")
183 (executable-find "sh")))
185 (defvar anything-grep-alist
186 '(("buffers" ("egrep -Hin %s $buffers" "/"))
187 ("memo" ("ack-grep -af | xargs egrep -Hin %s" "~/memo"))
188 ("PostgreSQL" ("egrep -Hin %s *.txt" "~/doc/postgresql-74/"))
189 ("~/bin and ~/ruby"
190 ("ack-grep -afG 'rb$' | xargs egrep -Hin %s" "~/ruby")
191 ("ack-grep -af | xargs egrep -Hin %s" "~/bin")))
192 "Mapping of location and command/pwd used by `anything-grep-by-name'.
193 The command is grep command line. Note that %s is replaced by query.
194 The command is typically \"ack-grep -af | xargs egrep -Hin %s\", which means
195 regexp/case-insensitive search for all files (including subdirectories)
196 except unneeded files.
197 The occurrence of $file in command is replaced with `buffer-file-name' of
198 all buffers.
200 The pwd is current directory to grep.
202 The format is:
204 ((LOCATION1
205 (COMMAND1-1 PWD1-1)
206 (COMMAND1-2 PWD1-2)
207 ...)
208 (LOCATION2
209 (COMMAND2-1 PWD2-1)
210 (COMMAND2-2 PWD2-2)
211 ...)
212 ...)
215 (defvar anything-grep-filter-command nil
216 "If non-nil, filter the result of grep command.
218 For example, normalizing many Japanese encodings to EUC-JP,
219 set this variable to \"ruby -rkconv -pe '$_.replace $_.toeuc'\".
220 The command is converting standard input to EUC-JP line by line. ")
222 (defvar anything-grep-repository-root-function (if (require 'repository-root nil t)
223 'repository-root
224 nil)
225 "*If non-nil, a function that returns the current file's repository root directory.
226 The function is called with a single string argument (a file name) and should
227 return either nil, or a string, which is the root directory of that file's repository.")
229 ;; (@* "core")
230 (defvar anything-grep-sources nil
231 "`anything-sources' for last invoked `anything-grep'.")
232 (defvar anything-grep-buffer-name nil)
233 (defun anything-grep-base (sources &optional bufname)
234 "Invoke `anything' for `anything-grep'."
235 (and anything-grep-save-buffers-before-grep
236 (save-some-buffers (not compilation-ask-about-save) nil))
237 (setq anything-grep-sources sources)
238 (setq anything-grep-buffer-name (or bufname "*anything grep*"))
239 (let ((anything-quit-if-no-candidate t)
240 (anything-compile-source-functions
241 (cons 'anything-compile-source--agrep-init anything-compile-source-functions)))
242 (anything sources nil nil nil nil bufname)))
244 ;; (anything (list (agrep-source "grep -Hin agrep anything-grep.el" default-directory) (agrep-source "grep -Hin pwd anything-grep.el" default-directory)))
245 (defvar agrep-map
246 (let ((map (make-sparse-keymap)))
247 (set-keymap-parent map anything-map)
248 (define-key map (kbd "RET") 'agrep-return)
249 (define-key map (kbd "C-o") 'agrep-next-source-or-detail)
250 (define-key map (kbd "<right>") 'agrep-next-source-or-detail)
251 map))
253 (defun agrep-source (command pwd)
254 "Anything Source of `anything-grep'."
255 `((command . ,command)
256 (pwd . ,pwd)
257 (name . ,(format "%s [%s]" command pwd))
258 (action . agrep-goto)
259 (anything-grep)
260 (candidate-number-limit . 9999)
261 (migemo)
262 (keymap . ,agrep-map)
263 ;; to inherit faces
264 (candidates-in-buffer)
265 (get-line . buffer-substring)
266 ,@(when anything-grep-multiline
267 '((multiline)
268 (real-to-display . agrep-real-to-display)))))
270 (defun agrep-return ()
271 (interactive)
272 (let ((it (anything-get-selection nil t)))
273 (if (string-match ":[0-9]+:." it)
274 (anything-exit-minibuffer)
275 (anything-set-pattern (concat it ": ")))))
276 (defun agrep-next-source-or-detail ()
277 (interactive)
278 (anything-move-selection-common
279 (lambda ()
280 (goto-char (or (re-search-forward " details .+\n" (anything-get-next-header-pos) t)
281 (anything-get-next-header-pos)
282 (point-min))))
283 'source 'next))
285 (defun anything-compile-source--agrep-init (source)
286 (if (assq 'anything-grep source)
287 (append '((init . agrep-init)
288 (candidates)) source)
289 source))
291 (defun agrep-init ()
292 (agrep-create-buffer (anything-attr 'command) (anything-attr 'pwd)))
294 (defun agrep-real-to-display (file-line-content)
295 (if (string-match ":\\([0-9]+\\):" file-line-content)
296 (format "%s:%s\n %s"
297 (substring file-line-content 0 (match-beginning 0))
298 (match-string 1 file-line-content)
299 (substring file-line-content (match-end 0)))
300 file-line-content))
302 (defvar agrep-source-local nil)
303 (defvar agrep-waiting-source nil
304 "`anything' sources to get together in `agrep-sentinel'.")
305 (defvar agrep-proc-tmpfile-alist nil)
306 (defun agrep-do-grep (command pwd)
307 "Insert result of COMMAND. The current directory is PWD.
308 GNU grep is expected for COMMAND. The grep result is colorized."
309 (let ((process-environment process-environment)
310 proc
311 (tmpfile (make-temp-file "agrep-")))
312 (when (eq grep-highlight-matches t)
313 ;; Modify `process-environment' locally bound in `call-process-shell-command'.
314 (setenv "GREP_OPTIONS" (concat (getenv "GREP_OPTIONS") " --color=always"))
315 ;; for GNU grep 2.5.1
316 (setenv "GREP_COLOR" "01;31")
317 ;; for GNU grep 2.5.1-cvs
318 (setenv "GREP_COLORS" "mt=01;31:fn=:ln=:bn=:se=:ml=:cx=:ne"))
319 (set (make-local-variable 'agrep-source-local) (anything-get-current-source))
320 (add-to-list 'agrep-waiting-source agrep-source-local)
321 (setq proc (start-process "anything-grep" (current-buffer)
322 anything-grep-sh-program "-c"
323 (format "cd %s; %s > %s" pwd command tmpfile)))
324 (push (cons proc tmpfile) agrep-proc-tmpfile-alist)
325 (set-process-sentinel proc 'agrep-sentinel)))
327 (defvar agrep-do-after-minibuffer-exit nil)
328 (defun agrep-minibuffer-exit-hook ()
329 (when agrep-do-after-minibuffer-exit
330 (run-at-time 1 nil agrep-do-after-minibuffer-exit)
331 (setq agrep-do-after-minibuffer-exit nil)))
332 (add-hook 'minibuffer-exit-hook 'agrep-minibuffer-exit-hook)
334 (defun agrep-highlight-line-after-persistent-action ()
335 (when anything-in-persistent-action
336 (anything-persistent-highlight-point (point-at-bol) (point-at-eol))))
337 (add-hook 'anything-grep-goto-hook 'agrep-highlight-line-after-persistent-action)
339 (defun agrep-show (func)
340 (if (active-minibuffer-window)
341 (setq agrep-do-after-minibuffer-exit func)
342 (funcall func)))
343 ;; (anything-grep "sleep 1; grep -Hin grep anything-grep.el" "~/src/anything-config/extensions/")
345 (defun agrep-sentinel (proc stat)
346 (with-current-buffer (process-buffer proc)
347 (setq agrep-waiting-source (delete agrep-source-local agrep-waiting-source))
348 (let ((tmpfile (assoc-default proc agrep-proc-tmpfile-alist)))
349 (insert-file-contents tmpfile)
350 (goto-char 1)
351 (delete-file tmpfile))
352 (agrep-fontify))
353 (unless agrep-waiting-source
354 ;; call anything
355 (setq agrep-proc-tmpfile-alist nil)
356 (agrep-show
357 (lambda ()
358 (let ((anything-quit-if-no-candidate (lambda () (message "No matches"))))
359 (anything anything-grep-sources nil nil nil nil anything-grep-buffer-name))))))
361 (defun agrep-fontify ()
362 "Fontify the result of `agrep-do-grep'."
363 ;; Color matches.
364 (goto-char 1)
365 (while (re-search-forward "\\(\033\\[01;31m\\)\\(.*?\\)\\(\033\\[[0-9]*m\\)" nil t)
366 (put-text-property (match-beginning 2) (match-end 2) 'face grep-match-face)
367 (replace-match "" t t nil 1)
368 (replace-match "" t t nil 3))
369 ;; Delete other escape sequences.
370 (goto-char 1)
371 (while (re-search-forward "\\(\033\\[[0-9;]*[mK]\\)" nil t)
372 (replace-match "" t t nil 0))
373 (when anything-grep-fontify-file-name
374 (goto-char 1)
375 (while (re-search-forward ":\\([0-9]+\\):" nil t)
376 (put-text-property (point-at-bol) (match-beginning 0) 'face compilation-info-face)
377 (put-text-property (match-beginning 1) (match-end 1) 'face compilation-line-face)
378 (forward-line 1))))
379 ;; (anything-grep "grep -n grep *.el" "~/emacs/init.d")
381 (defun agrep-create-buffer (command pwd)
382 "Create candidate buffer for `anything-grep'.
383 Its contents is fontified grep result."
384 (with-current-buffer (anything-candidate-buffer 'global)
385 (setq default-directory pwd)
386 (agrep-do-grep command pwd)
387 (current-buffer)))
388 ;; (display-buffer (agrep-create-buffer "grep --color=always -Hin agrep anything-grep.el" default-directory))
389 ;; (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))))
391 (defun agrep-goto (file-line-content)
392 "Visit the source for the grep result at point."
393 (if (not (string-match ":\\([0-9]+\\):" file-line-content))
394 ;; If lineno is unavailable, just open file
395 (funcall anything-grep-find-file-function
396 (expand-file-name file-line-content (anything-attr 'pwd)))
397 (save-match-data
398 (funcall anything-grep-find-file-function
399 (expand-file-name (substring file-line-content
400 0 (match-beginning 0))
401 (anything-attr 'pwd))))
402 (anything-goto-line (string-to-number (match-string 1 file-line-content))))
403 (run-hooks 'anything-grep-goto-hook))
405 ;; (@* "simple grep interface")
406 (defun anything-grep (command pwd)
407 "Run grep in `anything' buffer to narrow results.
408 It asks COMMAND for grep command line and PWD for current directory."
409 (interactive
410 (progn
411 (grep-compute-defaults)
412 (let ((default (grep-default-command)))
413 (list (read-from-minibuffer "Run grep (like this): "
414 (if current-prefix-arg
415 default grep-command)
416 nil nil 'grep-history
417 (if current-prefix-arg nil default))
418 (read-directory-name "Directory: " default-directory default-directory t)))))
419 (anything-grep-base (list (agrep-source (agrep-preprocess-command command) pwd))
420 (format "*anything grep:%s [%s]*" command (abbreviate-file-name pwd))))
421 ;; (anything-grep "grep -Hin agrep anything-grep.el" default-directory)
423 (defun agrep-preprocess-command (command)
424 (with-temp-buffer
425 (insert command)
426 (goto-char 1)
427 (when (search-forward "$buffers" nil t)
428 (delete-region (match-beginning 0) (match-end 0))
429 (insert (mapconcat 'shell-quote-argument
430 (delq nil (mapcar 'agrep-abbreviated-buffer-file-name
431 (buffer-list))) " ")))
432 (when anything-grep-filter-command
433 (goto-char (point-max))
434 (insert "|" anything-grep-filter-command))
435 (buffer-string)))
437 (defun agrep-abbreviated-buffer-file-name (b)
438 "Abbreviated buffer-file-name by `default-directory'"
439 (let ((dir-re (concat "^" (regexp-quote default-directory))))
440 (anything-aif (buffer-file-name b)
441 (and (file-exists-p it)
442 (if (string-match dir-re it)
443 (substring it (match-end 0))
444 it)))))
446 ;; (@* "grep in predefined files")
447 (defvar agbn-last-name nil
448 "The last used name by `anything-grep-by-name'.")
450 (defun agrep-by-name-read-info (&rest kinds)
451 (let* ((default (or (and (region-active-p)
452 (buffer-substring (region-beginning) (region-end)))
453 (thing-at-point 'symbol) ""))
454 (result (mapcar (lambda (kind)
455 (case kind
456 ('query (read-string
457 (format "Grep query (default:%s): " default)
458 nil nil default))
459 ('name (completing-read
460 "Grep by name: "
461 anything-grep-alist
462 nil t nil nil agbn-last-name))))
463 kinds)))
464 (deactivate-mark)
465 (if (cdr result) ; length >= 1
466 result
467 (car result))))
468 (defun anything-grep-by-name (&optional query name)
469 "Do `anything-grep' from predefined location.
470 It asks NAME for location name and QUERY."
471 (interactive (agrep-by-name-read-info 'query 'name))
472 (setq query (or query (agrep-by-name-read-info 'query)))
473 (setq name (or name (agrep-by-name-read-info 'name)))
474 (setq agbn-last-name name)
475 (anything-aif (assoc-default name anything-grep-alist)
476 (progn
477 (grep-compute-defaults)
478 (anything-grep-base
479 (mapcar (lambda (args)
480 (destructuring-bind (cmd dir) args
481 (agrep-source
482 (format-spec (agrep-preprocess-command cmd)
483 `((?s . ,(shell-quote-argument query))
484 (?a . ,query)))
485 dir)))
487 (format "*anything grep:%s [%s]" query name)))
488 (error "no such name %s" name)))
490 (defun anything-grep-by-name-reversed (&optional name query)
491 "Do `anything-grep' from predefined location.
492 It asks QUERY and NAME for location name.
494 Difference with `anything-grep-by-name' is prompt order."
495 (interactive (agrep-by-name-read-info (quote name) (quote query)))
496 (anything-grep-by-name query name))
498 ;;; repository root
499 (defun agrep-repository-root (filename)
500 "Attempt to deduce the current file's repository root directory.
501 You should customize `anything-grep-repository-root-function' and provide a function that
502 does the actual work, based of the type of SCM tool that you're using."
503 (if (null filename)
505 (let* ((directory (file-name-directory filename))
506 (repository-root (if (and anything-grep-repository-root-function
507 (functionp anything-grep-repository-root-function))
508 (apply anything-grep-repository-root-function (list filename))
509 nil)))
510 (or repository-root directory))))
512 (defun anything-grep-repository-1 (command)
513 "Run `anything-grep' in repository."
514 (interactive
515 (progn
516 (grep-compute-defaults)
517 (let ((default (grep-default-command)))
518 (list (read-from-minibuffer
519 (format "Run grep in %s (like this): "
520 (agrep-repository-root
521 (or buffer-file-name default-directory)))
522 (if current-prefix-arg
523 default grep-command)
524 nil nil 'grep-history
525 (if current-prefix-arg nil default))))))
526 (anything-grep command (agrep-repository-root (or buffer-file-name default-directory))))
528 (defun anything-grep-repository (&optional query)
529 "Do `anything-grep' from predefined location.
530 It asks NAME for location name and QUERY."
531 (interactive (list (agrep-by-name-read-info 'query)))
532 (grep-compute-defaults)
533 (anything-grep-repository-1
534 (format (concat grep-command " %s")
535 (shell-quote-argument query))))
537 ;;; visited file
538 ;;;; unit test
539 ;; (install-elisp "http://www.emacswiki.org/cgi-bin/wiki/download/el-expectations.el")
540 ;; (install-elisp "http://www.emacswiki.org/cgi-bin/wiki/download/el-mock.el")
541 (dont-compile
542 (when (fboundp 'expectations)
543 (expectations
544 (desc "agrep-by-name-read-info")
545 (expect "query1"
546 (stub read-string => "query1")
547 (agrep-by-name-read-info 'query))
548 (expect "elinit"
549 (stub completing-read => "elinit")
550 (agrep-by-name-read-info 'name))
551 (expect '("query1" "elinit")
552 (stub read-string => "query1")
553 (stub completing-read => "elinit")
554 (agrep-by-name-read-info 'query 'name))
555 (expect '("elinit" "query1")
556 (stub read-string => "query1")
557 (stub completing-read => "elinit")
558 (agrep-by-name-read-info 'name 'query))
561 (provide 'anything-grep)
563 ;; How to save (DO NOT REMOVE!!)
564 ;; (progn (magit-push) (emacswiki-post "anything-grep.el"))
565 ;;; anything-grep.el ends here