commit | author | age
|
5cb5f7
|
1 |
;;; helm-grep.el --- Helm Incremental Grep. -*- lexical-binding: t -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2012 ~ 2018 Thierry Volpiatto <thierry.volpiatto@gmail.com> |
|
4 |
|
|
5 |
;; This program is free software; you can redistribute it and/or modify |
|
6 |
;; it under the terms of the GNU General Public License as published by |
|
7 |
;; the Free Software Foundation, either version 3 of the License, or |
|
8 |
;; (at your option) any later version. |
|
9 |
|
|
10 |
;; This program is distributed in the hope that it will be useful, |
|
11 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
;; GNU General Public License for more details. |
|
14 |
|
|
15 |
;; You should have received a copy of the GNU General Public License |
|
16 |
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 |
|
|
18 |
;;; Code: |
|
19 |
(require 'cl-lib) |
|
20 |
(require 'format-spec) |
|
21 |
(require 'helm) |
|
22 |
(require 'helm-help) |
|
23 |
(require 'helm-regexp) |
|
24 |
|
|
25 |
;;; load wgrep proxy if it's available |
|
26 |
(require 'wgrep-helm nil t) |
|
27 |
|
|
28 |
(declare-function helm-buffer-list "helm-buffers") |
|
29 |
(declare-function View-quit "view") |
|
30 |
(declare-function doc-view-goto-page "doc-view" (page)) |
|
31 |
(declare-function pdf-view-goto-page "pdf-view" (page &optional window)) |
|
32 |
(declare-function helm-mm-split-pattern "helm-multi-match") |
|
33 |
(declare-function helm--ansi-color-apply "helm-lib") |
|
34 |
(defvar helm--ansi-color-regexp) |
|
35 |
|
|
36 |
|
|
37 |
(defgroup helm-grep nil |
|
38 |
"Grep related Applications and libraries for Helm." |
|
39 |
:group 'helm) |
|
40 |
|
|
41 |
(defcustom helm-grep-default-command |
|
42 |
"grep --color=always -a -d skip %e -n%cH -e %p %f" |
|
43 |
"Default grep format command for `helm-do-grep-1'. |
|
44 |
Where: |
|
45 |
'%e' format spec is for --exclude or --include grep options or |
|
46 |
ack-grep --type option. (Not mandatory) |
|
47 |
|
|
48 |
'%c' format spec is for case-fold-search, |
|
49 |
whether to use the -i option of grep. (Not mandatory) |
|
50 |
When you specify this spec, helm grep will use smartcase |
|
51 |
that is when a upcase character is found in pattern case will |
|
52 |
be respected and no '-i' option will be used, otherwise, when |
|
53 |
no upcase character is found in pattern always use '-i'. |
|
54 |
If you don't want this behavior, don't use this spec and |
|
55 |
specify or not the '-i' option. |
|
56 |
Note that with ack-grep this is not needed, just specify |
|
57 |
the '--smart-case' option. |
|
58 |
|
|
59 |
'%p' format spec is for pattern. (Mandatory) |
|
60 |
|
|
61 |
'%f' format spec is for filenames. (Mandatory) |
|
62 |
|
|
63 |
If your grep version doesn't support the --exclude/include args |
|
64 |
don't specify the '%e' format spec. |
|
65 |
|
|
66 |
Helm also support ack-grep and git-grep , |
|
67 |
here a default command example for ack-grep: |
|
68 |
|
|
69 |
\(setq helm-grep-default-command \"ack-grep -Hn --color --smart-case --no-group %e %p %f\" |
|
70 |
helm-grep-default-recurse-command \"ack-grep -H --color --smart-case --no-group %e %p %f\") |
|
71 |
|
|
72 |
You can ommit the %e spec if you don't want to be prompted for types. |
|
73 |
|
|
74 |
NOTE: Helm for ack-grep support ANSI sequences, so you can remove |
|
75 |
the \"--no-color\" option safely (recommended) |
|
76 |
However you should specify --color to enable multi matches highlighting |
|
77 |
because ack disable it when output is piped. |
|
78 |
|
|
79 |
Same for grep you can use safely the option \"--color=always\" (default). |
|
80 |
You can customize the color of matches using GREP_COLORS env var. |
|
81 |
e.g: \(setenv \"GREP_COLORS\" \"ms=30;43:mc=30;43:sl=01;37:cx=:fn=35:ln=32:bn=32:se=36\") |
|
82 |
|
|
83 |
To enable ANSI color in git-grep just add \"--color=always\". |
|
84 |
To customize the ANSI color in git-grep, GREP_COLORS have no effect, |
|
85 |
you will have to setup this in your .gitconfig: |
|
86 |
|
|
87 |
[color \"grep\"] |
|
88 |
match = black yellow |
|
89 |
|
|
90 |
where \"black\" is the foreground and \"yellow\" the background. |
|
91 |
See the git documentation for more infos. |
|
92 |
|
|
93 |
`helm-grep-default-command' and `helm-grep-default-recurse-command'are |
|
94 |
independents, so you can enable `helm-grep-default-command' with ack-grep |
|
95 |
and `helm-grep-default-recurse-command' with grep if you want to be faster |
|
96 |
on recursive grep. |
|
97 |
|
|
98 |
NOTE: Remote grepping is not available with ack-grep, |
|
99 |
and badly supported with grep because tramp handle badly |
|
100 |
repeated remote processes in a short delay (< to 5s)." |
|
101 |
:group 'helm-grep |
|
102 |
:type 'string) |
|
103 |
|
|
104 |
(defcustom helm-grep-default-recurse-command |
|
105 |
"grep --color=always -a -d recurse %e -n%cH -e %p %f" |
|
106 |
"Default recursive grep format command for `helm-do-grep-1'. |
|
107 |
See `helm-grep-default-command' for format specs and infos about ack-grep." |
|
108 |
:group 'helm-grep |
|
109 |
:type 'string) |
|
110 |
|
|
111 |
(defcustom helm-default-zgrep-command |
|
112 |
"zgrep --color=always -a -n%cH -e %p %f" |
|
113 |
"Default command for Zgrep. |
|
114 |
See `helm-grep-default-command' for infos on format specs. |
|
115 |
Option --color=always is supported and can be used safely |
|
116 |
to replace the helm internal match highlighting, |
|
117 |
see `helm-grep-default-command' for more infos." |
|
118 |
:group 'helm-grep |
|
119 |
:type 'string) |
|
120 |
|
|
121 |
(defcustom helm-pdfgrep-default-command |
|
122 |
"pdfgrep --color always -niH %s %s" |
|
123 |
"Default command for pdfgrep. |
|
124 |
Option \"--color always\" is supported starting helm version 1.7.8, |
|
125 |
when used matchs will be highlighted according to GREP_COLORS env var." |
|
126 |
:group 'helm-grep |
|
127 |
:type 'string) |
|
128 |
|
|
129 |
(defcustom helm-pdfgrep-default-recurse-command |
|
130 |
"pdfgrep --color always -rniH %s %s" |
|
131 |
"Default recurse command for pdfgrep. |
|
132 |
Option \"--color always\" is supported starting helm version 1.7.8, |
|
133 |
when used matchs will be highlighted according to GREP_COLORS env var." |
|
134 |
:group 'helm-grep |
|
135 |
:type 'string) |
|
136 |
|
|
137 |
(defcustom helm-grep-use-ioccur-style-keys t |
|
138 |
"Use Arrow keys to jump to occurences." |
|
139 |
:group 'helm-grep |
|
140 |
:type 'boolean) |
|
141 |
|
|
142 |
(defcustom helm-pdfgrep-default-read-command nil |
|
143 |
"Default command to read pdf files from pdfgrep. |
|
144 |
Where '%f' format spec is filename and '%p' is page number. |
|
145 |
e.g In Ubuntu you can set it to: |
|
146 |
|
|
147 |
\"evince --page-label=%p '%f'\" |
|
148 |
|
|
149 |
If set to nil either `doc-view-mode' or `pdf-view-mode' will be used |
|
150 |
instead of an external command." |
|
151 |
:group 'helm-grep |
|
152 |
:type 'string) |
|
153 |
|
|
154 |
(defcustom helm-grep-max-length-history 100 |
|
155 |
"Max number of elements to save in `helm-grep-history'." |
|
156 |
:group 'helm-grep |
|
157 |
:type 'integer) |
|
158 |
|
|
159 |
(defcustom helm-zgrep-file-extension-regexp |
|
160 |
".*\\(\\.gz\\|\\.bz\\|\\.xz\\|\\.lzma\\)$" |
|
161 |
"Default file extensions zgrep will search in." |
|
162 |
:group 'helm-grep |
|
163 |
:type 'string) |
|
164 |
|
|
165 |
(defcustom helm-grep-preferred-ext nil |
|
166 |
"This file extension will be preselected for grep." |
|
167 |
:group 'helm-grep |
|
168 |
:type 'string) |
|
169 |
|
|
170 |
(defcustom helm-grep-save-buffer-name-no-confirm nil |
|
171 |
"when *hgrep* already exists,auto append suffix." |
|
172 |
:group 'helm-grep |
|
173 |
:type 'boolean) |
|
174 |
|
|
175 |
(defcustom helm-grep-ignored-files |
|
176 |
(cons ".#*" (delq nil (mapcar (lambda (s) |
|
177 |
(unless (string-match-p "/\\'" s) |
|
178 |
(concat "*" s))) |
|
179 |
completion-ignored-extensions))) |
|
180 |
"List of file names which `helm-grep' shall exclude." |
|
181 |
:group 'helm-grep |
|
182 |
:type '(repeat string)) |
|
183 |
|
|
184 |
(defcustom helm-grep-ignored-directories |
|
185 |
helm-walk-ignore-directories |
|
186 |
"List of names of sub-directories which `helm-grep' shall not recurse into." |
|
187 |
:group 'helm-grep |
|
188 |
:type '(repeat string)) |
|
189 |
|
|
190 |
(defcustom helm-grep-truncate-lines t |
|
191 |
"When nil the grep line that appears will not be truncated." |
|
192 |
:group 'helm-grep |
|
193 |
:type 'boolean) |
|
194 |
|
|
195 |
(defcustom helm-grep-file-path-style 'basename |
|
196 |
"File path display style when grep results are displayed. |
|
197 |
Possible value are: |
|
198 |
basename: displays only the filename, none of the directory path |
|
199 |
absolute: displays absolute path |
|
200 |
relative: displays relative path from root grep directory." |
|
201 |
:group 'helm-grep |
|
202 |
:type '(choice (const :tag "Basename" basename) |
|
203 |
(const :tag "Absolute" absolute) |
|
204 |
(const :tag "Relative" relative))) |
|
205 |
|
|
206 |
(defcustom helm-grep-actions |
|
207 |
(helm-make-actions |
|
208 |
"Find File" 'helm-grep-action |
|
209 |
"Find file other frame" 'helm-grep-other-frame |
|
210 |
"Save results in grep buffer" 'helm-grep-save-results |
|
211 |
"Find file other window (C-u vertically)" 'helm-grep-other-window) |
|
212 |
"Actions for helm grep." |
|
213 |
:group 'helm-grep |
|
214 |
:type '(alist :key-type string :value-type function)) |
|
215 |
|
|
216 |
(defcustom helm-grep-pipe-cmd-switches nil |
|
217 |
"A list of additional parameters to pass to grep pipe command. |
|
218 |
This will be used for pipe command for multiple pattern matching |
|
219 |
for grep, zgrep ack-grep and git-grep backends. |
|
220 |
If you add extra args for ack-grep, use ack-grep options, |
|
221 |
for others (grep, zgrep and git-grep) use grep options. |
|
222 |
Here are the commands where you may want to add switches: |
|
223 |
|
|
224 |
grep --color=always |
|
225 |
ack-grep --smart-case --color |
|
226 |
|
|
227 |
You probably don't need to use this unless you know what you are doing." |
|
228 |
:group 'helm-grep |
|
229 |
:type 'string) |
|
230 |
|
|
231 |
(defcustom helm-grep-ag-pipe-cmd-switches nil |
|
232 |
"A list of additional parameters to pass to grep-ag pipe command. |
|
233 |
Use parameters compatibles with the backend you are using |
|
234 |
\(i.e AG for AG, PT for PT or RG for RG) |
|
235 |
|
|
236 |
You probably don't need to use this unless you know what you are doing." |
|
237 |
:group 'helm-grep |
|
238 |
:type 'string) |
|
239 |
|
|
240 |
|
|
241 |
;;; Faces |
|
242 |
;; |
|
243 |
;; |
|
244 |
(defgroup helm-grep-faces nil |
|
245 |
"Customize the appearance of helm-grep." |
|
246 |
:prefix "helm-" |
|
247 |
:group 'helm-grep |
|
248 |
:group 'helm-faces) |
|
249 |
|
|
250 |
(defface helm-grep-match |
|
251 |
'((((background light)) :foreground "#b00000") |
|
252 |
(((background dark)) :foreground "gold1")) |
|
253 |
"Face used to highlight grep matches. |
|
254 |
Have no effect when grep backend use \"--color=\"." |
|
255 |
:group 'helm-grep-faces) |
|
256 |
|
|
257 |
(defface helm-grep-file |
|
258 |
'((t (:foreground "BlueViolet" |
|
259 |
:underline t))) |
|
260 |
"Face used to highlight grep results filenames." |
|
261 |
:group 'helm-grep-faces) |
|
262 |
|
|
263 |
(defface helm-grep-lineno |
|
264 |
'((t (:foreground "Darkorange1"))) |
|
265 |
"Face used to highlight grep number lines." |
|
266 |
:group 'helm-grep-faces) |
|
267 |
|
|
268 |
(defface helm-grep-finish |
|
269 |
'((t (:foreground "Green"))) |
|
270 |
"Face used in mode line when grep is finish." |
|
271 |
:group 'helm-grep-faces) |
|
272 |
|
|
273 |
(defface helm-grep-cmd-line |
|
274 |
'((t (:inherit font-lock-type-face))) |
|
275 |
"Face used to highlight grep command line when no results." |
|
276 |
:group 'helm-grep-faces) |
|
277 |
|
|
278 |
|
|
279 |
;;; Keymaps |
|
280 |
;; |
|
281 |
;; |
|
282 |
(defvar helm-grep-map |
|
283 |
(let ((map (make-sparse-keymap))) |
|
284 |
(set-keymap-parent map helm-map) |
|
285 |
(define-key map (kbd "M-<down>") 'helm-goto-next-file) |
|
286 |
(define-key map (kbd "M-<up>") 'helm-goto-precedent-file) |
|
287 |
(define-key map (kbd "C-c o") 'helm-grep-run-other-window-action) |
|
288 |
(define-key map (kbd "C-c C-o") 'helm-grep-run-other-frame-action) |
|
289 |
(define-key map (kbd "C-x C-s") 'helm-grep-run-save-buffer) |
|
290 |
(define-key map (kbd "DEL") 'helm-delete-backward-no-update) |
|
291 |
(when helm-grep-use-ioccur-style-keys |
|
292 |
(define-key map (kbd "<right>") 'helm-execute-persistent-action) |
|
293 |
(define-key map (kbd "<left>") 'helm-grep-run-default-action)) |
|
294 |
(delq nil map)) |
|
295 |
"Keymap used in Grep sources.") |
|
296 |
|
|
297 |
(defvar helm-pdfgrep-map |
|
298 |
(let ((map (make-sparse-keymap))) |
|
299 |
(set-keymap-parent map helm-map) |
|
300 |
(define-key map (kbd "M-<down>") 'helm-goto-next-file) |
|
301 |
(define-key map (kbd "M-<up>") 'helm-goto-precedent-file) |
|
302 |
(define-key map (kbd "DEL") 'helm-delete-backward-no-update) |
|
303 |
map) |
|
304 |
"Keymap used in pdfgrep.") |
|
305 |
|
|
306 |
(defvar helm-grep-mode-map |
|
307 |
(let ((map (make-sparse-keymap))) |
|
308 |
(define-key map (kbd "RET") 'helm-grep-mode-jump) |
|
309 |
(define-key map (kbd "C-o") 'helm-grep-mode-jump-other-window) |
|
310 |
(define-key map (kbd "<C-down>") 'helm-grep-mode-jump-other-window-forward) |
|
311 |
(define-key map (kbd "<C-up>") 'helm-grep-mode-jump-other-window-backward) |
|
312 |
(define-key map (kbd "<M-down>") 'helm-gm-next-file) |
|
313 |
(define-key map (kbd "<M-up>") 'helm-gm-precedent-file) |
|
314 |
(define-key map (kbd "M-n") 'helm-grep-mode-jump-other-window-forward) |
|
315 |
(define-key map (kbd "M-p") 'helm-grep-mode-jump-other-window-backward) |
|
316 |
(define-key map (kbd "M-N") 'helm-gm-next-file) |
|
317 |
(define-key map (kbd "M-P") 'helm-gm-precedent-file) |
|
318 |
map)) |
|
319 |
|
|
320 |
|
|
321 |
;;; Internals vars |
|
322 |
;; |
|
323 |
;; |
|
324 |
(defvar helm-rzgrep-cache (make-hash-table :test 'equal)) |
|
325 |
(defvar helm-grep-default-function 'helm-grep-init) |
|
326 |
(defvar helm-zgrep-recurse-flag nil) |
|
327 |
(defvar helm-grep-history nil) |
|
328 |
(defvar helm-grep-ag-history nil) |
|
329 |
(defvar helm-grep-last-targets nil) |
|
330 |
(defvar helm-grep-include-files nil) |
|
331 |
(defvar helm-grep-in-recurse nil) |
|
332 |
(defvar helm-grep-use-zgrep nil) |
|
333 |
(defvar helm-grep-default-directory-fn nil |
|
334 |
"A function that should return a directory to expand candidate to. |
|
335 |
It is intended to use as a let-bound variable, DON'T set this globaly.") |
|
336 |
(defvar helm-pdfgrep-targets nil) |
|
337 |
(defvar helm-grep-last-cmd-line nil) |
|
338 |
(defvar helm-grep-split-line-regexp "^\\([[:lower:][:upper:]]?:?.*?\\):\\([0-9]+\\):\\(.*\\)") |
|
339 |
|
|
340 |
|
|
341 |
;;; Init |
|
342 |
;; |
|
343 |
;; |
|
344 |
(defun helm-grep-prepare-candidates (candidates in-directory) |
|
345 |
"Prepare filenames and directories CANDIDATES for grep command line." |
|
346 |
;; If one or more candidate is a directory, search in all files |
|
347 |
;; of this candidate (e.g /home/user/directory/*). |
|
348 |
;; If r option is enabled search also in subdidrectories. |
|
349 |
;; We need here to expand wildcards to support crap windows filenames |
|
350 |
;; as grep doesn't accept quoted wildcards (e.g "dir/*.el"). |
|
351 |
(if helm-zgrep-recurse-flag |
|
352 |
(mapconcat 'shell-quote-argument candidates " ") |
|
353 |
;; When candidate is a directory, search in all its files. |
|
354 |
;; NOTE that `file-expand-wildcards' will return also |
|
355 |
;; directories, they will be ignored by grep but not |
|
356 |
;; by ack-grep that will grep all files of this directory |
|
357 |
;; without recursing in their subdirs though, see that as a one |
|
358 |
;; level recursion with ack-grep. |
|
359 |
;; So I leave it as it is, considering it is a feature. [1] |
|
360 |
(cl-loop for i in candidates append |
|
361 |
(cond ((string-match "^git" helm-grep-default-command) |
|
362 |
(list i)) |
|
363 |
;; Candidate is a directory and we use recursion or ack. |
|
364 |
((and (file-directory-p i) |
|
365 |
(or helm-grep-in-recurse |
|
366 |
;; ack-grep accept directory [1]. |
|
367 |
(helm-grep-use-ack-p))) |
|
368 |
(list (expand-file-name i))) |
|
369 |
;; Grep doesn't support directory only when not in recurse. |
|
370 |
((file-directory-p i) |
|
371 |
(file-expand-wildcards |
|
372 |
(concat (file-name-as-directory (expand-file-name i)) "*") t)) |
|
373 |
;; Candidate is a file or wildcard and we use recursion, use the |
|
374 |
;; current directory instead of candidate. |
|
375 |
((and (or (file-exists-p i) (string-match "[*]" i)) |
|
376 |
helm-grep-in-recurse) |
|
377 |
(list (expand-file-name |
|
378 |
(directory-file-name ; Needed for windoze. |
|
379 |
(file-name-directory (directory-file-name i)))))) |
|
380 |
;; Else should be one or more file/directory |
|
381 |
;; possibly marked. |
|
382 |
;; When real is a normal filename without wildcard |
|
383 |
;; file-expand-wildcards returns a list of one file. |
|
384 |
;; wildcards should have been already handled by |
|
385 |
;; helm-read-file-name or helm-find-files but do it from |
|
386 |
;; here too in case we are called from elsewhere. |
|
387 |
(t (file-expand-wildcards i t))) into all-files ; [1] |
|
388 |
finally return |
|
389 |
(let ((files (if (file-remote-p in-directory) |
|
390 |
;; Grep don't understand tramp filenames |
|
391 |
;; use the local name. |
|
392 |
(mapcar (lambda (x) |
|
393 |
(file-remote-p x 'localname)) |
|
394 |
all-files) |
|
395 |
all-files))) |
|
396 |
;; When user mark files and use recursion with grep |
|
397 |
;; backend enabled, the loop collect on each marked |
|
398 |
;; candidate its `file-name-directory' and we endup with |
|
399 |
;; duplicates (Issue #1714). FIXME: For now as a quick fix |
|
400 |
;; I just remove dups here but I should handle this inside |
|
401 |
;; the cond above. |
|
402 |
(setq files (helm-fast-remove-dups files :test 'equal)) |
|
403 |
(if (string-match "^git" helm-grep-default-command) |
|
404 |
(mapconcat 'identity files " ") |
|
405 |
(mapconcat 'shell-quote-argument files " ")))))) |
|
406 |
|
|
407 |
(defun helm-grep-command (&optional recursive grep) |
|
408 |
(let* ((com (if recursive |
|
409 |
helm-grep-default-recurse-command |
|
410 |
helm-grep-default-command)) |
|
411 |
(exe (if grep |
|
412 |
(symbol-name grep) |
|
413 |
(and com (car (split-string com " ")))))) |
|
414 |
(if (and exe (string= exe "git")) "git-grep" exe))) |
|
415 |
|
|
416 |
(cl-defun helm-grep-use-ack-p (&key where) |
|
417 |
(let* ((rec-com (helm-grep-command t)) |
|
418 |
(norm-com (helm-grep-command)) |
|
419 |
(norm-com-ack-p (string-match "\\`ack" norm-com)) |
|
420 |
(rec-com-ack-p (and rec-com (string-match "\\`ack" rec-com)))) |
|
421 |
(cl-case where |
|
422 |
(default (and norm-com norm-com-ack-p)) |
|
423 |
(recursive (and rec-com rec-com-ack-p)) |
|
424 |
(strict (and norm-com rec-com rec-com-ack-p norm-com-ack-p)) |
|
425 |
(t (and (not (and norm-com (string= norm-com "git-grep"))) |
|
426 |
(or (and norm-com norm-com-ack-p) |
|
427 |
(and rec-com rec-com-ack-p))))))) |
|
428 |
|
|
429 |
(defun helm-grep--pipe-command-for-grep-command (smartcase pipe-switches &optional grep-cmd) |
|
430 |
(pcase (or grep-cmd (helm-grep-command)) |
|
431 |
;; Use grep for GNU regexp based tools. |
|
432 |
((or "grep" "zgrep" "git-grep") |
|
433 |
(format "grep --color=always%s %s" |
|
434 |
(if smartcase " -i" "") |
|
435 |
pipe-switches)) |
|
436 |
;; Use ack-grep for PCRE based tools. |
|
437 |
;; Sometimes ack-grep cmd is ack only. |
|
438 |
((and (pred (string-match-p "ack")) ack) |
|
439 |
(format "%s --smart-case --color %s" ack pipe-switches)))) |
|
440 |
|
|
441 |
(defun helm-grep--prepare-cmd-line (only-files &optional include zgrep) |
|
442 |
(let* ((default-directory (or helm-ff-default-directory |
|
443 |
(helm-default-directory) |
|
444 |
default-directory)) |
|
445 |
(fnargs (helm-grep-prepare-candidates |
|
446 |
only-files default-directory)) |
|
447 |
(ignored-files (unless (helm-grep-use-ack-p) |
|
448 |
(mapconcat |
|
449 |
(lambda (x) |
|
450 |
(concat "--exclude=" |
|
451 |
(shell-quote-argument x))) |
|
452 |
helm-grep-ignored-files " "))) |
|
453 |
(ignored-dirs (unless (helm-grep-use-ack-p) |
|
454 |
(mapconcat |
|
455 |
;; Need grep version >=2.5.4 |
|
456 |
;; of Gnuwin32 on windoze. |
|
457 |
(lambda (x) |
|
458 |
(concat "--exclude-dir=" |
|
459 |
(shell-quote-argument x))) |
|
460 |
helm-grep-ignored-directories " "))) |
|
461 |
(exclude (unless (helm-grep-use-ack-p) |
|
462 |
(if helm-grep-in-recurse |
|
463 |
(concat (or include ignored-files) |
|
464 |
" " ignored-dirs) |
|
465 |
ignored-files))) |
|
466 |
(types (and (helm-grep-use-ack-p) |
|
467 |
;; When %e format spec is not specified |
|
468 |
;; in `helm-grep-default-command' |
|
469 |
;; we need to pass an empty string |
|
470 |
;; to types to avoid error. |
|
471 |
(or include ""))) |
|
472 |
(smartcase (if (helm-grep-use-ack-p) |
|
473 |
"" |
|
474 |
(unless (let ((case-fold-search nil)) |
|
475 |
(string-match-p |
|
476 |
"[[:upper:]]" helm-pattern)) |
|
477 |
"i"))) |
|
478 |
(helm-grep-default-command |
|
479 |
(concat helm-grep-default-command " %m")) ; `%m' like multi. |
|
480 |
(patterns (helm-mm-split-pattern helm-pattern t)) |
|
481 |
(pipe-switches (mapconcat 'identity helm-grep-pipe-cmd-switches " ")) |
|
482 |
(pipes |
|
483 |
(helm-aif (cdr patterns) |
|
484 |
(cl-loop with pipcom = (helm-grep--pipe-command-for-grep-command |
|
485 |
smartcase pipe-switches) |
|
486 |
for p in it concat |
|
487 |
(format " | %s %s" pipcom (shell-quote-argument p))) |
|
488 |
""))) |
|
489 |
(format-spec |
|
490 |
helm-grep-default-command |
|
491 |
(delq nil |
|
492 |
(list (unless zgrep |
|
493 |
(if types |
|
494 |
(cons ?e types) |
|
495 |
(cons ?e exclude))) |
|
496 |
(cons ?c (or smartcase "")) |
|
497 |
(cons ?p (shell-quote-argument (car patterns))) |
|
498 |
(cons ?f fnargs) |
|
499 |
(cons ?m pipes)))))) |
|
500 |
|
|
501 |
(defun helm-grep-init (cmd-line) |
|
502 |
"Start an asynchronous grep process with CMD-LINE using ZGREP if non--nil." |
|
503 |
(let* ((default-directory (or helm-ff-default-directory |
|
504 |
(helm-default-directory) |
|
505 |
default-directory)) |
|
506 |
(zgrep (string-match "\\`zgrep" cmd-line)) |
|
507 |
;; Use pipe only with grep, zgrep or git-grep. |
|
508 |
(process-connection-type (and (not zgrep) (helm-grep-use-ack-p))) |
|
509 |
(tramp-verbose helm-tramp-verbose) |
|
510 |
(start-time (float-time)) |
|
511 |
(proc-name (if helm-grep-use-zgrep |
|
512 |
"Zgrep" |
|
513 |
(capitalize |
|
514 |
(if helm-grep-in-recurse |
|
515 |
(helm-grep-command t) |
|
516 |
(helm-grep-command))))) |
|
517 |
non-essential) |
|
518 |
;; Start grep process. |
|
519 |
(helm-log "Starting Grep process in directory `%s'" default-directory) |
|
520 |
(helm-log "Command line used was:\n\n%s" |
|
521 |
(concat ">>> " (propertize cmd-line 'face 'helm-grep-cmd-line) "\n\n")) |
|
522 |
(prog1 ; This function should return the process first. |
|
523 |
(start-file-process-shell-command |
|
524 |
proc-name helm-buffer cmd-line) |
|
525 |
;; Init sentinel. |
|
526 |
(set-process-sentinel |
|
527 |
(get-buffer-process helm-buffer) |
|
528 |
(lambda (process event) |
|
529 |
(let* ((err (process-exit-status process)) |
|
530 |
(noresult (= err 1))) |
|
531 |
(unless (and err (> err 0)) |
|
532 |
(helm-process-deferred-sentinel-hook |
|
533 |
process event (helm-default-directory))) |
|
534 |
(cond ((and noresult |
|
535 |
;; This is a workaround for zgrep |
|
536 |
;; that exit with code 1 |
|
537 |
;; after a certain amount of results. |
|
538 |
(with-helm-buffer (helm-empty-buffer-p))) |
|
539 |
(with-helm-buffer |
|
540 |
(insert (concat "* Exit with code 1, no result found," |
|
541 |
" command line was:\n\n " |
|
542 |
(propertize helm-grep-last-cmd-line |
|
543 |
'face 'helm-grep-cmd-line))) |
|
544 |
(setq mode-line-format |
|
545 |
`(" " mode-line-buffer-identification " " |
|
546 |
(:eval (format "L%s" (helm-candidate-number-at-point))) " " |
|
547 |
(:eval (propertize |
|
548 |
(format |
|
549 |
"[%s process finished - (no results)] " |
|
550 |
,proc-name) |
|
551 |
'face 'helm-grep-finish)))))) |
|
552 |
((or (string= event "finished\n") |
|
553 |
(and noresult |
|
554 |
;; This is a workaround for zgrep |
|
555 |
;; that exit with code 1 |
|
556 |
;; after a certain amount of results. |
|
557 |
(with-helm-buffer (not (helm-empty-buffer-p))))) |
|
558 |
(helm-log "%s process finished with %s results in %fs" |
|
559 |
proc-name |
|
560 |
(helm-get-candidate-number) |
|
561 |
(- (float-time) start-time)) |
|
562 |
(helm-maybe-show-help-echo) |
|
563 |
(with-helm-window |
|
564 |
(setq mode-line-format |
|
565 |
`(" " mode-line-buffer-identification " " |
|
566 |
(:eval (format "L%s" (helm-candidate-number-at-point))) " " |
|
567 |
(:eval (propertize |
|
568 |
(format |
|
569 |
"[%s process finished in %.2fs - (%s results)] " |
|
570 |
,proc-name |
|
571 |
,(- (float-time) start-time) |
|
572 |
(helm-get-candidate-number)) |
|
573 |
'face 'helm-grep-finish)))) |
|
574 |
(force-mode-line-update) |
|
575 |
(when helm-allow-mouse |
|
576 |
(helm--bind-mouse-for-selection helm-selection-point)))) |
|
577 |
;; Catch error output in log. |
|
578 |
(t (helm-log |
|
579 |
"Error: %s %s" |
|
580 |
proc-name |
|
581 |
(replace-regexp-in-string "\n" "" event)))))))))) |
|
582 |
|
|
583 |
(defun helm-grep-collect-candidates () |
|
584 |
(let ((cmd-line (helm-grep--prepare-cmd-line |
|
585 |
helm-grep-last-targets |
|
586 |
helm-grep-include-files |
|
587 |
helm-grep-use-zgrep))) |
|
588 |
(set (make-local-variable 'helm-grep-last-cmd-line) cmd-line) |
|
589 |
(funcall helm-grep-default-function cmd-line))) |
|
590 |
|
|
591 |
|
|
592 |
;;; Actions |
|
593 |
;; |
|
594 |
;; |
|
595 |
(defun helm-grep-action (candidate &optional where) |
|
596 |
"Define a default action for `helm-do-grep-1' on CANDIDATE. |
|
597 |
WHERE can be one of other-window, other-frame." |
|
598 |
(let* ((split (helm-grep-split-line candidate)) |
|
599 |
(split-pat (helm-mm-split-pattern helm-input)) |
|
600 |
(lineno (string-to-number (nth 1 split))) |
|
601 |
(loc-fname (or (with-current-buffer |
|
602 |
(if (eq major-mode 'helm-grep-mode) |
|
603 |
(current-buffer) |
|
604 |
helm-buffer) |
|
605 |
(get-text-property (point-at-bol) 'helm-grep-fname)) |
|
606 |
(car split))) |
|
607 |
(tramp-method (file-remote-p (or helm-ff-default-directory |
|
608 |
default-directory) 'method)) |
|
609 |
(tramp-host (file-remote-p (or helm-ff-default-directory |
|
610 |
default-directory) 'host)) |
|
611 |
(tramp-prefix (concat "/" tramp-method ":" tramp-host ":")) |
|
612 |
(fname (if tramp-host |
|
613 |
(concat tramp-prefix loc-fname) loc-fname))) |
|
614 |
(cl-case where |
|
615 |
(other-window (helm-window-show-buffers |
|
616 |
(list (find-file-noselect fname)) t)) |
|
617 |
(other-frame (find-file-other-frame fname)) |
|
618 |
(grep (helm-grep-save-results-1)) |
|
619 |
(pdf (if helm-pdfgrep-default-read-command |
|
620 |
(helm-pdfgrep-action-1 split lineno (car split)) |
|
621 |
(find-file (car split)) (if (derived-mode-p 'pdf-view-mode) |
|
622 |
(pdf-view-goto-page lineno) |
|
623 |
(doc-view-goto-page lineno)))) |
|
624 |
(t (find-file fname))) |
|
625 |
(unless (or (eq where 'grep) (eq where 'pdf)) |
|
626 |
(helm-goto-line lineno)) |
|
627 |
;; Move point to the nearest matching regexp from bol. |
|
628 |
(cl-loop for reg in split-pat |
|
629 |
when (save-excursion |
|
630 |
(condition-case _err |
|
631 |
(if helm-migemo-mode |
|
632 |
(helm-mm-migemo-forward reg (point-at-eol) t) |
|
633 |
(re-search-forward reg (point-at-eol) t)) |
|
634 |
(invalid-regexp nil))) |
|
635 |
collect (match-beginning 0) into pos-ls |
|
636 |
finally (when pos-ls (goto-char (apply #'min pos-ls)))) |
|
637 |
;; Save history |
|
638 |
(unless (or helm-in-persistent-action |
|
639 |
(eq major-mode 'helm-grep-mode) |
|
640 |
(string= helm-pattern "")) |
|
641 |
(setq helm-grep-history |
|
642 |
(cons helm-pattern |
|
643 |
(delete helm-pattern helm-grep-history))) |
|
644 |
(when (> (length helm-grep-history) |
|
645 |
helm-grep-max-length-history) |
|
646 |
(setq helm-grep-history |
|
647 |
(delete (car (last helm-grep-history)) |
|
648 |
helm-grep-history)))))) |
|
649 |
|
|
650 |
(defun helm-grep-persistent-action (candidate) |
|
651 |
"Persistent action for `helm-do-grep-1'. |
|
652 |
With a prefix arg record CANDIDATE in `mark-ring'." |
|
653 |
(helm-grep-action candidate) |
|
654 |
(helm-highlight-current-line)) |
|
655 |
|
|
656 |
(defun helm-grep-other-window (candidate) |
|
657 |
"Jump to result in other window from helm grep." |
|
658 |
(helm-grep-action candidate 'other-window)) |
|
659 |
|
|
660 |
(defun helm-grep-other-frame (candidate) |
|
661 |
"Jump to result in other frame from helm grep." |
|
662 |
(helm-grep-action candidate 'other-frame)) |
|
663 |
|
|
664 |
(defun helm-goto-next-or-prec-file (n) |
|
665 |
"Go to next or precedent candidate file in helm grep/etags buffers. |
|
666 |
If N is positive go forward otherwise go backward." |
|
667 |
(let* ((allow-mode (or (eq major-mode 'helm-grep-mode) |
|
668 |
(eq major-mode 'helm-moccur-mode))) |
|
669 |
(sel (if allow-mode |
|
670 |
(buffer-substring (point-at-bol) (point-at-eol)) |
|
671 |
(helm-get-selection nil t))) |
|
672 |
(current-line-list (helm-grep-split-line sel)) |
|
673 |
(current-fname (nth 0 current-line-list)) |
|
674 |
(bob-or-eof (if (eq n 1) 'eobp 'bobp)) |
|
675 |
(mark-maybe (lambda () |
|
676 |
(if allow-mode |
|
677 |
(ignore) |
|
678 |
(helm-mark-current-line))))) |
|
679 |
(catch 'break |
|
680 |
(while (not (funcall bob-or-eof)) |
|
681 |
(forward-line n) ; Go forward or backward depending of n value. |
|
682 |
;; Exit when current-fname is not matched or in `helm-grep-mode' |
|
683 |
;; the line is not a grep line i.e 'fname:num:tag'. |
|
684 |
(setq sel (buffer-substring (point-at-bol) (point-at-eol))) |
|
685 |
(when helm-allow-mouse |
|
686 |
(helm--mouse-reset-selection-help-echo)) |
|
687 |
(unless (or (string= current-fname |
|
688 |
(car (helm-grep-split-line sel))) |
|
689 |
(and (eq major-mode 'helm-grep-mode) |
|
690 |
(not (get-text-property (point-at-bol) 'helm-grep-fname)))) |
|
691 |
(funcall mark-maybe) |
|
692 |
(throw 'break nil)))) |
|
693 |
(cond ((and (> n 0) (eobp)) |
|
694 |
(re-search-backward ".") |
|
695 |
(forward-line 0) |
|
696 |
(funcall mark-maybe)) |
|
697 |
((and (< n 0) (bobp)) |
|
698 |
(helm-aif (next-single-property-change (point-at-bol) 'helm-grep-fname) |
|
699 |
(goto-char it) |
|
700 |
(forward-line 1)) |
|
701 |
(funcall mark-maybe))) |
|
702 |
(unless allow-mode |
|
703 |
(helm-follow-execute-persistent-action-maybe) |
|
704 |
(helm-log-run-hook 'helm-move-selection-after-hook)))) |
|
705 |
|
|
706 |
;;;###autoload |
|
707 |
(defun helm-goto-precedent-file () |
|
708 |
"Go to precedent file in helm grep/etags buffers." |
|
709 |
(interactive) |
|
710 |
(with-helm-alive-p |
|
711 |
(with-helm-window |
|
712 |
(helm-goto-next-or-prec-file -1)))) |
|
713 |
(put 'helm-goto-precedent-file 'helm-only t) |
|
714 |
|
|
715 |
;;;###autoload |
|
716 |
(defun helm-goto-next-file () |
|
717 |
"Go to precedent file in helm grep/etags buffers." |
|
718 |
(interactive) |
|
719 |
(with-helm-window |
|
720 |
(helm-goto-next-or-prec-file 1))) |
|
721 |
|
|
722 |
(defun helm-grep-run-default-action () |
|
723 |
"Run grep default action from `helm-do-grep-1'." |
|
724 |
(interactive) |
|
725 |
(with-helm-alive-p |
|
726 |
(helm-exit-and-execute-action 'helm-grep-action))) |
|
727 |
(put 'helm-grep-run-default-action 'helm-only t) |
|
728 |
|
|
729 |
(defun helm-grep-run-other-window-action () |
|
730 |
"Run grep goto other window action from `helm-do-grep-1'." |
|
731 |
(interactive) |
|
732 |
(with-helm-alive-p |
|
733 |
(helm-exit-and-execute-action 'helm-grep-other-window))) |
|
734 |
(put 'helm-grep-run-other-window-action 'helm-only t) |
|
735 |
|
|
736 |
(defun helm-grep-run-other-frame-action () |
|
737 |
"Run grep goto other frame action from `helm-do-grep-1'." |
|
738 |
(interactive) |
|
739 |
(with-helm-alive-p |
|
740 |
(helm-exit-and-execute-action 'helm-grep-other-frame))) |
|
741 |
(put 'helm-grep-run-other-frame-action 'helm-only t) |
|
742 |
|
|
743 |
(defun helm-grep-run-save-buffer () |
|
744 |
"Run grep save results action from `helm-do-grep-1'." |
|
745 |
(interactive) |
|
746 |
(with-helm-alive-p |
|
747 |
(helm-exit-and-execute-action 'helm-grep-save-results))) |
|
748 |
(put 'helm-grep-run-save-buffer 'helm-only t) |
|
749 |
|
|
750 |
|
|
751 |
;;; helm-grep-mode |
|
752 |
;; |
|
753 |
;; |
|
754 |
(defun helm-grep-save-results (candidate) |
|
755 |
(helm-grep-action candidate 'grep)) |
|
756 |
|
|
757 |
(defun helm-grep-save-results-1 () |
|
758 |
"Save helm grep result in a `helm-grep-mode' buffer." |
|
759 |
(let ((buf "*hgrep*") |
|
760 |
new-buf |
|
761 |
(pattern (with-helm-buffer helm-input-local)) |
|
762 |
(src-name (assoc-default 'name (helm-get-current-source)))) |
|
763 |
(when (get-buffer buf) |
|
764 |
(if helm-grep-save-buffer-name-no-confirm |
|
765 |
(setq new-buf (format "*hgrep|%s|-%s" pattern |
|
766 |
(format-time-string "%H-%M-%S*"))) |
|
767 |
(setq new-buf (helm-read-string "GrepBufferName: " buf)) |
|
768 |
(cl-loop for b in (helm-buffer-list) |
|
769 |
when (and (string= new-buf b) |
|
770 |
(not (y-or-n-p |
|
771 |
(format "Buffer `%s' already exists overwrite? " |
|
772 |
new-buf)))) |
|
773 |
do (setq new-buf (helm-read-string "GrepBufferName: " "*hgrep ")))) |
|
774 |
(setq buf new-buf)) |
|
775 |
(with-current-buffer (get-buffer-create buf) |
|
776 |
(setq default-directory (or helm-ff-default-directory |
|
777 |
(helm-default-directory) |
|
778 |
default-directory)) |
|
779 |
(setq buffer-read-only t) |
|
780 |
(let ((inhibit-read-only t) |
|
781 |
(map (make-sparse-keymap))) |
|
782 |
(erase-buffer) |
|
783 |
(insert "-*- mode: helm-grep -*-\n\n" |
|
784 |
(format "%s Results for `%s':\n\n" src-name pattern)) |
|
785 |
(save-excursion |
|
786 |
(insert (with-current-buffer helm-buffer |
|
787 |
(goto-char (point-min)) (forward-line 1) |
|
788 |
(buffer-substring (point) (point-max))))) |
|
789 |
(save-excursion |
|
790 |
(while (not (eobp)) |
|
791 |
(add-text-properties (point-at-bol) (point-at-eol) |
|
792 |
`(keymap ,map |
|
793 |
help-echo ,(concat |
|
794 |
(get-text-property |
|
795 |
(point) 'helm-grep-fname) |
|
796 |
"\nmouse-1: set point\nmouse-2: jump to selection") |
|
797 |
mouse-face highlight)) |
|
798 |
(define-key map [mouse-1] 'mouse-set-point) |
|
799 |
(define-key map [mouse-2] 'helm-grep-mode-mouse-jump) |
|
800 |
(define-key map [mouse-3] 'ignore) |
|
801 |
(forward-line 1)))) |
|
802 |
(helm-grep-mode)) |
|
803 |
(pop-to-buffer buf) |
|
804 |
(message "Helm %s Results saved in `%s' buffer" src-name buf))) |
|
805 |
|
|
806 |
(defun helm-grep-mode-mouse-jump (event) |
|
807 |
(interactive "e") |
|
808 |
(let* ((window (posn-window (event-end event))) |
|
809 |
(pos (posn-point (event-end event)))) |
|
810 |
(with-selected-window window |
|
811 |
(when (eq major-mode 'helm-grep-mode) |
|
812 |
(goto-char pos) |
|
813 |
(helm-grep-mode-jump))))) |
|
814 |
(put 'helm-grep-mode-mouse-jump 'helm-only t) |
|
815 |
|
|
816 |
(define-derived-mode helm-grep-mode |
|
817 |
special-mode "helm-grep" |
|
818 |
"Major mode to provide actions in helm grep saved buffer. |
|
819 |
|
|
820 |
Special commands: |
|
821 |
\\{helm-grep-mode-map}" |
|
822 |
(set (make-local-variable 'helm-grep-last-cmd-line) |
|
823 |
(with-helm-buffer helm-grep-last-cmd-line)) |
|
824 |
(set (make-local-variable 'revert-buffer-function) |
|
825 |
#'helm-grep-mode--revert-buffer-function)) |
|
826 |
(put 'helm-grep-mode 'helm-only t) |
|
827 |
|
|
828 |
(defun helm-grep-mode--revert-buffer-function (&optional _ignore-auto _noconfirm) |
|
829 |
(goto-char (point-min)) |
|
830 |
(when (re-search-forward helm-grep-split-line-regexp nil t) (forward-line 0)) |
|
831 |
(let ((inhibit-read-only t)) |
|
832 |
(delete-region (point) (point-max))) |
|
833 |
(message "Reverting buffer...") |
|
834 |
(let ((process-connection-type |
|
835 |
;; Git needs a nil value otherwise it tries to use a pager. |
|
836 |
(null (string-match-p "\\`git" helm-grep-last-cmd-line)))) |
|
837 |
(set-process-sentinel |
|
838 |
(start-file-process-shell-command |
|
839 |
"hgrep" (generate-new-buffer "*hgrep revert*") helm-grep-last-cmd-line) |
|
840 |
'helm-grep-mode--sentinel))) |
|
841 |
|
|
842 |
(defun helm-grep-mode--sentinel (process event) |
|
843 |
(when (string= event "finished\n") |
|
844 |
(with-current-buffer (current-buffer) |
|
845 |
(let ((inhibit-read-only t)) |
|
846 |
(save-excursion |
|
847 |
(cl-loop for l in (with-current-buffer (process-buffer process) |
|
848 |
(prog1 (split-string (buffer-string) "\n") |
|
849 |
(kill-buffer))) |
|
850 |
for line = (if (string-match-p helm--ansi-color-regexp l) |
|
851 |
(helm--ansi-color-apply l) l) |
|
852 |
when (string-match helm-grep-split-line-regexp line) |
|
853 |
do (insert (propertize |
|
854 |
(car (helm-grep-filter-one-by-one line)) |
|
855 |
;; needed for wgrep. |
|
856 |
'helm-realvalue line) |
|
857 |
"\n")))) |
|
858 |
(message "Reverting buffer done")))) |
|
859 |
|
|
860 |
(defun helm-gm-next-file () |
|
861 |
(interactive) |
|
862 |
(helm-goto-next-or-prec-file 1)) |
|
863 |
|
|
864 |
(defun helm-gm-precedent-file () |
|
865 |
(interactive) |
|
866 |
(helm-goto-next-or-prec-file -1)) |
|
867 |
|
|
868 |
(defun helm-grep-mode-jump () |
|
869 |
(interactive) |
|
870 |
(helm-grep-action |
|
871 |
(buffer-substring (point-at-bol) (point-at-eol))) |
|
872 |
(helm-match-line-cleanup-pulse)) |
|
873 |
|
|
874 |
(defun helm-grep-mode-jump-other-window-1 (arg) |
|
875 |
(let ((candidate (buffer-substring (point-at-bol) (point-at-eol)))) |
|
876 |
(condition-case nil |
|
877 |
(progn |
|
878 |
(save-selected-window |
|
879 |
(helm-grep-action candidate 'other-window) |
|
880 |
(helm-match-line-cleanup-pulse) |
|
881 |
(recenter)) |
|
882 |
(forward-line arg)) |
|
883 |
(error nil)))) |
|
884 |
|
|
885 |
(defun helm-grep-mode-jump-other-window-forward () |
|
886 |
(interactive) |
|
887 |
(helm-grep-mode-jump-other-window-1 1)) |
|
888 |
|
|
889 |
(defun helm-grep-mode-jump-other-window-backward () |
|
890 |
(interactive) |
|
891 |
(helm-grep-mode-jump-other-window-1 -1)) |
|
892 |
|
|
893 |
(defun helm-grep-mode-jump-other-window () |
|
894 |
(interactive) |
|
895 |
(let ((candidate (buffer-substring (point-at-bol) (point-at-eol)))) |
|
896 |
(condition-case nil |
|
897 |
(progn (helm-grep-action candidate 'other-window) |
|
898 |
(helm-match-line-cleanup-pulse)) |
|
899 |
(error nil)))) |
|
900 |
|
|
901 |
|
|
902 |
;;; ack-grep types |
|
903 |
;; |
|
904 |
;; |
|
905 |
(defun helm-grep-hack-types () |
|
906 |
"Return a list of known ack-grep types." |
|
907 |
(with-temp-buffer |
|
908 |
;; "--help-types" works with both 1.96 and 2.1+, while |
|
909 |
;; "--help types" works only with 1.96 Issue #422. |
|
910 |
;; `helm-grep-command' should return the ack executable |
|
911 |
;; when this function is used in the right context |
|
912 |
;; i.e After checking is we are using ack-grep with |
|
913 |
;; `helm-grep-use-ack-p'. |
|
914 |
(call-process (helm-grep-command t) nil t nil "--help-types") |
|
915 |
(goto-char (point-min)) |
|
916 |
(cl-loop while (re-search-forward |
|
917 |
"^ *--\\(\\[no\\]\\)\\([^. ]+\\) *\\(.*\\)" nil t) |
|
918 |
collect (cons (concat (match-string 2) |
|
919 |
" [" (match-string 3) "]") |
|
920 |
(match-string 2)) |
|
921 |
collect (cons (concat "no" (match-string 2) |
|
922 |
" [" (match-string 3) "]") |
|
923 |
(concat "no" (match-string 2)))))) |
|
924 |
|
|
925 |
(defun helm-grep-ack-types-transformer (candidates _source) |
|
926 |
(cl-loop for i in candidates |
|
927 |
if (stringp i) |
|
928 |
collect (rassoc i helm-grep-ack-types-cache) |
|
929 |
else |
|
930 |
collect i)) |
|
931 |
|
|
932 |
(defvar helm-grep-ack-types-cache nil) |
|
933 |
(defun helm-grep-read-ack-type () |
|
934 |
"Select types for the '--type' argument of ack-grep." |
|
935 |
(require 'helm-mode) |
|
936 |
(require 'helm-adaptive) |
|
937 |
(setq helm-grep-ack-types-cache (helm-grep-hack-types)) |
|
938 |
(let ((types (helm-comp-read |
|
939 |
"Types: " helm-grep-ack-types-cache |
|
940 |
:name "*Ack-grep types*" |
|
941 |
:marked-candidates t |
|
942 |
:must-match t |
|
943 |
:fc-transformer '(helm-adaptive-sort |
|
944 |
helm-grep-ack-types-transformer) |
|
945 |
:buffer "*helm ack-types*"))) |
|
946 |
(mapconcat (lambda (type) (concat "--type=" type)) types " "))) |
|
947 |
|
|
948 |
|
|
949 |
;;; grep extensions |
|
950 |
;; |
|
951 |
;; |
|
952 |
(defun helm-grep-guess-extensions (files) |
|
953 |
"Try to guess file extensions in FILES list when using grep recurse. |
|
954 |
These extensions will be added to command line with --include arg of grep." |
|
955 |
(cl-loop with ext-list = (list helm-grep-preferred-ext "*") |
|
956 |
with lst = (if (file-directory-p (car files)) |
|
957 |
(directory-files |
|
958 |
(car files) nil |
|
959 |
directory-files-no-dot-files-regexp) |
|
960 |
files) |
|
961 |
for i in lst |
|
962 |
for ext = (file-name-extension i 'dot) |
|
963 |
for glob = (and ext (not (string= ext "")) |
|
964 |
(concat "*" ext)) |
|
965 |
unless (or (not glob) |
|
966 |
(and glob-list (member glob glob-list)) |
|
967 |
(and glob-list (member glob ext-list)) |
|
968 |
(and glob-list (member glob helm-grep-ignored-files))) |
|
969 |
collect glob into glob-list |
|
970 |
finally return (delq nil (append ext-list glob-list)))) |
|
971 |
|
|
972 |
(defun helm-grep-get-file-extensions (files) |
|
973 |
"Try to return a list of file extensions to pass to '--include' arg of grep." |
|
974 |
(require 'helm-adaptive) |
|
975 |
(let* ((all-exts (helm-grep-guess-extensions |
|
976 |
(mapcar 'expand-file-name files))) |
|
977 |
(extensions (helm-comp-read "Search Only in: " all-exts |
|
978 |
:marked-candidates t |
|
979 |
:fc-transformer 'helm-adaptive-sort |
|
980 |
:buffer "*helm grep exts*" |
|
981 |
:name "*helm grep extensions*"))) |
|
982 |
(when (listp extensions) ; Otherwise it is empty string returned by C-RET. |
|
983 |
;; If extensions is a list of one string containing spaces, |
|
984 |
;; assume user entered more than one glob separated by space(s) and |
|
985 |
;; split this string to pass it later to mapconcat. |
|
986 |
;; e.g '("*.el *.py") |
|
987 |
(cl-loop for i in extensions |
|
988 |
append (split-string-and-unquote i " "))))) |
|
989 |
|
|
990 |
|
|
991 |
;;; Set up source |
|
992 |
;; |
|
993 |
;; |
|
994 |
(defvar helm-grep-before-init-hook nil |
|
995 |
"Hook that runs before initialization of the helm buffer.") |
|
996 |
|
|
997 |
(defvar helm-grep-after-init-hook nil |
|
998 |
"Hook that runs after initialization of the helm buffer.") |
|
999 |
|
|
1000 |
(defclass helm-grep-class (helm-source-async) |
|
1001 |
((candidates-process :initform 'helm-grep-collect-candidates) |
|
1002 |
(filter-one-by-one :initform 'helm-grep-filter-one-by-one) |
|
1003 |
(keymap :initform helm-grep-map) |
|
1004 |
(pcre :initarg :pcre :initform nil |
|
1005 |
:documentation |
|
1006 |
" Backend is using pcre regexp engine when non--nil.") |
|
1007 |
(nohighlight :initform t) |
|
1008 |
(nomark :initform t) |
|
1009 |
(backend :initarg :backend |
|
1010 |
:initform nil |
|
1011 |
:documentation |
|
1012 |
" The grep backend that will be used. |
|
1013 |
It is actually used only as an internal flag |
|
1014 |
and don't set the backend by itself. |
|
1015 |
You probably don't want to modify this.") |
|
1016 |
(candidate-number-limit :initform 9999) |
|
1017 |
(help-message :initform 'helm-grep-help-message) |
|
1018 |
(history :initform 'helm-grep-history) |
|
1019 |
(action :initform 'helm-grep-actions) |
|
1020 |
(persistent-action :initform 'helm-grep-persistent-action) |
|
1021 |
(persistent-help :initform "Jump to line (`C-u' Record in mark ring)") |
|
1022 |
(requires-pattern :initform 2) |
|
1023 |
(before-init-hook :initform 'helm-grep-before-init-hook) |
|
1024 |
(after-init-hook :initform 'helm-grep-after-init-hook) |
|
1025 |
(group :initform 'helm-grep))) |
|
1026 |
|
|
1027 |
(defvar helm-source-grep nil) |
|
1028 |
|
|
1029 |
(defmethod helm--setup-source ((source helm-grep-class)) |
|
1030 |
(call-next-method) |
|
1031 |
(helm-aif (and helm-follow-mode-persistent |
|
1032 |
(if (eq (slot-value source 'backend) 'git) |
|
1033 |
helm-source-grep-git |
|
1034 |
helm-source-grep)) |
|
1035 |
(setf (slot-value source 'follow) |
|
1036 |
(assoc-default 'follow it)))) |
|
1037 |
|
|
1038 |
(cl-defun helm-do-grep-1 (targets &optional recurse backend exts |
|
1039 |
default-input input (source 'helm-source-grep)) |
|
1040 |
"Launch helm using backend BACKEND on a list of TARGETS files. |
|
1041 |
|
|
1042 |
When RECURSE is given and BACKEND is 'grep' use -r option of |
|
1043 |
BACKEND and prompt user for EXTS to set the --include args of BACKEND. |
|
1044 |
Interactively you can give more than one arg separated by space at prompt. |
|
1045 |
e.g |
|
1046 |
$Pattern: *.el *.py *.tex |
|
1047 |
|
|
1048 |
From lisp use the EXTS argument as a list of extensions as above. |
|
1049 |
If you are using ack-grep, you will be prompted for --type |
|
1050 |
instead and EXTS will be ignored. |
|
1051 |
If prompt is empty `helm-grep-ignored-files' are added to --exclude. |
|
1052 |
|
|
1053 |
Argument DEFAULT-INPUT is use as `default' arg of `helm' and INPUT |
|
1054 |
is used as `input' arg of `helm', See `helm' docstring. |
|
1055 |
|
|
1056 |
Arg BACKEND when non--nil specify which backend to use |
|
1057 |
It is used actually to specify 'zgrep' or 'git'. |
|
1058 |
When BACKEND 'zgrep' is used don't prompt for a choice |
|
1059 |
in recurse, and ignore EXTS, search being made recursively on files matching |
|
1060 |
`helm-zgrep-file-extension-regexp' only." |
|
1061 |
(let* (non-essential |
|
1062 |
(ack-rec-p (helm-grep-use-ack-p :where 'recursive)) |
|
1063 |
(exts (and recurse |
|
1064 |
;; [FIXME] I could handle this from helm-walk-directory. |
|
1065 |
(not (eq backend 'zgrep)) ; zgrep doesn't handle -r opt. |
|
1066 |
(not ack-rec-p) |
|
1067 |
(or exts (helm-grep-get-file-extensions targets)))) |
|
1068 |
(include-files |
|
1069 |
(and exts |
|
1070 |
(mapconcat (lambda (x) |
|
1071 |
(concat "--include=" |
|
1072 |
(shell-quote-argument x))) |
|
1073 |
(if (> (length exts) 1) |
|
1074 |
(remove "*" exts) |
|
1075 |
exts) " "))) |
|
1076 |
(types (and (not include-files) |
|
1077 |
(not (eq backend 'zgrep)) |
|
1078 |
recurse |
|
1079 |
ack-rec-p |
|
1080 |
;; When %e format spec is not specified |
|
1081 |
;; ignore types and do not prompt for choice. |
|
1082 |
(string-match "%e" helm-grep-default-command) |
|
1083 |
(helm-grep-read-ack-type))) |
|
1084 |
(src-name (capitalize (helm-grep-command recurse backend))) |
|
1085 |
(com (cond ((eq backend 'zgrep) helm-default-zgrep-command) |
|
1086 |
((eq backend 'git) helm-grep-git-grep-command) |
|
1087 |
(recurse helm-grep-default-recurse-command) |
|
1088 |
;; When resuming, the local value of |
|
1089 |
;; `helm-grep-default-command' is used, only git-grep |
|
1090 |
;; should need this. |
|
1091 |
(t helm-grep-default-command)))) |
|
1092 |
;; When called as action from an other source e.g *-find-files |
|
1093 |
;; we have to kill action buffer. |
|
1094 |
(when (get-buffer helm-action-buffer) |
|
1095 |
(kill-buffer helm-action-buffer)) |
|
1096 |
;; If `helm-find-files' haven't already started, |
|
1097 |
;; give a default value to `helm-ff-default-directory' |
|
1098 |
;; and set locally `default-directory' to this value . See below [1]. |
|
1099 |
(unless helm-ff-default-directory |
|
1100 |
(setq helm-ff-default-directory default-directory)) |
|
1101 |
;; We need to store these vars locally |
|
1102 |
;; to pass infos later to `helm-resume'. |
|
1103 |
(helm-set-local-variable |
|
1104 |
'helm-zgrep-recurse-flag (and recurse (eq backend 'zgrep)) |
|
1105 |
'helm-grep-last-targets targets |
|
1106 |
'helm-grep-include-files (or include-files types) |
|
1107 |
'helm-grep-in-recurse recurse |
|
1108 |
'helm-grep-use-zgrep (eq backend 'zgrep) |
|
1109 |
'helm-grep-default-command com |
|
1110 |
'default-directory helm-ff-default-directory) ;; [1] |
|
1111 |
;; Setup the source. |
|
1112 |
(set source (helm-make-source src-name 'helm-grep-class |
|
1113 |
:backend backend |
|
1114 |
:pcre (string-match-p "\\`ack" com))) |
|
1115 |
(helm |
|
1116 |
:sources source |
|
1117 |
:buffer (format "*helm %s*" (helm-grep-command recurse backend)) |
|
1118 |
:default default-input |
|
1119 |
:input input |
|
1120 |
:keymap helm-grep-map |
|
1121 |
:history 'helm-grep-history |
|
1122 |
:truncate-lines helm-grep-truncate-lines))) |
|
1123 |
|
|
1124 |
|
|
1125 |
;;; zgrep |
|
1126 |
;; |
|
1127 |
;; |
|
1128 |
(defun helm-ff-zgrep-1 (flist recursive) |
|
1129 |
(unwind-protect |
|
1130 |
(let* ((def-dir (or helm-ff-default-directory |
|
1131 |
default-directory)) |
|
1132 |
(only (if recursive |
|
1133 |
(or (gethash def-dir helm-rzgrep-cache) |
|
1134 |
(puthash |
|
1135 |
def-dir |
|
1136 |
(helm-walk-directory |
|
1137 |
def-dir |
|
1138 |
:directories nil |
|
1139 |
:path 'full |
|
1140 |
:match helm-zgrep-file-extension-regexp) |
|
1141 |
helm-rzgrep-cache)) |
|
1142 |
flist))) |
|
1143 |
(helm-do-grep-1 only recursive 'zgrep)) |
|
1144 |
(setq helm-zgrep-recurse-flag nil))) |
|
1145 |
|
|
1146 |
|
|
1147 |
;;; transformers |
|
1148 |
;; |
|
1149 |
;; |
|
1150 |
(defun helm-grep-split-line (line) |
|
1151 |
"Split a grep output line." |
|
1152 |
;; The output of grep may send a truncated line in this chunk, |
|
1153 |
;; so don't split until grep line is valid, that is |
|
1154 |
;; once the second part of the line comes with next chunk |
|
1155 |
;; send by process. |
|
1156 |
(when (string-match helm-grep-split-line-regexp line) |
|
1157 |
;; Don't use split-string because buffer/file name or string |
|
1158 |
;; may contain a ":". |
|
1159 |
(cl-loop for n from 1 to 3 collect (match-string n line)))) |
|
1160 |
|
|
1161 |
(defun helm-grep--filter-candidate-1 (candidate &optional dir) |
|
1162 |
(let* ((root (or dir (and helm-grep-default-directory-fn |
|
1163 |
(funcall helm-grep-default-directory-fn)))) |
|
1164 |
(ansi-p (string-match-p helm--ansi-color-regexp candidate)) |
|
1165 |
(line (if ansi-p (helm--ansi-color-apply candidate) candidate)) |
|
1166 |
(split (helm-grep-split-line line)) |
|
1167 |
(fname (if (and root split) |
|
1168 |
;; Filename should always be provided as a local |
|
1169 |
;; path, if the root directory is remote, the |
|
1170 |
;; tramp prefix will be added before executing |
|
1171 |
;; action, see `helm-grep-action' and issue #2032. |
|
1172 |
(expand-file-name (car split) |
|
1173 |
(or (file-remote-p root 'localname) |
|
1174 |
root)) |
|
1175 |
(car-safe split))) |
|
1176 |
(lineno (nth 1 split)) |
|
1177 |
(str (nth 2 split)) |
|
1178 |
(display-fname (cl-ecase helm-grep-file-path-style |
|
1179 |
(basename (and fname (file-name-nondirectory fname))) |
|
1180 |
(absolute fname) |
|
1181 |
(relative (and fname root |
|
1182 |
(file-relative-name fname root)))))) |
|
1183 |
(if (and display-fname lineno str) |
|
1184 |
(cons (concat (propertize display-fname |
|
1185 |
'face 'helm-grep-file |
|
1186 |
'help-echo (abbreviate-file-name fname) |
|
1187 |
'helm-grep-fname fname) |
|
1188 |
":" |
|
1189 |
(propertize lineno 'face 'helm-grep-lineno) |
|
1190 |
":" |
|
1191 |
(if ansi-p str (helm-grep-highlight-match str t))) |
|
1192 |
line) |
|
1193 |
""))) |
|
1194 |
|
|
1195 |
(defun helm-grep-filter-one-by-one (candidate) |
|
1196 |
"`filter-one-by-one' transformer function for `helm-do-grep-1'." |
|
1197 |
(let ((helm-grep-default-directory-fn |
|
1198 |
(or helm-grep-default-directory-fn |
|
1199 |
(lambda () (or helm-ff-default-directory |
|
1200 |
(and (null (eq major-mode 'helm-grep-mode)) |
|
1201 |
(helm-default-directory)) |
|
1202 |
default-directory))))) |
|
1203 |
(if (consp candidate) |
|
1204 |
;; Already computed do nothing (default as input). |
|
1205 |
candidate |
|
1206 |
(and (stringp candidate) |
|
1207 |
(helm-grep--filter-candidate-1 candidate))))) |
|
1208 |
|
|
1209 |
(defun helm-grep-highlight-match (str &optional multi-match) |
|
1210 |
"Highlight in string STR all occurences matching `helm-pattern'." |
|
1211 |
(let (beg end) |
|
1212 |
(condition-case-unless-debug nil |
|
1213 |
(with-temp-buffer |
|
1214 |
(insert (propertize str 'read-only nil)) ; Fix (#1176) |
|
1215 |
(goto-char (point-min)) |
|
1216 |
(cl-loop for reg in |
|
1217 |
(if multi-match |
|
1218 |
;; (m)occur. |
|
1219 |
(cl-loop for r in (helm-mm-split-pattern |
|
1220 |
helm-pattern) |
|
1221 |
unless (string-match "\\`!" r) |
|
1222 |
collect |
|
1223 |
(helm-aif (and helm-migemo-mode |
|
1224 |
(assoc r helm-mm--previous-migemo-info)) |
|
1225 |
(cdr it) r)) |
|
1226 |
;; async sources (grep, gid etc...) |
|
1227 |
(list helm-input)) |
|
1228 |
do |
|
1229 |
(while (and (re-search-forward reg nil t) |
|
1230 |
(> (- (setq end (match-end 0)) |
|
1231 |
(setq beg (match-beginning 0))) 0)) |
|
1232 |
(helm-add-face-text-properties beg end 'helm-grep-match)) |
|
1233 |
do (goto-char (point-min))) |
|
1234 |
(buffer-string)) |
|
1235 |
(error nil)))) |
|
1236 |
|
|
1237 |
|
|
1238 |
;;; Grep from buffer list |
|
1239 |
;; |
|
1240 |
;; |
|
1241 |
(defun helm-grep-buffers-1 (candidate &optional zgrep) |
|
1242 |
"Run grep on all file--buffers or CANDIDATE if it is a file--buffer. |
|
1243 |
If one of selected buffers is not a file--buffer, |
|
1244 |
it is ignored and grep will run on all others file--buffers. |
|
1245 |
If only one candidate is selected and it is not a file--buffer, |
|
1246 |
switch to this buffer and run `helm-occur'. |
|
1247 |
If a prefix arg is given run grep on all buffers ignoring non--file-buffers." |
|
1248 |
(let* ((prefarg (or current-prefix-arg helm-current-prefix-arg)) |
|
1249 |
(helm-ff-default-directory |
|
1250 |
(if (and helm-ff-default-directory |
|
1251 |
(file-remote-p helm-ff-default-directory)) |
|
1252 |
default-directory |
|
1253 |
helm-ff-default-directory)) |
|
1254 |
(cands (if prefarg |
|
1255 |
(buffer-list) |
|
1256 |
(helm-marked-candidates))) |
|
1257 |
(win-conf (current-window-configuration)) |
|
1258 |
;; Non--fname and remote buffers are ignored. |
|
1259 |
(bufs (cl-loop for buf in cands |
|
1260 |
for fname = (buffer-file-name (get-buffer buf)) |
|
1261 |
when (and fname (not (file-remote-p fname))) |
|
1262 |
collect (expand-file-name fname)))) |
|
1263 |
(if bufs |
|
1264 |
(if zgrep |
|
1265 |
(helm-do-grep-1 bufs nil 'zgrep) |
|
1266 |
(helm-do-grep-1 bufs)) |
|
1267 |
;; bufs is empty, thats mean we have only CANDIDATE |
|
1268 |
;; and it is not a buffer-filename, fallback to occur. |
|
1269 |
(switch-to-buffer candidate) |
|
1270 |
(when (get-buffer helm-action-buffer) |
|
1271 |
(kill-buffer helm-action-buffer)) |
|
1272 |
(helm-occur) |
|
1273 |
(when (eq helm-exit-status 1) |
|
1274 |
(set-window-configuration win-conf))))) |
|
1275 |
|
|
1276 |
(defun helm-grep-buffers (candidate) |
|
1277 |
"Action to grep buffers." |
|
1278 |
(helm-grep-buffers-1 candidate)) |
|
1279 |
|
|
1280 |
(defun helm-zgrep-buffers (candidate) |
|
1281 |
"Action to zgrep buffers." |
|
1282 |
(helm-grep-buffers-1 candidate 'zgrep)) |
|
1283 |
|
|
1284 |
|
|
1285 |
;;; Helm interface for pdfgrep |
|
1286 |
;; pdfgrep program <http://pdfgrep.sourceforge.net/> |
|
1287 |
;; and a pdf-reader (e.g xpdf) are needed. |
|
1288 |
;; |
|
1289 |
(defvar helm-pdfgrep-default-function 'helm-pdfgrep-init) |
|
1290 |
(defun helm-pdfgrep-init (only-files &optional recurse) |
|
1291 |
"Start an asynchronous pdfgrep process in ONLY-FILES list." |
|
1292 |
(let* ((default-directory (or helm-ff-default-directory |
|
1293 |
default-directory)) |
|
1294 |
(fnargs (helm-grep-prepare-candidates |
|
1295 |
(if (file-remote-p default-directory) |
|
1296 |
(mapcar (lambda (x) |
|
1297 |
(file-remote-p x 'localname)) |
|
1298 |
only-files) |
|
1299 |
only-files) |
|
1300 |
default-directory)) |
|
1301 |
(cmd-line (format (if recurse |
|
1302 |
helm-pdfgrep-default-recurse-command |
|
1303 |
helm-pdfgrep-default-command) |
|
1304 |
helm-pattern |
|
1305 |
fnargs)) |
|
1306 |
process-connection-type) |
|
1307 |
;; Start pdf grep process. |
|
1308 |
(helm-log "Starting Pdf Grep process in directory `%s'" default-directory) |
|
1309 |
(helm-log "Command line used was:\n\n%s" |
|
1310 |
(concat ">>> " (propertize cmd-line 'face 'helm-grep-cmd-line) "\n\n")) |
|
1311 |
(prog1 |
|
1312 |
(start-file-process-shell-command |
|
1313 |
"pdfgrep" helm-buffer cmd-line) |
|
1314 |
(message nil) |
|
1315 |
(set-process-sentinel |
|
1316 |
(get-buffer-process helm-buffer) |
|
1317 |
(lambda (_process event) |
|
1318 |
(if (string= event "finished\n") |
|
1319 |
(with-helm-window |
|
1320 |
(setq mode-line-format |
|
1321 |
'(" " mode-line-buffer-identification " " |
|
1322 |
(:eval (format "L%s" (helm-candidate-number-at-point))) " " |
|
1323 |
(:eval (propertize |
|
1324 |
(format "[Pdfgrep Process Finish - %s result(s)] " |
|
1325 |
(max (1- (count-lines |
|
1326 |
(point-min) (point-max))) 0)) |
|
1327 |
'face 'helm-grep-finish)))) |
|
1328 |
(force-mode-line-update) |
|
1329 |
(when helm-allow-mouse |
|
1330 |
(helm--bind-mouse-for-selection helm-selection-point))) |
|
1331 |
(helm-log "Error: Pdf grep %s" |
|
1332 |
(replace-regexp-in-string "\n" "" event)))))))) |
|
1333 |
|
|
1334 |
(defun helm-do-pdfgrep-1 (only &optional recurse) |
|
1335 |
"Launch pdfgrep with a list of ONLY files." |
|
1336 |
(unless (executable-find "pdfgrep") |
|
1337 |
(error "Error: No such program `pdfgrep'.")) |
|
1338 |
(let (helm-grep-in-recurse) ; recursion is implemented differently in *pdfgrep. |
|
1339 |
;; When called as action from an other source e.g *-find-files |
|
1340 |
;; we have to kill action buffer. |
|
1341 |
(when (get-buffer helm-action-buffer) |
|
1342 |
(kill-buffer helm-action-buffer)) |
|
1343 |
(setq helm-pdfgrep-targets only) |
|
1344 |
(helm |
|
1345 |
:sources (helm-build-async-source "PdfGrep" |
|
1346 |
:init (lambda () |
|
1347 |
;; If `helm-find-files' haven't already started, |
|
1348 |
;; give a default value to `helm-ff-default-directory'. |
|
1349 |
(setq helm-ff-default-directory (or helm-ff-default-directory |
|
1350 |
default-directory))) |
|
1351 |
:candidates-process (lambda () |
|
1352 |
(funcall helm-pdfgrep-default-function |
|
1353 |
helm-pdfgrep-targets recurse)) |
|
1354 |
:nohighlight t |
|
1355 |
:nomark t |
|
1356 |
:filter-one-by-one #'helm-grep-filter-one-by-one |
|
1357 |
:candidate-number-limit 9999 |
|
1358 |
:history 'helm-grep-history |
|
1359 |
:keymap helm-pdfgrep-map |
|
1360 |
:help-message 'helm-pdfgrep-help-message |
|
1361 |
:action #'helm-pdfgrep-action |
|
1362 |
:persistent-help "Jump to PDF Page" |
|
1363 |
:requires-pattern 2) |
|
1364 |
:buffer "*helm pdfgrep*" |
|
1365 |
:history 'helm-grep-history))) |
|
1366 |
|
|
1367 |
(defun helm-pdfgrep-action (candidate) |
|
1368 |
(helm-grep-action candidate 'pdf)) |
|
1369 |
|
|
1370 |
(defun helm-pdfgrep-action-1 (_split pageno fname) |
|
1371 |
(save-selected-window |
|
1372 |
(start-file-process-shell-command |
|
1373 |
"pdf-reader" nil |
|
1374 |
(format-spec helm-pdfgrep-default-read-command |
|
1375 |
(list (cons ?f fname) (cons ?p pageno)))))) |
|
1376 |
|
|
1377 |
;;; AG - PT - RG |
|
1378 |
;; |
|
1379 |
;; https://github.com/ggreer/the_silver_searcher |
|
1380 |
;; https://github.com/monochromegane/the_platinum_searcher |
|
1381 |
;; https://github.com/BurntSushi/ripgrep |
|
1382 |
|
|
1383 |
(defcustom helm-grep-ag-command |
|
1384 |
"ag --line-numbers -S --hidden --color --nogroup %s %s %s" |
|
1385 |
"The default command for AG, PT or RG. |
|
1386 |
|
|
1387 |
Takes three format specs, the first for type(s), the second for pattern |
|
1388 |
and the third for directory. |
|
1389 |
|
|
1390 |
You can use safely \"--color\" (used by default) with AG RG and PT. |
|
1391 |
|
|
1392 |
For ripgrep here is the command line to use: |
|
1393 |
|
|
1394 |
rg --color=always --smart-case --no-heading --line-number %s %s %s |
|
1395 |
|
|
1396 |
NOTE: Old versions of ripgrep was not supporting colors in emacs and a |
|
1397 |
workaround had to be used (i.e prefixing command line with |
|
1398 |
\"TERM=eterm-color\"), this is no more needed. |
|
1399 |
See issue <https://github.com/BurntSushi/ripgrep/issues/182> for more infos. |
|
1400 |
|
|
1401 |
You must use an output format that fit with helm grep, that is: |
|
1402 |
|
|
1403 |
\"filename:line-number:string\" |
|
1404 |
|
|
1405 |
The option \"--nogroup\" allow this. |
|
1406 |
The option \"--line-numbers\" is also mandatory except with PT (not supported). |
|
1407 |
For RG the options \"--no-heading\" and \"--line-number\" are the ones to use. |
|
1408 |
|
|
1409 |
When modifying the default colors of matches with e.g \"--color-match\" option of AG |
|
1410 |
you may want to modify as well `helm-grep-ag-pipe-cmd-switches' to have all matches |
|
1411 |
colorized with same color in multi match." |
|
1412 |
:group 'helm-grep |
|
1413 |
:type 'string) |
|
1414 |
|
|
1415 |
(defun helm-grep--ag-command () |
|
1416 |
(car (helm-remove-if-match |
|
1417 |
"\\`[A-Z]*=" (split-string helm-grep-ag-command)))) |
|
1418 |
|
|
1419 |
(defun helm-grep-ag-get-types () |
|
1420 |
"Returns a list of AG types if available with AG version. |
|
1421 |
See AG option \"--list-file-types\" |
|
1422 |
Ripgrep (rg) types are also supported if this backend is used." |
|
1423 |
(with-temp-buffer |
|
1424 |
(let* ((com (helm-grep--ag-command)) |
|
1425 |
(ripgrep (string= com "rg")) |
|
1426 |
(regex (if ripgrep "^\\(.*\\):" "^ *\\(--[a-z]*\\)")) |
|
1427 |
(prefix (if ripgrep "-t" ""))) |
|
1428 |
(when (equal (call-process com |
|
1429 |
nil t nil |
|
1430 |
(if ripgrep |
|
1431 |
"--type-list" "--list-file-types")) 0) |
|
1432 |
(goto-char (point-min)) |
|
1433 |
(cl-loop while (re-search-forward regex nil t) |
|
1434 |
for type = (match-string 1) |
|
1435 |
collect (cons type (concat prefix type))))))) |
|
1436 |
|
|
1437 |
(defun helm-grep-ag-prepare-cmd-line (pattern directory &optional type) |
|
1438 |
"Prepare AG command line to search PATTERN in DIRECTORY. |
|
1439 |
When TYPE is specified it is one of what returns `helm-grep-ag-get-types' |
|
1440 |
if available with current AG version." |
|
1441 |
(let* ((patterns (helm-mm-split-pattern pattern t)) |
|
1442 |
(pipe-switches (mapconcat 'identity helm-grep-ag-pipe-cmd-switches " ")) |
|
1443 |
(pipe-cmd (helm-acase (helm-grep--ag-command) |
|
1444 |
(("ag" "pt") |
|
1445 |
(format "%s -S --color%s" it (concat " " pipe-switches))) |
|
1446 |
("rg" (format "rg -N -S --color=always%s" |
|
1447 |
(concat " " pipe-switches))))) |
|
1448 |
(cmd (format helm-grep-ag-command |
|
1449 |
(mapconcat 'identity type " ") |
|
1450 |
(shell-quote-argument (car patterns)) |
|
1451 |
(shell-quote-argument directory)))) |
|
1452 |
(helm-aif (cdr patterns) |
|
1453 |
(concat cmd (cl-loop for p in it concat |
|
1454 |
(format " | %s %s" |
|
1455 |
pipe-cmd (shell-quote-argument p)))) |
|
1456 |
cmd))) |
|
1457 |
|
|
1458 |
(defun helm-grep-ag-init (directory &optional type) |
|
1459 |
"Start AG process in DIRECTORY maybe searching only files of type TYPE." |
|
1460 |
(let ((default-directory (or helm-ff-default-directory |
|
1461 |
(helm-default-directory) |
|
1462 |
default-directory)) |
|
1463 |
(cmd-line (helm-grep-ag-prepare-cmd-line |
|
1464 |
helm-pattern (or (file-remote-p directory 'localname) |
|
1465 |
directory) |
|
1466 |
type)) |
|
1467 |
(start-time (float-time)) |
|
1468 |
(proc-name (helm-grep--ag-command))) |
|
1469 |
(set (make-local-variable 'helm-grep-last-cmd-line) cmd-line) |
|
1470 |
(helm-log "Starting %s process in directory `%s'" |
|
1471 |
proc-name directory) |
|
1472 |
(helm-log "Command line used was:\n\n%s" |
|
1473 |
(concat ">>> " cmd-line "\n\n")) |
|
1474 |
(prog1 |
|
1475 |
(start-file-process-shell-command |
|
1476 |
proc-name helm-buffer cmd-line) |
|
1477 |
(set-process-sentinel |
|
1478 |
(get-buffer-process helm-buffer) |
|
1479 |
(lambda (process event) |
|
1480 |
(let* ((err (process-exit-status process)) |
|
1481 |
(noresult (= err 1))) |
|
1482 |
(cond (noresult |
|
1483 |
(with-helm-buffer |
|
1484 |
(insert (concat "* Exit with code 1, no result found," |
|
1485 |
" command line was:\n\n " |
|
1486 |
(propertize helm-grep-last-cmd-line |
|
1487 |
'face 'helm-grep-cmd-line))) |
|
1488 |
(setq mode-line-format |
|
1489 |
`(" " mode-line-buffer-identification " " |
|
1490 |
(:eval (format "L%s" (helm-candidate-number-at-point))) " " |
|
1491 |
(:eval (propertize |
|
1492 |
(format |
|
1493 |
"[%s process finished - (no results)] " |
|
1494 |
,(upcase proc-name)) |
|
1495 |
'face 'helm-grep-finish)))))) |
|
1496 |
((string= event "finished\n") |
|
1497 |
(helm-log "%s process finished with %s results in %fs" |
|
1498 |
proc-name |
|
1499 |
(helm-get-candidate-number) |
|
1500 |
(- (float-time) start-time)) |
|
1501 |
(helm-maybe-show-help-echo) |
|
1502 |
(with-helm-window |
|
1503 |
(setq mode-line-format |
|
1504 |
`(" " mode-line-buffer-identification " " |
|
1505 |
(:eval (format "L%s" (helm-candidate-number-at-point))) " " |
|
1506 |
(:eval (propertize |
|
1507 |
(format |
|
1508 |
"[%s process finished in %.2fs - (%s results)] " |
|
1509 |
,(upcase proc-name) |
|
1510 |
,(- (float-time) start-time) |
|
1511 |
(helm-get-candidate-number)) |
|
1512 |
'face 'helm-grep-finish)))) |
|
1513 |
(force-mode-line-update) |
|
1514 |
(when helm-allow-mouse |
|
1515 |
(helm--bind-mouse-for-selection helm-selection-point)))) |
|
1516 |
(t (helm-log |
|
1517 |
"Error: %s %s" |
|
1518 |
proc-name |
|
1519 |
(replace-regexp-in-string "\n" "" event)))))))))) |
|
1520 |
|
|
1521 |
(defclass helm-grep-ag-class (helm-source-async) |
|
1522 |
((nohighlight :initform t) |
|
1523 |
(pcre :initarg :pcre :initform t |
|
1524 |
:documentation |
|
1525 |
" Backend is using pcre regexp engine when non--nil.") |
|
1526 |
(keymap :initform helm-grep-map) |
|
1527 |
(history :initform 'helm-grep-ag-history) |
|
1528 |
(help-message :initform 'helm-grep-help-message) |
|
1529 |
(filter-one-by-one :initform 'helm-grep-filter-one-by-one) |
|
1530 |
(persistent-action :initform 'helm-grep-persistent-action) |
|
1531 |
(persistent-help :initform "Jump to line (`C-u' Record in mark ring)") |
|
1532 |
(candidate-number-limit :initform 99999) |
|
1533 |
(requires-pattern :initform 2) |
|
1534 |
(nomark :initform t) |
|
1535 |
(action :initform 'helm-grep-actions) |
|
1536 |
(group :initform 'helm-grep))) |
|
1537 |
|
|
1538 |
(defvar helm-source-grep-ag nil) |
|
1539 |
|
|
1540 |
(defmethod helm--setup-source ((source helm-grep-ag-class)) |
|
1541 |
(call-next-method) |
|
1542 |
(helm-aif (and helm-follow-mode-persistent |
|
1543 |
helm-source-grep-ag |
|
1544 |
(assoc-default 'follow helm-source-grep-ag)) |
|
1545 |
(setf (slot-value source 'follow) it))) |
|
1546 |
|
|
1547 |
(defun helm-grep-ag-1 (directory &optional type) |
|
1548 |
"Start helm ag in DIRECTORY maybe searching in files of type TYPE." |
|
1549 |
(setq helm-source-grep-ag |
|
1550 |
(helm-make-source (upcase (helm-grep--ag-command)) 'helm-grep-ag-class |
|
1551 |
:header-name (lambda (name) |
|
1552 |
(format "%s [%s]" |
|
1553 |
name (abbreviate-file-name directory))) |
|
1554 |
:candidates-process |
|
1555 |
(lambda () (helm-grep-ag-init directory type)))) |
|
1556 |
(helm :sources 'helm-source-grep-ag |
|
1557 |
:keymap helm-grep-map |
|
1558 |
:history 'helm-grep-ag-history |
|
1559 |
:truncate-lines helm-grep-truncate-lines |
|
1560 |
:buffer (format "*helm %s*" (helm-grep--ag-command)))) |
|
1561 |
|
|
1562 |
(defun helm-grep-ag (directory with-types) |
|
1563 |
"Start grep AG in DIRECTORY. |
|
1564 |
When WITH-TYPES is non-nil provide completion on AG types." |
|
1565 |
(require 'helm-adaptive) |
|
1566 |
(helm-grep-ag-1 directory |
|
1567 |
(helm-aif (and with-types |
|
1568 |
(helm-grep-ag-get-types)) |
|
1569 |
(helm-comp-read |
|
1570 |
"Ag type: " it |
|
1571 |
:must-match t |
|
1572 |
:marked-candidates t |
|
1573 |
:fc-transformer 'helm-adaptive-sort |
|
1574 |
:buffer "*helm ag types*")))) |
|
1575 |
|
|
1576 |
;;; Git grep |
|
1577 |
;; |
|
1578 |
;; |
|
1579 |
(defvar helm-source-grep-git nil) |
|
1580 |
|
|
1581 |
(defcustom helm-grep-git-grep-command |
|
1582 |
"git --no-pager grep -n%cH --color=always --full-name -e %p -- %f" |
|
1583 |
"The git grep default command line. |
|
1584 |
The option \"--color=always\" can be used safely. |
|
1585 |
The color of matched items can be customized in your .gitconfig |
|
1586 |
See `helm-grep-default-command' for more infos. |
|
1587 |
|
|
1588 |
The \"--exclude-standard\" and \"--no-index\" switches allow |
|
1589 |
skipping unwanted files specified in ~/.gitignore_global |
|
1590 |
and searching files not already staged (not enabled by default). |
|
1591 |
|
|
1592 |
You have also to enable this in global \".gitconfig\" with |
|
1593 |
\"git config --global core.excludesfile ~/.gitignore_global\"." |
|
1594 |
:group 'helm-grep |
|
1595 |
:type 'string) |
|
1596 |
|
|
1597 |
(defun helm-grep-git-1 (directory &optional all default input) |
|
1598 |
"Run git-grep on DIRECTORY. |
|
1599 |
If DIRECTORY is not inside or part of a git repo exit with error. |
|
1600 |
If optional arg ALL is non-nil grep the whole repo otherwise start |
|
1601 |
at DIRECTORY. |
|
1602 |
Arg DEFAULT is what you will have with `next-history-element', |
|
1603 |
arg INPUT is what you will have by default at prompt on startup." |
|
1604 |
(require 'vc) |
|
1605 |
(let* (helm-grep-default-recurse-command |
|
1606 |
;; Expand filename of each candidate with the git root dir. |
|
1607 |
;; The filename will be in the helm-grep-fname prop. |
|
1608 |
(helm-grep-default-directory-fn (lambda () |
|
1609 |
(vc-find-root directory ".git"))) |
|
1610 |
(helm-ff-default-directory (funcall helm-grep-default-directory-fn))) |
|
1611 |
(cl-assert helm-ff-default-directory nil "Not inside a Git repository") |
|
1612 |
(helm-do-grep-1 (if all '("") `(,(expand-file-name directory))) |
|
1613 |
nil 'git nil default input 'helm-source-grep-git))) |
|
1614 |
|
|
1615 |
|
|
1616 |
;;;###autoload |
|
1617 |
(defun helm-do-grep-ag (arg) |
|
1618 |
"Preconfigured helm for grepping with AG in `default-directory'. |
|
1619 |
With prefix-arg prompt for type if available with your AG version." |
|
1620 |
(interactive "P") |
|
1621 |
(require 'helm-files) |
|
1622 |
(helm-grep-ag (expand-file-name default-directory) arg)) |
|
1623 |
|
|
1624 |
;;;###autoload |
|
1625 |
(defun helm-grep-do-git-grep (arg) |
|
1626 |
"Preconfigured helm for git-grepping `default-directory'. |
|
1627 |
With a prefix arg ARG git-grep the whole repository." |
|
1628 |
(interactive "P") |
|
1629 |
(require 'helm-files) |
|
1630 |
(helm-grep-git-1 default-directory arg)) |
|
1631 |
|
|
1632 |
|
|
1633 |
(provide 'helm-grep) |
|
1634 |
|
|
1635 |
;; Local Variables: |
|
1636 |
;; byte-compile-warnings: (not obsolete) |
|
1637 |
;; coding: utf-8 |
|
1638 |
;; indent-tabs-mode: nil |
|
1639 |
;; End: |
|
1640 |
|
|
1641 |
;;; helm-grep.el ends here |