anything-config.el: anything-c-source-call-source: Add action: "Copy variable name"
[anything-config.git] / contrib / anything-grep.el
blob58b30e74a4f04fcab9cb1281dceb6a225561ee84
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)
12 ;; any later version.
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.
24 ;;; Commentary:
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.
29 ;;; Commands:
31 ;; Below are complete command list:
33 ;; `anything-grep'
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
50 ;; only 0.2s!
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
60 ;;; Code:
62 (defvar anything-grep-version "")
63 (require 'anything-config)
64 (require 'grep)
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-ripgrep-command
88 (when (executable-find "rg")
89 "rg -n --smart-case --no-heading")
90 "If non-nil, use ripgrep (rg) instead of standard grep.
91 ripgrep is VERY VERY FAST grep implementation.
94 (defvar anything-grep-alist
95 '(("buffers" ("egrep -Hin %s $buffers" "/"))
96 ("memo" ("ack-grep -af | xargs egrep -Hin %s" "~/memo"))
97 ("PostgreSQL" ("egrep -Hin %s *.txt" "~/doc/postgresql-74/"))
98 ("~/bin and ~/ruby"
99 ("ack-grep -afG 'rb$' | xargs egrep -Hin %s" "~/ruby")
100 ("ack-grep -af | xargs egrep -Hin %s" "~/bin")))
101 "Mapping of location and command/pwd used by `anything-grep-by-name'.
102 The command is grep command line. Note that %s is replaced by query.
103 The command is typically \"ack-grep -af | xargs egrep -Hin %s\", which means
104 regexp/case-insensitive search for all files (including subdirectories)
105 except unneeded files.
106 The occurrence of $file in command is replaced with `buffer-file-name' of
107 all buffers.
109 The pwd is current directory to grep.
111 The format is:
113 ((LOCATION1
114 (COMMAND1-1 PWD1-1)
115 (COMMAND1-2 PWD1-2)
116 ...)
117 (LOCATION2
118 (COMMAND2-1 PWD2-1)
119 (COMMAND2-2 PWD2-2)
120 ...)
121 ...)
124 (defvar anything-grep-filter-command nil
125 "If non-nil, filter the result of grep command.
127 For example, normalizing many Japanese encodings to UTF-8,
128 set this variable to \"ruby -rkconv -pe '$_.replace $_.toutf8'\".
129 The command is converting standard input to UTF-8 line by line. ")
131 (defvar anything-grep-repository-root-function (if (require 'repository-root nil t)
132 'repository-root
133 nil)
134 "*If non-nil, a function that returns the current file's repository root directory.
135 The function is called with a single string argument (a file name) and should
136 return either nil, or a string, which is the root directory of that file's repository.")
138 ;; (@* "core")
139 (defvar anything-grep-sources nil
140 "`anything-sources' for last invoked `anything-grep'.")
141 (defvar anything-grep-buffer-name nil)
142 (defun anything-grep-base (sources &optional bufname)
143 "Invoke `anything' for `anything-grep'."
144 (and anything-grep-save-buffers-before-grep
145 (save-some-buffers (not compilation-ask-about-save) nil))
146 (setq anything-grep-sources sources)
147 (setq anything-grep-buffer-name (or bufname "*anything grep*"))
148 (let ((anything-quit-if-no-candidate t)
149 (anything-compile-source-functions
150 (cons 'anything-compile-source--agrep-init anything-compile-source-functions)))
151 (anything sources nil nil nil nil bufname)))
153 ;; (anything (list (agrep-source "grep -Hin agrep anything-grep.el" default-directory) (agrep-source "grep -Hin pwd anything-grep.el" default-directory)))
154 (defvar agrep-map
155 (let ((map (make-sparse-keymap)))
156 (set-keymap-parent map anything-map)
157 (define-key map (kbd "RET") 'agrep-return)
158 (define-key map (kbd "C-o") 'agrep-next-source-or-detail)
159 (define-key map (kbd "<right>") 'agrep-next-source-or-detail)
160 map))
162 (defun agrep-source (command pwd)
163 "Anything Source of `anything-grep'."
164 `((command . ,command)
165 (pwd . ,pwd)
166 (name . ,(format "%s [%s]" command pwd))
167 (action . agrep-goto)
168 (anything-grep)
169 (candidate-number-limit . 9999)
170 (migemo)
171 (keymap . ,agrep-map)
172 ;; to inherit faces
173 (candidates-in-buffer)
174 (get-line . buffer-substring)
175 ,@(when anything-grep-multiline
176 '((multiline)
177 (real-to-display . agrep-real-to-display)))))
179 (defun agrep-return ()
180 (interactive)
181 (let ((it (anything-get-selection)))
182 (if (or anything-grep-multiline
183 (string-match ":[0-9]+:." it))
184 (anything-exit-minibuffer)
185 (anything-set-pattern (concat it ": ")))))
186 (defun agrep-next-source-or-detail ()
187 (interactive)
188 (anything-move-selection-common
189 (lambda ()
190 (goto-char (or (re-search-forward " details .+\n" (anything-get-next-header-pos) t)
191 (anything-get-next-header-pos)
192 (point-min))))
193 'source 'next))
195 (defun anything-compile-source--agrep-init (source)
196 (if (assq 'anything-grep source)
197 (append '((init . agrep-init)
198 (candidates)) source)
199 source))
201 (defun agrep-init ()
202 (agrep-create-buffer (anything-attr 'command) (anything-attr 'pwd)))
204 (defun agrep-real-to-display (file-line-content)
205 (if (string-match ":\\([0-9]+\\):" file-line-content)
206 (format "%s:%s\n %s"
207 (substring file-line-content 0 (match-beginning 0))
208 (match-string 1 file-line-content)
209 (substring file-line-content (match-end 0)))
210 file-line-content))
212 (defvar agrep-source-local nil)
213 (defvar agrep-waiting-source nil
214 "`anything' sources to get together in `agrep-sentinel'.")
215 (defvar agrep-proc-tmpfile-alist nil)
216 (defun agrep-do-grep (command pwd)
217 "Insert result of COMMAND. The current directory is PWD.
218 GNU grep is expected for COMMAND. The grep result is colorized."
219 (let ((process-environment process-environment)
220 proc
221 (tmpfile (make-temp-file "agrep-")))
222 (when grep-highlight-matches
223 ;; for GNU grep 2.5.1
224 (setenv "GREP_COLOR" "01;31")
225 ;; for GNU grep 2.5.1-cvs
226 (setenv "GREP_COLORS" "mt=01;31:fn=:ln=:bn=:se=:ml=:cx=:ne"))
227 (set (make-local-variable 'agrep-source-local) (anything-get-current-source))
228 (add-to-list 'agrep-waiting-source agrep-source-local)
229 (setq proc (start-process "anything-grep" (current-buffer)
230 anything-grep-sh-program "-c"
231 (format "cd %s; %s > %s" pwd command tmpfile)))
232 (push (cons proc tmpfile) agrep-proc-tmpfile-alist)
233 (set-process-sentinel proc 'agrep-sentinel)))
235 (defvar agrep-do-after-minibuffer-exit nil)
236 (defun agrep-minibuffer-exit-hook ()
237 (when agrep-do-after-minibuffer-exit
238 (run-at-time 1 nil agrep-do-after-minibuffer-exit)
239 (setq agrep-do-after-minibuffer-exit nil)))
240 (add-hook 'minibuffer-exit-hook 'agrep-minibuffer-exit-hook)
242 (defun agrep-highlight-line-after-persistent-action ()
243 (when anything-in-persistent-action
244 (anything-persistent-highlight-point (point-at-bol) (point-at-eol))))
245 (add-hook 'anything-grep-goto-hook 'agrep-highlight-line-after-persistent-action)
247 (defun agrep-show (func)
248 (if (active-minibuffer-window)
249 (setq agrep-do-after-minibuffer-exit func)
250 (funcall func)))
251 ;; (anything-grep "sleep 1; grep -Hin grep anything-grep.el" "~/src/anything-config/extensions/")
253 (defun agrep-sentinel (proc stat)
254 (with-current-buffer (process-buffer proc)
255 (setq agrep-waiting-source (delete agrep-source-local agrep-waiting-source))
256 (let ((tmpfile (assoc-default proc agrep-proc-tmpfile-alist)))
257 (insert-file-contents tmpfile)
258 (goto-char 1)
259 (delete-file tmpfile))
260 (agrep-fontify))
261 (unless agrep-waiting-source
262 ;; call anything
263 (setq agrep-proc-tmpfile-alist nil)
264 (agrep-show
265 (lambda ()
266 (let ((anything-quit-if-no-candidate (lambda () (message "No matches"))))
267 (anything anything-grep-sources nil nil nil nil anything-grep-buffer-name))))))
269 (defun agrep-fontify ()
270 "Fontify the result of `agrep-do-grep'."
271 ;; Color matches.
272 (goto-char 1)
273 (while (re-search-forward "\\(\033\\[01;31m\\)\\(.*?\\)\\(\033\\[[0-9]*m\\)" nil t)
274 (put-text-property (match-beginning 2) (match-end 2) 'face grep-match-face)
275 (replace-match "" t t nil 1)
276 (replace-match "" t t nil 3))
277 ;; Delete other escape sequences.
278 (goto-char 1)
279 (while (re-search-forward "\\(\033\\[[0-9;]*[mK]\\)" nil t)
280 (replace-match "" t t nil 0))
281 (when anything-grep-fontify-file-name
282 (goto-char 1)
283 (while (re-search-forward ":\\([0-9]+\\):" nil t)
284 (put-text-property (point-at-bol) (match-beginning 0) 'face compilation-info-face)
285 (put-text-property (match-beginning 1) (match-end 1) 'face compilation-line-face)
286 (forward-line 1))))
287 ;; (anything-grep "grep -n grep *.el" "~/emacs/init.d")
289 (defun agrep-create-buffer (command pwd)
290 "Create candidate buffer for `anything-grep'.
291 Its contents is fontified grep result."
292 (with-current-buffer (anything-candidate-buffer 'global)
293 (setq default-directory pwd)
294 (agrep-do-grep command pwd)
295 (current-buffer)))
296 ;; (display-buffer (agrep-create-buffer "grep --color=always -Hin agrep anything-grep.el" default-directory))
297 ;; (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))))
299 (defun agrep-goto (file-line-content)
300 "Visit the source for the grep result at point."
301 (if (not (string-match ":\\([0-9]+\\):" file-line-content))
302 ;; If lineno is unavailable, just open file
303 (funcall anything-grep-find-file-function
304 (expand-file-name file-line-content (anything-attr 'pwd)))
305 (save-match-data
306 (funcall anything-grep-find-file-function
307 (expand-file-name (substring file-line-content
308 0 (match-beginning 0))
309 (anything-attr 'pwd))))
310 (anything-goto-line (string-to-number (match-string 1 file-line-content))))
311 (run-hooks 'anything-grep-goto-hook))
313 ;; (@* "simple grep interface")
314 (defun anything-grep (command pwd)
315 "Run grep in `anything' buffer to narrow results.
316 It asks COMMAND for grep command line and PWD for current directory."
317 (interactive
318 (progn
320 (let ((default (concat (or anything-grep-ripgrep-command
321 (progn (grep-compute-defaults)
322 (grep-default-command)))
323 " ")))
324 (list (read-from-minibuffer "Run grep (like this): "
325 (if current-prefix-arg
326 default (or grep-command default))
327 nil nil 'grep-history
328 (if current-prefix-arg nil default))
329 (read-directory-name "Directory: " default-directory default-directory t)))))
330 (anything-grep-base (list (agrep-source (agrep-preprocess-command command) pwd))
331 (format "*anything grep:%s [%s]*" command (abbreviate-file-name pwd))))
332 ;; (anything-grep "grep -Hin agrep anything-grep.el" default-directory)
334 (defun agrep-preprocess-command--buffers ()
335 (when (search-forward "$buffers" nil t)
336 (delete-region (match-beginning 0) (match-end 0))
337 (insert (mapconcat 'shell-quote-argument
338 (delq nil (mapcar 'agrep-abbreviated-buffer-file-name
339 (buffer-list))) " "))))
340 (defun agrep-preprocess-command--filter (command)
341 (when anything-grep-filter-command
342 (goto-char (point-max))
343 (insert "|" anything-grep-filter-command)))
345 (defun agrep-preprocess-command (command)
346 (with-temp-buffer
347 (insert command)
348 (goto-char 1)
349 (agrep-preprocess-command--buffers)
350 (agrep-preprocess-command--filter command)
351 (buffer-string)))
353 (defun agrep-abbreviated-buffer-file-name (b)
354 "Abbreviated buffer-file-name by `default-directory'"
355 (let ((dir-re (concat "^" (regexp-quote default-directory))))
356 (anything-aif (buffer-file-name b)
357 (and (file-exists-p it)
358 (if (string-match dir-re it)
359 (substring it (match-end 0))
360 it)))))
362 ;; (@* "grep in predefined files")
363 (defvar agbn-last-name nil
364 "The last used name by `anything-grep-by-name'.")
366 (defun agrep-by-name-read-info (&rest kinds)
367 (let* ((default (or (and (region-active-p)
368 (buffer-substring (region-beginning) (region-end)))
369 (thing-at-point 'symbol) ""))
370 (result (mapcar (lambda (kind)
371 (case kind
372 ('query (read-string
373 (format "Grep query (default:%s): " default)
374 nil nil default))
375 ('name (completing-read
376 "Grep by name: "
377 anything-grep-alist
378 nil t nil nil agbn-last-name))))
379 kinds)))
380 (deactivate-mark)
381 (if (cdr result) ; length >= 1
382 result
383 (car result))))
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)
392 (progn
393 (grep-compute-defaults)
394 (anything-grep-base
395 (mapcar (lambda (args)
396 (destructuring-bind (cmd dir) args
397 (agrep-source
398 (format-spec (agrep-preprocess-command cmd)
399 `((?s . ,(shell-quote-argument query))
400 (?a . ,query)))
401 dir)))
403 (format "*anything grep:%s [%s]" query name)))
404 (error "no such name %s" name)))
406 (defun anything-grep-by-name-reversed (&optional name query)
407 "Do `anything-grep' from predefined location.
408 It asks QUERY and NAME for location name.
410 Difference with `anything-grep-by-name' is prompt order."
411 (interactive (agrep-by-name-read-info (quote name) (quote query)))
412 (anything-grep-by-name query name))
414 ;;; repository root
415 (defun agrep-repository-root (filename)
416 "Attempt to deduce the current file's repository root directory.
417 You should customize `anything-grep-repository-root-function' and provide a function that
418 does the actual work, based of the type of SCM tool that you're using."
419 (if (null filename)
421 (let* ((directory (file-name-directory filename))
422 (repository-root (if (and anything-grep-repository-root-function
423 (functionp anything-grep-repository-root-function))
424 (apply anything-grep-repository-root-function (list filename))
425 nil)))
426 (or repository-root directory))))
428 (defun anything-grep-repository-1 (command)
429 "Run `anything-grep' in repository."
430 (interactive
431 (progn
432 (grep-compute-defaults)
433 (let ((default (grep-default-command)))
434 (list (read-from-minibuffer
435 (format "Run grep in %s (like this): "
436 (agrep-repository-root
437 (or buffer-file-name default-directory)))
438 (if current-prefix-arg
439 default grep-command)
440 nil nil 'grep-history
441 (if current-prefix-arg nil default))))))
442 (anything-grep command (agrep-repository-root (or buffer-file-name default-directory))))
444 (defun anything-grep-repository (&optional query)
445 "Do `anything-grep' from predefined location.
446 It asks NAME for location name and QUERY."
447 (interactive (list (agrep-by-name-read-info 'query)))
448 (grep-compute-defaults)
449 (anything-grep-repository-1
450 (format (concat grep-command " %s")
451 (shell-quote-argument query))))
453 ;;; visited file
454 ;;;; unit test
455 (dont-compile
456 (when (fboundp 'expectations)
457 (expectations
458 (desc "agrep-by-name-read-info")
459 (expect "query1"
460 (stub read-string => "query1")
461 (agrep-by-name-read-info 'query))
462 (expect "elinit"
463 (stub completing-read => "elinit")
464 (agrep-by-name-read-info 'name))
465 (expect '("query1" "elinit")
466 (stub read-string => "query1")
467 (stub completing-read => "elinit")
468 (agrep-by-name-read-info 'query 'name))
469 (expect '("elinit" "query1")
470 (stub read-string => "query1")
471 (stub completing-read => "elinit")
472 (agrep-by-name-read-info 'name 'query))
475 (provide 'anything-grep)
477 ;; How to save (DO NOT REMOVE!!)
478 ;; (progn (magit-push) (emacswiki-post "anything-grep.el"))
479 ;;; anything-grep.el ends here