Klimi's new dotfiles with stow.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1661 lines
70 KiB

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