1 ;;; anything-grep.el --- search refinement of grep result with anything
3 ;; Copyright (C) 2008-2015, 2017 rubikitch
5 ;; Author: rubikitch <rubikitch@ruby-lang.org>
6 ;; Keywords: convenience, unix
7 ;; URL: http://www.emacswiki.org/cgi-bin/wiki/download/anything-grep.el
9 ;; This file is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; This file is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to
21 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
26 ;; Do grep in anything buffer. When we search information with grep,
27 ;; we often narrow the candidates. Let's use `anything' to do it.
31 ;; Below are complete command list:
34 ;; Run grep in `anything' buffer to narrow results.
35 ;; `anything-grep-by-name'
36 ;; Do `anything-grep' from predefined location.
37 ;; `anything-grep-by-name-reversed'
38 ;; Do `anything-grep' from predefined location.
40 ;;; Customizable Options:
42 ;; Below are customizable option list:
45 ;; `anything-grep' is simple interface to grep a query. It asks
46 ;; directory to grep. The grep process is synchronous process. You may
47 ;; have to wait when you grep the target for the first time. But once
48 ;; the target is on the disk cache, queries are grepped at lightning
49 ;; speed. Even if older Pentium4 computer, grepping from 180MB takes
52 ;; `anything-grep-by-name' asks query and predefined location. It is
53 ;; good idea to have ripgrep (rg), VERY FAST grep implemented in Rust, to
54 ;; exclude unneeded files. Such as .git, .svn and so on.
56 ;; ripgrep -- ripgrep combines the usability of The Silver Searcher
57 ;; with the raw speed of grep.
58 ;; https://github.com/BurntSushi/ripgrep
62 (defvar anything-grep-version
"")
63 (require 'anything-config
)
65 (require 'format-spec
)
67 (defvar anything-grep-save-buffers-before-grep nil
68 "Do `save-some-buffers' before performing `anything-grep'.")
70 (defvar anything-grep-goto-hook nil
71 "List of functions to be called after `agrep-goto' opens file.")
73 (defvar anything-grep-find-file-function
'find-file
74 "Function to visit a file with.
75 It takes one argument, a file name to visit.")
77 (defvar anything-grep-multiline t
78 "If non-nil, use multi-line display. It is prettier.")
80 (defvar anything-grep-fontify-file-name t
81 "If non-nil, fontify file name and line number of matches.")
83 (defvar anything-grep-sh-program
84 (or (executable-find "zsh")
85 (executable-find "sh")))
87 (defvar anything-grep-alist
88 '(("buffers" ("egrep -Hin %s $buffers" "/"))
89 ("memo" ("ack-grep -af | xargs egrep -Hin %s" "~/memo"))
90 ("PostgreSQL" ("egrep -Hin %s *.txt" "~/doc/postgresql-74/"))
92 ("ack-grep -afG 'rb$' | xargs egrep -Hin %s" "~/ruby")
93 ("ack-grep -af | xargs egrep -Hin %s" "~/bin")))
94 "Mapping of location and command/pwd used by `anything-grep-by-name'.
95 The command is grep command line. Note that %s is replaced by query.
96 The command is typically \"ack-grep -af | xargs egrep -Hin %s\", which means
97 regexp/case-insensitive search for all files (including subdirectories)
98 except unneeded files.
99 The occurrence of $file in command is replaced with `buffer-file-name' of
102 The pwd is current directory to grep.
117 (defvar anything-grep-filter-command nil
118 "If non-nil, filter the result of grep command.
120 For example, normalizing many Japanese encodings to UTF-8,
121 set this variable to \"ruby -rkconv -pe '$_.replace $_.toutf8'\".
122 The command is converting standard input to UTF-8 line by line. ")
124 (defvar anything-grep-repository-root-function
(if (require 'repository-root nil t
)
127 "*If non-nil, a function that returns the current file's repository root directory.
128 The function is called with a single string argument (a file name) and should
129 return either nil, or a string, which is the root directory of that file's repository.")
132 (defvar anything-grep-sources nil
133 "`anything-sources' for last invoked `anything-grep'.")
134 (defvar anything-grep-buffer-name nil
)
135 (defun anything-grep-base (sources &optional bufname
)
136 "Invoke `anything' for `anything-grep'."
137 (and anything-grep-save-buffers-before-grep
138 (save-some-buffers (not compilation-ask-about-save
) nil
))
139 (setq anything-grep-sources sources
)
140 (setq anything-grep-buffer-name
(or bufname
"*anything grep*"))
141 (let ((anything-quit-if-no-candidate t
)
142 (anything-compile-source-functions
143 (cons 'anything-compile-source--agrep-init anything-compile-source-functions
)))
144 (anything sources nil nil nil nil bufname
)))
146 ;; (anything (list (agrep-source "grep -Hin agrep anything-grep.el" default-directory) (agrep-source "grep -Hin pwd anything-grep.el" default-directory)))
148 (let ((map (make-sparse-keymap)))
149 (set-keymap-parent map anything-map
)
150 (define-key map
(kbd "RET") 'agrep-return
)
151 (define-key map
(kbd "C-o") 'agrep-next-source-or-detail
)
152 (define-key map
(kbd "<right>") 'agrep-next-source-or-detail
)
155 (defun agrep-source (command pwd
)
156 "Anything Source of `anything-grep'."
157 `((command .
,command
)
159 (name .
,(format "%s [%s]" command pwd
))
160 (action . agrep-goto
)
162 (candidate-number-limit .
9999)
164 (keymap .
,agrep-map
)
166 (candidates-in-buffer)
167 (get-line . buffer-substring
)
168 ,@(when anything-grep-multiline
170 (real-to-display . agrep-real-to-display
)))))
172 (defun agrep-return ()
174 (let ((it (anything-get-selection)))
175 (if (or anything-grep-multiline
176 (string-match ":[0-9]+:." it
))
177 (anything-exit-minibuffer)
178 (anything-set-pattern (concat it
": ")))))
179 (defun agrep-next-source-or-detail ()
181 (anything-move-selection-common
183 (goto-char (or (re-search-forward " details .+\n" (anything-get-next-header-pos) t
)
184 (anything-get-next-header-pos)
188 (defun anything-compile-source--agrep-init (source)
189 (if (assq 'anything-grep source
)
190 (append '((init . agrep-init
)
191 (candidates)) source
)
195 (agrep-create-buffer (anything-attr 'command
) (anything-attr 'pwd
)))
197 (defun agrep-real-to-display (file-line-content)
198 (if (string-match ":\\([0-9]+\\):" file-line-content
)
200 (substring file-line-content
0 (match-beginning 0))
201 (match-string 1 file-line-content
)
202 (substring file-line-content
(match-end 0)))
205 (defvar agrep-source-local nil
)
206 (defvar agrep-waiting-source nil
207 "`anything' sources to get together in `agrep-sentinel'.")
208 (defvar agrep-proc-tmpfile-alist nil
)
209 (defun agrep-do-grep (command pwd
)
210 "Insert result of COMMAND. The current directory is PWD.
211 GNU grep is expected for COMMAND. The grep result is colorized."
212 (let ((process-environment process-environment
)
214 (tmpfile (make-temp-file "agrep-")))
215 (when grep-highlight-matches
216 ;; Modify `process-environment' locally bound in `call-process-shell-command'.
217 (setenv "GREP_OPTIONS" (concat (getenv "GREP_OPTIONS") " --color=always"))
218 ;; for GNU grep 2.5.1
219 (setenv "GREP_COLOR" "01;31")
220 ;; for GNU grep 2.5.1-cvs
221 (setenv "GREP_COLORS" "mt=01;31:fn=:ln=:bn=:se=:ml=:cx=:ne"))
222 (set (make-local-variable 'agrep-source-local
) (anything-get-current-source))
223 (add-to-list 'agrep-waiting-source agrep-source-local
)
224 (setq proc
(start-process "anything-grep" (current-buffer)
225 anything-grep-sh-program
"-c"
226 (format "cd %s; %s > %s" pwd command tmpfile
)))
227 (push (cons proc tmpfile
) agrep-proc-tmpfile-alist
)
228 (set-process-sentinel proc
'agrep-sentinel
)))
230 (defvar agrep-do-after-minibuffer-exit nil
)
231 (defun agrep-minibuffer-exit-hook ()
232 (when agrep-do-after-minibuffer-exit
233 (run-at-time 1 nil agrep-do-after-minibuffer-exit
)
234 (setq agrep-do-after-minibuffer-exit nil
)))
235 (add-hook 'minibuffer-exit-hook
'agrep-minibuffer-exit-hook
)
237 (defun agrep-highlight-line-after-persistent-action ()
238 (when anything-in-persistent-action
239 (anything-persistent-highlight-point (point-at-bol) (point-at-eol))))
240 (add-hook 'anything-grep-goto-hook
'agrep-highlight-line-after-persistent-action
)
242 (defun agrep-show (func)
243 (if (active-minibuffer-window)
244 (setq agrep-do-after-minibuffer-exit func
)
246 ;; (anything-grep "sleep 1; grep -Hin grep anything-grep.el" "~/src/anything-config/extensions/")
248 (defun agrep-sentinel (proc stat
)
249 (with-current-buffer (process-buffer proc
)
250 (setq agrep-waiting-source
(delete agrep-source-local agrep-waiting-source
))
251 (let ((tmpfile (assoc-default proc agrep-proc-tmpfile-alist
)))
252 (insert-file-contents tmpfile
)
254 (delete-file tmpfile
))
256 (unless agrep-waiting-source
258 (setq agrep-proc-tmpfile-alist nil
)
261 (let ((anything-quit-if-no-candidate (lambda () (message "No matches"))))
262 (anything anything-grep-sources nil nil nil nil anything-grep-buffer-name
))))))
264 (defun agrep-fontify ()
265 "Fontify the result of `agrep-do-grep'."
268 (while (re-search-forward "\\(\033\\[01;31m\\)\\(.*?\\)\\(\033\\[[0-9]*m\\)" nil t
)
269 (put-text-property (match-beginning 2) (match-end 2) 'face grep-match-face
)
270 (replace-match "" t t nil
1)
271 (replace-match "" t t nil
3))
272 ;; Delete other escape sequences.
274 (while (re-search-forward "\\(\033\\[[0-9;]*[mK]\\)" nil t
)
275 (replace-match "" t t nil
0))
276 (when anything-grep-fontify-file-name
278 (while (re-search-forward ":\\([0-9]+\\):" nil t
)
279 (put-text-property (point-at-bol) (match-beginning 0) 'face compilation-info-face
)
280 (put-text-property (match-beginning 1) (match-end 1) 'face compilation-line-face
)
282 ;; (anything-grep "grep -n grep *.el" "~/emacs/init.d")
284 (defun agrep-create-buffer (command pwd
)
285 "Create candidate buffer for `anything-grep'.
286 Its contents is fontified grep result."
287 (with-current-buffer (anything-candidate-buffer 'global
)
288 (setq default-directory pwd
)
289 (agrep-do-grep command pwd
)
291 ;; (display-buffer (agrep-create-buffer "grep --color=always -Hin agrep anything-grep.el" default-directory))
292 ;; (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))))
294 (defun agrep-goto (file-line-content)
295 "Visit the source for the grep result at point."
296 (if (not (string-match ":\\([0-9]+\\):" file-line-content
))
297 ;; If lineno is unavailable, just open file
298 (funcall anything-grep-find-file-function
299 (expand-file-name file-line-content
(anything-attr 'pwd
)))
301 (funcall anything-grep-find-file-function
302 (expand-file-name (substring file-line-content
303 0 (match-beginning 0))
304 (anything-attr 'pwd
))))
305 (anything-goto-line (string-to-number (match-string 1 file-line-content
))))
306 (run-hooks 'anything-grep-goto-hook
))
308 ;; (@* "simple grep interface")
309 (defun anything-grep (command pwd
)
310 "Run grep in `anything' buffer to narrow results.
311 It asks COMMAND for grep command line and PWD for current directory."
314 (grep-compute-defaults)
315 (let ((default (grep-default-command)))
316 (list (read-from-minibuffer "Run grep (like this): "
317 (if current-prefix-arg
318 default grep-command
)
319 nil nil
'grep-history
320 (if current-prefix-arg nil default
))
321 (read-directory-name "Directory: " default-directory default-directory t
)))))
322 (anything-grep-base (list (agrep-source (agrep-preprocess-command command
) pwd
))
323 (format "*anything grep:%s [%s]*" command
(abbreviate-file-name pwd
))))
324 ;; (anything-grep "grep -Hin agrep anything-grep.el" default-directory)
326 (defun agrep-preprocess-command--buffers ()
327 (when (search-forward "$buffers" nil t
)
328 (delete-region (match-beginning 0) (match-end 0))
329 (insert (mapconcat 'shell-quote-argument
330 (delq nil
(mapcar 'agrep-abbreviated-buffer-file-name
331 (buffer-list))) " "))))
332 (defun agrep-preprocess-command--filter (command)
333 (when anything-grep-filter-command
334 (goto-char (point-max))
335 (insert "|" anything-grep-filter-command
)))
337 (defun agrep-preprocess-command (command)
341 (agrep-preprocess-command--buffers)
342 (agrep-preprocess-command--filter command
)
345 (defun agrep-abbreviated-buffer-file-name (b)
346 "Abbreviated buffer-file-name by `default-directory'"
347 (let ((dir-re (concat "^" (regexp-quote default-directory
))))
348 (anything-aif (buffer-file-name b
)
349 (and (file-exists-p it
)
350 (if (string-match dir-re it
)
351 (substring it
(match-end 0))
354 ;; (@* "grep in predefined files")
355 (defvar agbn-last-name nil
356 "The last used name by `anything-grep-by-name'.")
358 (defun agrep-by-name-read-info (&rest kinds
)
359 (let* ((default (or (and (region-active-p)
360 (buffer-substring (region-beginning) (region-end)))
361 (thing-at-point 'symbol
) ""))
362 (result (mapcar (lambda (kind)
365 (format "Grep query (default:%s): " default
)
367 ('name
(completing-read
370 nil t nil nil agbn-last-name
))))
373 (if (cdr result
) ; length >= 1
376 (defun anything-grep-by-name (&optional query name
)
377 "Do `anything-grep' from predefined location.
378 It asks NAME for location name and QUERY."
379 (interactive (agrep-by-name-read-info 'query
'name
))
380 (setq query
(or query
(agrep-by-name-read-info 'query
)))
381 (setq name
(or name
(agrep-by-name-read-info 'name
)))
382 (setq agbn-last-name name
)
383 (anything-aif (assoc-default name anything-grep-alist
)
385 (grep-compute-defaults)
387 (mapcar (lambda (args)
388 (destructuring-bind (cmd dir
) args
390 (format-spec (agrep-preprocess-command cmd
)
391 `((?s .
,(shell-quote-argument query
))
395 (format "*anything grep:%s [%s]" query name
)))
396 (error "no such name %s" name
)))
398 (defun anything-grep-by-name-reversed (&optional name query
)
399 "Do `anything-grep' from predefined location.
400 It asks QUERY and NAME for location name.
402 Difference with `anything-grep-by-name' is prompt order."
403 (interactive (agrep-by-name-read-info (quote name
) (quote query
)))
404 (anything-grep-by-name query name
))
407 (defun agrep-repository-root (filename)
408 "Attempt to deduce the current file's repository root directory.
409 You should customize `anything-grep-repository-root-function' and provide a function that
410 does the actual work, based of the type of SCM tool that you're using."
413 (let* ((directory (file-name-directory filename
))
414 (repository-root (if (and anything-grep-repository-root-function
415 (functionp anything-grep-repository-root-function
))
416 (apply anything-grep-repository-root-function
(list filename
))
418 (or repository-root directory
))))
420 (defun anything-grep-repository-1 (command)
421 "Run `anything-grep' in repository."
424 (grep-compute-defaults)
425 (let ((default (grep-default-command)))
426 (list (read-from-minibuffer
427 (format "Run grep in %s (like this): "
428 (agrep-repository-root
429 (or buffer-file-name default-directory
)))
430 (if current-prefix-arg
431 default grep-command
)
432 nil nil
'grep-history
433 (if current-prefix-arg nil default
))))))
434 (anything-grep command
(agrep-repository-root (or buffer-file-name default-directory
))))
436 (defun anything-grep-repository (&optional query
)
437 "Do `anything-grep' from predefined location.
438 It asks NAME for location name and QUERY."
439 (interactive (list (agrep-by-name-read-info 'query
)))
440 (grep-compute-defaults)
441 (anything-grep-repository-1
442 (format (concat grep-command
" %s")
443 (shell-quote-argument query
))))
448 (when (fboundp 'expectations
)
450 (desc "agrep-by-name-read-info")
452 (stub read-string
=> "query1")
453 (agrep-by-name-read-info 'query
))
455 (stub completing-read
=> "elinit")
456 (agrep-by-name-read-info 'name
))
457 (expect '("query1" "elinit")
458 (stub read-string
=> "query1")
459 (stub completing-read
=> "elinit")
460 (agrep-by-name-read-info 'query
'name
))
461 (expect '("elinit" "query1")
462 (stub read-string
=> "query1")
463 (stub completing-read
=> "elinit")
464 (agrep-by-name-read-info 'name
'query
))
467 (provide 'anything-grep
)
469 ;; How to save (DO NOT REMOVE!!)
470 ;; (progn (magit-push) (emacswiki-post "anything-grep.el"))
471 ;;; anything-grep.el ends here