|
|
- ;;; helm-grep.el --- Helm Incremental Grep. -*- lexical-binding: t -*-
-
- ;; Copyright (C) 2012 ~ 2019 Thierry Volpiatto <thierry.volpiatto@gmail.com>
-
- ;; This program is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation, either version 3 of the License, or
- ;; (at your option) any later version.
-
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Code:
- (require 'cl-lib)
- (require 'format-spec)
- (require 'helm)
- (require 'helm-help)
- (require 'helm-regexp)
-
- ;;; load wgrep proxy if it's available
- (require 'wgrep-helm nil t)
-
- (declare-function helm-buffer-list "helm-buffers")
- (declare-function View-quit "view")
- (declare-function doc-view-goto-page "doc-view" (page))
- (declare-function pdf-view-goto-page "pdf-view" (page &optional window))
- (declare-function helm-mm-split-pattern "helm-multi-match")
- (declare-function helm--ansi-color-apply "helm-lib")
- (defvar helm--ansi-color-regexp)
-
- (defgroup helm-grep nil
- "Grep related Applications and libraries for Helm."
- :group 'helm)
-
- (defcustom helm-grep-default-command
- "grep --color=always -a -d skip %e -n%cH -e %p %f"
- "Default grep format command for `helm-do-grep-1'.
- Where:
- '%e' format spec is for --exclude or --include grep options or
- ack-grep --type option. (Not mandatory)
-
- '%c' format spec is for case-fold-search,
- whether to use the -i option of grep. (Not mandatory)
- When you specify this spec, helm grep will use smartcase
- that is when a upcase character is found in pattern case will
- be respected and no '-i' option will be used, otherwise, when
- no upcase character is found in pattern always use '-i'.
- If you don't want this behavior, don't use this spec and
- specify or not the '-i' option.
- Note that with ack-grep this is not needed, just specify
- the '--smart-case' option.
-
- '%p' format spec is for pattern. (Mandatory)
-
- '%f' format spec is for filenames. (Mandatory)
-
- If your grep version doesn't support the --exclude/include args
- don't specify the '%e' format spec.
-
- Helm also support ack-grep and git-grep ,
- here a default command example for ack-grep:
-
- \(setq helm-grep-default-command \"ack-grep -Hn --color --smart-case --no-group %e %p %f\"
- helm-grep-default-recurse-command \"ack-grep -H --color --smart-case --no-group %e %p %f\")
-
- You can ommit the %e spec if you don't want to be prompted for types.
-
- NOTE: Helm for ack-grep support ANSI sequences, so you can remove
- the \"--no-color\" option safely (recommended)
- However you should specify --color to enable multi matches highlighting
- because ack disable it when output is piped.
-
- Same for grep you can use safely the option \"--color=always\" (default).
- You can customize the color of matches using GREP_COLORS env var.
- e.g: \(setenv \"GREP_COLORS\" \"ms=30;43:mc=30;43:sl=01;37:cx=:fn=35:ln=32:bn=32:se=36\")
-
- To enable ANSI color in git-grep just add \"--color=always\".
- To customize the ANSI color in git-grep, GREP_COLORS have no effect,
- you will have to setup this in your .gitconfig:
-
- [color \"grep\"]
- match = black yellow
-
- where \"black\" is the foreground and \"yellow\" the background.
- See the git documentation for more infos.
-
- `helm-grep-default-command' and `helm-grep-default-recurse-command'are
- independents, so you can enable `helm-grep-default-command' with ack-grep
- and `helm-grep-default-recurse-command' with grep if you want to be faster
- on recursive grep.
-
- NOTE: Remote grepping is not available with ack-grep,
- and badly supported with grep because tramp handle badly
- repeated remote processes in a short delay (< to 5s)."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-grep-default-recurse-command
- "grep --color=always -a -d recurse %e -n%cH -e %p %f"
- "Default recursive grep format command for `helm-do-grep-1'.
- See `helm-grep-default-command' for format specs and infos about ack-grep."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-default-zgrep-command
- "zgrep --color=always -a -n%cH -e %p %f"
- "Default command for Zgrep.
- See `helm-grep-default-command' for infos on format specs.
- Option --color=always is supported and can be used safely
- to replace the helm internal match highlighting,
- see `helm-grep-default-command' for more infos."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-pdfgrep-default-command
- "pdfgrep --color always -niH %s %s"
- "Default command for pdfgrep.
- Option \"--color always\" is supported starting helm version 1.7.8,
- when used matchs will be highlighted according to GREP_COLORS env var."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-pdfgrep-default-recurse-command
- "pdfgrep --color always -rniH %s %s"
- "Default recurse command for pdfgrep.
- Option \"--color always\" is supported starting helm version 1.7.8,
- when used matchs will be highlighted according to GREP_COLORS env var."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-pdfgrep-default-read-command nil
- "Default command to read pdf files from pdfgrep.
- Where '%f' format spec is filename and '%p' is page number.
- e.g In Ubuntu you can set it to:
-
- \"evince --page-label=%p '%f'\"
-
- If set to nil either `doc-view-mode' or `pdf-view-mode' will be used
- instead of an external command."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-grep-max-length-history 100
- "Max number of elements to save in `helm-grep-history'."
- :group 'helm-grep
- :type 'integer)
-
- (defcustom helm-zgrep-file-extension-regexp
- ".*\\(\\.gz\\|\\.bz\\|\\.xz\\|\\.lzma\\)$"
- "Default file extensions zgrep will search in."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-grep-preferred-ext nil
- "This file extension will be preselected for grep."
- :group 'helm-grep
- :type 'string)
-
- (defcustom helm-grep-save-buffer-name-no-confirm nil
- "when *hgrep* already exists,auto append suffix."
- :group 'helm-grep
- :type 'boolean)
-
- (defcustom helm-grep-ignored-files
- (cons ".#*" (delq nil (mapcar (lambda (s)
- (unless (string-match-p "/\\'" s)
- (concat "*" s)))
- completion-ignored-extensions)))
- "List of file names which `helm-grep' shall exclude."
- :group 'helm-grep
- :type '(repeat string))
-
- (defcustom helm-grep-ignored-directories
- helm-walk-ignore-directories
- "List of names of sub-directories which `helm-grep' shall not recurse into."
- :group 'helm-grep
- :type '(repeat string))
-
- (defcustom helm-grep-truncate-lines t
- "When nil the grep line that appears will not be truncated."
- :group 'helm-grep
- :type 'boolean)
-
- (defcustom helm-grep-file-path-style 'basename
- "File path display style when grep results are displayed.
- Possible value are:
- basename: displays only the filename, none of the directory path
- absolute: displays absolute path
- relative: displays relative path from root grep directory."
- :group 'helm-grep
- :type '(choice (const :tag "Basename" basename)
- (const :tag "Absolute" absolute)
- (const :tag "Relative" relative)))
-
- (defcustom helm-grep-actions
- (helm-make-actions
- "Find File" 'helm-grep-action
- "Find file other frame" 'helm-grep-other-frame
- "Save results in grep buffer" 'helm-grep-save-results
- "Find file other window (C-u vertically)" 'helm-grep-other-window)
- "Actions for helm grep."
- :group 'helm-grep
- :type '(alist :key-type string :value-type function))
-
- (defcustom helm-grep-pipe-cmd-switches nil
- "A list of additional parameters to pass to grep pipe command.
- This will be used for pipe command for multiple pattern matching
- for grep, zgrep ack-grep and git-grep backends.
- If you add extra args for ack-grep, use ack-grep options,
- for others (grep, zgrep and git-grep) use grep options.
- Here are the commands where you may want to add switches:
-
- grep --color=always
- ack-grep --smart-case --color
-
- You probably don't need to use this unless you know what you are doing."
- :group 'helm-grep
- :type '(repeat string))
-
- (defcustom helm-grep-ag-pipe-cmd-switches nil
- "A list of additional parameters to pass to grep-ag pipe command.
- Use parameters compatibles with the backend you are using
- \(i.e AG for AG, PT for PT or RG for RG)
-
- You probably don't need to use this unless you know what you are doing."
- :group 'helm-grep
- :type '(repeat string))
-
- (defcustom helm-grep-input-idle-delay 0.6
- "Same as `helm-input-idle-delay' but for grep commands.
- It have a higher value than `helm-input-idle-delay' to avoid
- flickering when updating."
- :group 'helm-grep
- :type 'float)
- ;;; Faces
- ;;
- ;;
- (defgroup helm-grep-faces nil
- "Customize the appearance of helm-grep."
- :prefix "helm-"
- :group 'helm-grep
- :group 'helm-faces)
-
- (defface helm-grep-match
- '((((background light)) :foreground "#b00000")
- (((background dark)) :foreground "gold1"))
- "Face used to highlight grep matches.
- Have no effect when grep backend use \"--color=\"."
- :group 'helm-grep-faces)
-
- (defface helm-grep-file
- '((t (:foreground "BlueViolet"
- :underline t)))
- "Face used to highlight grep results filenames."
- :group 'helm-grep-faces)
-
- (defface helm-grep-lineno
- '((t (:foreground "Darkorange1")))
- "Face used to highlight grep number lines."
- :group 'helm-grep-faces)
-
- (defface helm-grep-finish
- '((t (:foreground "Green")))
- "Face used in mode line when grep is finish."
- :group 'helm-grep-faces)
-
- (defface helm-grep-cmd-line
- '((t (:inherit font-lock-type-face)))
- "Face used to highlight grep command line when no results."
- :group 'helm-grep-faces)
-
- ;;; Keymaps
- ;;
- ;;
- (defvar helm-grep-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map helm-map)
- (define-key map (kbd "M-<down>") 'helm-goto-next-file)
- (define-key map (kbd "M-<up>") 'helm-goto-precedent-file)
- (define-key map (kbd "C-c o") 'helm-grep-run-other-window-action)
- (define-key map (kbd "C-c C-o") 'helm-grep-run-other-frame-action)
- (define-key map (kbd "C-x C-s") 'helm-grep-run-save-buffer)
- (define-key map (kbd "DEL") 'helm-delete-backward-no-update)
- map)
- "Keymap used in Grep sources.")
-
- (defcustom helm-grep-use-ioccur-style-keys t
- "Use Arrow keys to jump to occurences.
- Note that if you define this variable with `setq' your change will
- have no effect, use customize instead."
- :group 'helm-grep
- :type 'boolean
- :set (lambda (var val)
- (set var val)
- (if val
- (progn
- (define-key helm-grep-map (kbd "<right>") 'helm-execute-persistent-action)
- (define-key helm-grep-map (kbd "<left>") 'helm-grep-run-default-action))
- (define-key helm-grep-map (kbd "<right>") nil)
- (define-key helm-grep-map (kbd "<left>") nil))))
-
- (defvar helm-pdfgrep-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map helm-map)
- (define-key map (kbd "M-<down>") 'helm-goto-next-file)
- (define-key map (kbd "M-<up>") 'helm-goto-precedent-file)
- (define-key map (kbd "DEL") 'helm-delete-backward-no-update)
- map)
- "Keymap used in pdfgrep.")
-
- (defvar helm-grep-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "RET") 'helm-grep-mode-jump)
- (define-key map (kbd "C-o") 'helm-grep-mode-jump-other-window)
- (define-key map (kbd "<C-down>") 'helm-grep-mode-jump-other-window-forward)
- (define-key map (kbd "<C-up>") 'helm-grep-mode-jump-other-window-backward)
- (define-key map (kbd "<M-down>") 'helm-gm-next-file)
- (define-key map (kbd "<M-up>") 'helm-gm-precedent-file)
- (define-key map (kbd "M-n") 'helm-grep-mode-jump-other-window-forward)
- (define-key map (kbd "M-p") 'helm-grep-mode-jump-other-window-backward)
- (define-key map (kbd "M-N") 'helm-gm-next-file)
- (define-key map (kbd "M-P") 'helm-gm-precedent-file)
- map))
-
- ;;; Internals vars
- ;;
- ;;
- (defvar helm-rzgrep-cache (make-hash-table :test 'equal))
- (defvar helm-grep-default-function 'helm-grep-init)
- (defvar helm-zgrep-recurse-flag nil)
- (defvar helm-grep-history nil)
- (defvar helm-grep-ag-history nil)
- (defvar helm-grep-last-targets nil)
- (defvar helm-grep-include-files nil)
- (defvar helm-grep-in-recurse nil)
- (defvar helm-grep-use-zgrep nil)
- (defvar helm-grep-default-directory-fn nil
- "A function that should return a directory to expand candidate to.
- It is intended to use as a let-bound variable, DON'T set this globaly.")
- (defvar helm-pdfgrep-targets nil)
- (defvar helm-grep-last-cmd-line nil)
- (defvar helm-grep-split-line-regexp "^\\([[:lower:][:upper:]]?:?.*?\\):\\([0-9]+\\):\\(.*\\)")
-
- ;;; Init
- ;;
- ;;
- (defun helm-grep-prepare-candidates (candidates in-directory)
- "Prepare filenames and directories CANDIDATES for grep command line."
- ;; If one or more candidate is a directory, search in all files
- ;; of this candidate (e.g /home/user/directory/*).
- ;; If r option is enabled search also in subdidrectories.
- ;; We need here to expand wildcards to support crap windows filenames
- ;; as grep doesn't accept quoted wildcards (e.g "dir/*.el").
- (if helm-zgrep-recurse-flag
- (mapconcat 'shell-quote-argument candidates " ")
- ;; When candidate is a directory, search in all its files.
- ;; NOTE that `file-expand-wildcards' will return also
- ;; directories, they will be ignored by grep but not
- ;; by ack-grep that will grep all files of this directory
- ;; without recursing in their subdirs though, see that as a one
- ;; level recursion with ack-grep.
- ;; So I leave it as it is, considering it is a feature. [1]
- (cl-loop for i in candidates append
- (cond ((string-match "^git" helm-grep-default-command)
- (list i))
- ;; Candidate is a directory and we use recursion or ack.
- ((and (file-directory-p i)
- (or helm-grep-in-recurse
- ;; ack-grep accept directory [1].
- (helm-grep-use-ack-p)))
- (list (expand-file-name i)))
- ;; Grep doesn't support directory only when not in recurse.
- ((file-directory-p i)
- (file-expand-wildcards
- (concat (file-name-as-directory (expand-file-name i)) "*") t))
- ;; Candidate is a file or wildcard and we use recursion, use the
- ;; current directory instead of candidate.
- ((and (or (file-exists-p i) (string-match "[*]" i))
- helm-grep-in-recurse)
- (list (expand-file-name
- (directory-file-name ; Needed for windoze.
- (file-name-directory (directory-file-name i))))))
- ;; Else should be one or more file/directory
- ;; possibly marked.
- ;; When real is a normal filename without wildcard
- ;; file-expand-wildcards returns a list of one file.
- ;; wildcards should have been already handled by
- ;; helm-read-file-name or helm-find-files but do it from
- ;; here too in case we are called from elsewhere.
- (t (file-expand-wildcards i t))) into all-files ; [1]
- finally return
- (let ((files (if (file-remote-p in-directory)
- ;; Grep don't understand tramp filenames
- ;; use the local name.
- (mapcar (lambda (x)
- (file-remote-p x 'localname))
- all-files)
- all-files)))
- ;; When user mark files and use recursion with grep
- ;; backend enabled, the loop collect on each marked
- ;; candidate its `file-name-directory' and we endup with
- ;; duplicates (Issue #1714). FIXME: For now as a quick fix
- ;; I just remove dups here but I should handle this inside
- ;; the cond above.
- (setq files (helm-fast-remove-dups files :test 'equal))
- (if (string-match "^git" helm-grep-default-command)
- (mapconcat 'identity files " ")
- (mapconcat 'shell-quote-argument files " "))))))
-
- (defun helm-grep-command (&optional recursive grep)
- (let* ((com (if recursive
- helm-grep-default-recurse-command
- helm-grep-default-command))
- (exe (if grep
- (symbol-name grep)
- (and com (car (split-string com " "))))))
- (if (and exe (string= exe "git")) "git-grep" exe)))
-
- (cl-defun helm-grep-use-ack-p (&key where)
- (let* ((rec-com (helm-grep-command t))
- (norm-com (helm-grep-command))
- (norm-com-ack-p (string-match "\\`ack" norm-com))
- (rec-com-ack-p (and rec-com (string-match "\\`ack" rec-com))))
- (cl-case where
- (default (and norm-com norm-com-ack-p))
- (recursive (and rec-com rec-com-ack-p))
- (strict (and norm-com rec-com rec-com-ack-p norm-com-ack-p))
- (t (and (not (and norm-com (string= norm-com "git-grep")))
- (or (and norm-com norm-com-ack-p)
- (and rec-com rec-com-ack-p)))))))
-
- (defun helm-grep--pipe-command-for-grep-command (smartcase pipe-switches &optional grep-cmd)
- (pcase (or grep-cmd (helm-grep-command))
- ;; Use grep for GNU regexp based tools.
- ((or "grep" "zgrep" "git-grep")
- (format "grep --color=always%s %s"
- (if smartcase " -i" "")
- pipe-switches))
- ;; Use ack-grep for PCRE based tools.
- ;; Sometimes ack-grep cmd is ack only.
- ((and (pred (string-match-p "ack")) ack)
- (format "%s --smart-case --color %s" ack pipe-switches))))
-
- (defun helm-grep--prepare-cmd-line (only-files &optional include zgrep)
- (let* ((default-directory (or helm-ff-default-directory
- (helm-default-directory)
- default-directory))
- (fnargs (helm-grep-prepare-candidates
- only-files default-directory))
- (ignored-files (unless (helm-grep-use-ack-p)
- (mapconcat
- (lambda (x)
- (concat "--exclude="
- (shell-quote-argument x)))
- helm-grep-ignored-files " ")))
- (ignored-dirs (unless (helm-grep-use-ack-p)
- (mapconcat
- ;; Need grep version >=2.5.4
- ;; of Gnuwin32 on windoze.
- (lambda (x)
- (concat "--exclude-dir="
- (shell-quote-argument x)))
- helm-grep-ignored-directories " ")))
- (exclude (unless (helm-grep-use-ack-p)
- (if helm-grep-in-recurse
- (concat (or include ignored-files)
- " " ignored-dirs)
- ignored-files)))
- (types (and (helm-grep-use-ack-p)
- ;; When %e format spec is not specified
- ;; in `helm-grep-default-command'
- ;; we need to pass an empty string
- ;; to types to avoid error.
- (or include "")))
- (smartcase (if (helm-grep-use-ack-p)
- ""
- (unless (let ((case-fold-search nil))
- (string-match-p
- "[[:upper:]]" helm-pattern))
- "i")))
- (helm-grep-default-command
- (concat helm-grep-default-command " %m")) ; `%m' like multi.
- (patterns (helm-mm-split-pattern helm-pattern t))
- (pipe-switches (mapconcat 'identity helm-grep-pipe-cmd-switches " "))
- (pipes
- (helm-aif (cdr patterns)
- (cl-loop with pipcom = (helm-grep--pipe-command-for-grep-command
- smartcase pipe-switches)
- for p in it concat
- (format " | %s %s" pipcom (shell-quote-argument p)))
- "")))
- (format-spec
- helm-grep-default-command
- (delq nil
- (list (unless zgrep
- (if types
- (cons ?e types)
- (cons ?e exclude)))
- (cons ?c (or smartcase ""))
- (cons ?p (shell-quote-argument (car patterns)))
- (cons ?f fnargs)
- (cons ?m pipes))))))
-
- (defun helm-grep-init (cmd-line)
- "Start an asynchronous grep process with CMD-LINE using ZGREP if non--nil."
- (let* ((default-directory (or helm-ff-default-directory
- (helm-default-directory)
- default-directory))
- (zgrep (string-match "\\`zgrep" cmd-line))
- ;; Use pipe only with grep, zgrep or git-grep.
- (process-connection-type (and (not zgrep) (helm-grep-use-ack-p)))
- (tramp-verbose helm-tramp-verbose)
- (start-time (float-time))
- (proc-name (if helm-grep-use-zgrep
- "Zgrep"
- (capitalize
- (if helm-grep-in-recurse
- (helm-grep-command t)
- (helm-grep-command)))))
- non-essential)
- ;; Start grep process.
- (helm-log "Starting Grep process in directory `%s'" default-directory)
- (helm-log "Command line used was:\n\n%s"
- (concat ">>> " (propertize cmd-line 'face 'helm-grep-cmd-line) "\n\n"))
- (prog1 ; This function should return the process first.
- (start-file-process-shell-command
- proc-name helm-buffer cmd-line)
- ;; Init sentinel.
- (set-process-sentinel
- (get-buffer-process helm-buffer)
- (lambda (process event)
- (let* ((err (process-exit-status process))
- (noresult (= err 1)))
- (unless (and err (> err 0))
- (helm-process-deferred-sentinel-hook
- process event (helm-default-directory)))
- (cond ((and noresult
- ;; This is a workaround for zgrep
- ;; that exit with code 1
- ;; after a certain amount of results.
- (with-helm-buffer (helm-empty-buffer-p)))
- (with-helm-buffer
- (insert (concat "* Exit with code 1, no result found,"
- " command line was:\n\n "
- (propertize helm-grep-last-cmd-line
- 'face 'helm-grep-cmd-line)))
- (setq mode-line-format
- `(" " mode-line-buffer-identification " "
- (:eval (format "L%s" (helm-candidate-number-at-point))) " "
- (:eval (propertize
- (format
- "[%s process finished - (no results)] "
- ,proc-name)
- 'face 'helm-grep-finish))))))
- ((or (string= event "finished\n")
- (and noresult
- ;; This is a workaround for zgrep
- ;; that exit with code 1
- ;; after a certain amount of results.
- (with-helm-buffer (not (helm-empty-buffer-p)))))
- (helm-log "%s process finished with %s results in %fs"
- proc-name
- (helm-get-candidate-number)
- (- (float-time) start-time))
- (helm-maybe-show-help-echo)
- (with-helm-window
- (setq mode-line-format
- `(" " mode-line-buffer-identification " "
- (:eval (format "L%s" (helm-candidate-number-at-point))) " "
- (:eval (propertize
- (format
- "[%s process finished in %.2fs - (%s results)] "
- ,proc-name
- ,(- (float-time) start-time)
- (helm-get-candidate-number))
- 'face 'helm-grep-finish))))
- (force-mode-line-update)
- (when (and helm-allow-mouse helm-selection-point)
- (helm--bind-mouse-for-selection helm-selection-point))))
- ;; Catch error output in log.
- (t (helm-log
- "Error: %s %s"
- proc-name
- (replace-regexp-in-string "\n" "" event))))))))))
-
- (defun helm-grep-collect-candidates ()
- (let ((cmd-line (helm-grep--prepare-cmd-line
- helm-grep-last-targets
- helm-grep-include-files
- helm-grep-use-zgrep)))
- (set (make-local-variable 'helm-grep-last-cmd-line) cmd-line)
- (funcall helm-grep-default-function cmd-line)))
-
- ;;; Actions
- ;;
- ;;
- (defun helm-grep-action (candidate &optional where)
- "Define a default action for `helm-do-grep-1' on CANDIDATE.
- WHERE can be one of other-window, other-frame."
- (let* ((split (helm-grep-split-line candidate))
- (split-pat (helm-mm-split-pattern helm-input))
- (lineno (string-to-number (nth 1 split)))
- (loc-fname (or (with-current-buffer
- (if (eq major-mode 'helm-grep-mode)
- (current-buffer)
- helm-buffer)
- (get-text-property (point-at-bol) 'helm-grep-fname))
- (car split)))
- (tramp-method (file-remote-p (or helm-ff-default-directory
- default-directory) 'method))
- (tramp-host (file-remote-p (or helm-ff-default-directory
- default-directory) 'host))
- (tramp-prefix (concat "/" tramp-method ":" tramp-host ":"))
- (fname (if tramp-host
- (concat tramp-prefix loc-fname) loc-fname)))
- (cl-case where
- (other-window (helm-window-show-buffers
- (list (find-file-noselect fname)) t))
- (other-frame (find-file-other-frame fname))
- (grep (helm-grep-save-results-1))
- (pdf (if helm-pdfgrep-default-read-command
- (helm-pdfgrep-action-1 split lineno (car split))
- (find-file (car split)) (if (derived-mode-p 'pdf-view-mode)
- (pdf-view-goto-page lineno)
- (doc-view-goto-page lineno))))
- (t (find-file fname)))
- (unless (or (eq where 'grep) (eq where 'pdf))
- (helm-goto-line lineno))
- ;; Move point to the nearest matching regexp from bol.
- (cl-loop for reg in split-pat
- when (save-excursion
- (condition-case _err
- (if helm-migemo-mode
- (helm-mm-migemo-forward reg (point-at-eol) t)
- (re-search-forward reg (point-at-eol) t))
- (invalid-regexp nil)))
- collect (match-beginning 0) into pos-ls
- finally (when pos-ls (goto-char (apply #'min pos-ls))))
- ;; Save history
- (unless (or helm-in-persistent-action
- (eq major-mode 'helm-grep-mode)
- (string= helm-pattern ""))
- (setq helm-grep-history
- (cons helm-pattern
- (delete helm-pattern helm-grep-history)))
- (when (> (length helm-grep-history)
- helm-grep-max-length-history)
- (setq helm-grep-history
- (delete (car (last helm-grep-history))
- helm-grep-history))))))
-
- (defun helm-grep-persistent-action (candidate)
- "Persistent action for `helm-do-grep-1'.
- With a prefix arg record CANDIDATE in `mark-ring'."
- (helm-grep-action candidate)
- (helm-highlight-current-line))
-
- (defun helm-grep-other-window (candidate)
- "Jump to result in other window from helm grep."
- (helm-grep-action candidate 'other-window))
-
- (defun helm-grep-other-frame (candidate)
- "Jump to result in other frame from helm grep."
- (helm-grep-action candidate 'other-frame))
-
- (defun helm-goto-next-or-prec-file (n)
- "Go to next or precedent candidate file in helm grep/etags buffers.
- If N is positive go forward otherwise go backward."
- (let* ((allow-mode (or (eq major-mode 'helm-grep-mode)
- (eq major-mode 'helm-moccur-mode)
- (eq major-mode 'helm-occur-mode)))
- (sel (if allow-mode
- (buffer-substring (point-at-bol) (point-at-eol))
- (helm-get-selection nil t)))
- (current-line-list (helm-grep-split-line sel))
- (current-fname (nth 0 current-line-list))
- (bob-or-eof (if (eq n 1) 'eobp 'bobp))
- (mark-maybe (lambda ()
- (if allow-mode
- (ignore)
- (helm-mark-current-line)))))
- (catch 'break
- (while (not (funcall bob-or-eof))
- (forward-line n) ; Go forward or backward depending of n value.
- ;; Exit when current-fname is not matched or in `helm-grep-mode'
- ;; the line is not a grep line i.e 'fname:num:tag'.
- (setq sel (buffer-substring (point-at-bol) (point-at-eol)))
- (when helm-allow-mouse
- (helm--mouse-reset-selection-help-echo))
- (unless (or (string= current-fname
- (car (helm-grep-split-line sel)))
- (and (eq major-mode 'helm-grep-mode)
- (not (get-text-property (point-at-bol) 'helm-grep-fname))))
- (funcall mark-maybe)
- (throw 'break nil))))
- (cond ((and (> n 0) (eobp))
- (re-search-backward ".")
- (forward-line 0)
- (funcall mark-maybe))
- ((and (< n 0) (bobp))
- (helm-aif (next-single-property-change (point-at-bol) 'helm-grep-fname)
- (goto-char it)
- (forward-line 1))
- (funcall mark-maybe)))
- (unless allow-mode
- (helm-follow-execute-persistent-action-maybe)
- (helm-log-run-hook 'helm-move-selection-after-hook))))
-
- ;;;###autoload
- (defun helm-goto-precedent-file ()
- "Go to precedent file in helm grep/etags buffers."
- (interactive)
- (with-helm-alive-p
- (with-helm-window
- (helm-goto-next-or-prec-file -1))))
- (put 'helm-goto-precedent-file 'helm-only t)
-
- ;;;###autoload
- (defun helm-goto-next-file ()
- "Go to precedent file in helm grep/etags buffers."
- (interactive)
- (with-helm-window
- (helm-goto-next-or-prec-file 1)))
-
- (defun helm-grep-run-default-action ()
- "Run grep default action from `helm-do-grep-1'."
- (interactive)
- (with-helm-alive-p
- (helm-exit-and-execute-action 'helm-grep-action)))
- (put 'helm-grep-run-default-action 'helm-only t)
-
- (defun helm-grep-run-other-window-action ()
- "Run grep goto other window action from `helm-do-grep-1'."
- (interactive)
- (with-helm-alive-p
- (helm-exit-and-execute-action 'helm-grep-other-window)))
- (put 'helm-grep-run-other-window-action 'helm-only t)
-
- (defun helm-grep-run-other-frame-action ()
- "Run grep goto other frame action from `helm-do-grep-1'."
- (interactive)
- (with-helm-alive-p
- (helm-exit-and-execute-action 'helm-grep-other-frame)))
- (put 'helm-grep-run-other-frame-action 'helm-only t)
-
- (defun helm-grep-run-save-buffer ()
- "Run grep save results action from `helm-do-grep-1'."
- (interactive)
- (with-helm-alive-p
- (helm-exit-and-execute-action 'helm-grep-save-results)))
- (put 'helm-grep-run-save-buffer 'helm-only t)
-
- ;;; helm-grep-mode
- ;;
- ;;
- (defun helm-grep-save-results (candidate)
- (helm-grep-action candidate 'grep))
-
- (defun helm-grep-save-results-1 ()
- "Save helm grep result in a `helm-grep-mode' buffer."
- (let ((buf "*hgrep*")
- new-buf
- (pattern (with-helm-buffer helm-input-local))
- (src-name (assoc-default 'name (helm-get-current-source))))
- (when (get-buffer buf)
- (if helm-grep-save-buffer-name-no-confirm
- (setq new-buf (format "*hgrep|%s|-%s" pattern
- (format-time-string "%H-%M-%S*")))
- (setq new-buf (helm-read-string "GrepBufferName: " buf))
- (cl-loop for b in (helm-buffer-list)
- when (and (string= new-buf b)
- (not (y-or-n-p
- (format "Buffer `%s' already exists overwrite? "
- new-buf))))
- do (setq new-buf (helm-read-string "GrepBufferName: " "*hgrep "))))
- (setq buf new-buf))
- (with-current-buffer (get-buffer-create buf)
- (setq default-directory (or helm-ff-default-directory
- (helm-default-directory)
- default-directory))
- (setq buffer-read-only t)
- (let ((inhibit-read-only t)
- (map (make-sparse-keymap)))
- (erase-buffer)
- (insert "-*- mode: helm-grep -*-\n\n"
- (format "%s Results for `%s':\n\n" src-name pattern))
- (save-excursion
- (insert (with-current-buffer helm-buffer
- (goto-char (point-min)) (forward-line 1)
- (buffer-substring (point) (point-max)))))
- (save-excursion
- (while (not (eobp))
- (add-text-properties (point-at-bol) (point-at-eol)
- `(keymap ,map
- help-echo ,(concat
- (get-text-property
- (point) 'helm-grep-fname)
- "\nmouse-1: set point\nmouse-2: jump to selection")
- mouse-face highlight))
- (define-key map [mouse-1] 'mouse-set-point)
- (define-key map [mouse-2] 'helm-grep-mode-mouse-jump)
- (define-key map [mouse-3] 'ignore)
- (forward-line 1))))
- (helm-grep-mode))
- (pop-to-buffer buf)
- (message "Helm %s Results saved in `%s' buffer" src-name buf)))
-
- (defun helm-grep-mode-mouse-jump (event)
- (interactive "e")
- (let* ((window (posn-window (event-end event)))
- (pos (posn-point (event-end event))))
- (with-selected-window window
- (when (eq major-mode 'helm-grep-mode)
- (goto-char pos)
- (helm-grep-mode-jump)))))
- (put 'helm-grep-mode-mouse-jump 'helm-only t)
-
- (define-derived-mode helm-grep-mode
- special-mode "helm-grep"
- "Major mode to provide actions in helm grep saved buffer.
-
- Special commands:
- \\{helm-grep-mode-map}"
- (set (make-local-variable 'helm-grep-last-cmd-line)
- (with-helm-buffer helm-grep-last-cmd-line))
- (set (make-local-variable 'revert-buffer-function)
- #'helm-grep-mode--revert-buffer-function))
- (put 'helm-grep-mode 'helm-only t)
-
- (defun helm-grep-mode--revert-buffer-function (&optional _ignore-auto _noconfirm)
- (goto-char (point-min))
- (when (re-search-forward helm-grep-split-line-regexp nil t) (forward-line 0))
- (let ((inhibit-read-only t))
- (delete-region (point) (point-max)))
- (message "Reverting buffer...")
- (let ((process-connection-type
- ;; Git needs a nil value otherwise it tries to use a pager.
- (null (string-match-p "\\`git" helm-grep-last-cmd-line))))
- (set-process-sentinel
- (start-file-process-shell-command
- "hgrep" (generate-new-buffer "*hgrep revert*") helm-grep-last-cmd-line)
- 'helm-grep-mode--sentinel)))
-
- (defun helm-grep-mode--sentinel (process event)
- (when (string= event "finished\n")
- (with-current-buffer (current-buffer)
- (let ((inhibit-read-only t))
- (save-excursion
- (cl-loop for l in (with-current-buffer (process-buffer process)
- (prog1 (split-string (buffer-string) "\n")
- (kill-buffer)))
- for line = (if (string-match-p helm--ansi-color-regexp l)
- (helm--ansi-color-apply l) l)
- when (string-match helm-grep-split-line-regexp line)
- do (insert (propertize
- (car (helm-grep-filter-one-by-one line))
- ;; needed for wgrep.
- 'helm-realvalue line)
- "\n"))))
- (when (fboundp 'wgrep-cleanup-overlays)
- (wgrep-cleanup-overlays (point-min) (point-max)))
- (message "Reverting buffer done"))))
-
- (defun helm-gm-next-file ()
- (interactive)
- (helm-goto-next-or-prec-file 1))
-
- (defun helm-gm-precedent-file ()
- (interactive)
- (helm-goto-next-or-prec-file -1))
-
- (defun helm-grep-mode-jump ()
- (interactive)
- (helm-grep-action
- (buffer-substring (point-at-bol) (point-at-eol)))
- (helm-match-line-cleanup-pulse))
-
- (defun helm-grep-mode-jump-other-window-1 (arg)
- (condition-case nil
- (progn
- (when (or (eq last-command 'helm-grep-mode-jump-other-window-forward)
- (eq last-command 'helm-grep-mode-jump-other-window-backward))
- (forward-line arg))
- (save-selected-window
- (helm-grep-action (buffer-substring (point-at-bol) (point-at-eol))
- 'other-window)
- (helm-match-line-cleanup-pulse)
- (recenter)))
- (error nil)))
-
- (defun helm-grep-mode-jump-other-window-forward (arg)
- (interactive "p")
- (helm-grep-mode-jump-other-window-1 arg))
-
- (defun helm-grep-mode-jump-other-window-backward (arg)
- (interactive "p")
- (helm-grep-mode-jump-other-window-1 (- arg)))
-
- (defun helm-grep-mode-jump-other-window ()
- (interactive)
- (let ((candidate (buffer-substring (point-at-bol) (point-at-eol))))
- (condition-case nil
- (progn (helm-grep-action candidate 'other-window)
- (helm-match-line-cleanup-pulse))
- (error nil))))
-
- ;;; ack-grep types
- ;;
- ;;
- (defun helm-grep-hack-types ()
- "Return a list of known ack-grep types."
- (with-temp-buffer
- ;; "--help-types" works with both 1.96 and 2.1+, while
- ;; "--help types" works only with 1.96 Issue #422.
- ;; `helm-grep-command' should return the ack executable
- ;; when this function is used in the right context
- ;; i.e After checking is we are using ack-grep with
- ;; `helm-grep-use-ack-p'.
- (call-process (helm-grep-command t) nil t nil "--help-types")
- (goto-char (point-min))
- (cl-loop while (re-search-forward
- "^ *--\\(\\[no\\]\\)\\([^. ]+\\) *\\(.*\\)" nil t)
- collect (cons (concat (match-string 2)
- " [" (match-string 3) "]")
- (match-string 2))
- collect (cons (concat "no" (match-string 2)
- " [" (match-string 3) "]")
- (concat "no" (match-string 2))))))
-
- (defun helm-grep-ack-types-transformer (candidates _source)
- (cl-loop for i in candidates
- if (stringp i)
- collect (rassoc i helm-grep-ack-types-cache)
- else
- collect i))
-
- (defvar helm-grep-ack-types-cache nil)
- (defun helm-grep-read-ack-type ()
- "Select types for the '--type' argument of ack-grep."
- (require 'helm-mode)
- (require 'helm-adaptive)
- (setq helm-grep-ack-types-cache (helm-grep-hack-types))
- (let ((types (helm-comp-read
- "Types: " helm-grep-ack-types-cache
- :name "*Ack-grep types*"
- :marked-candidates t
- :must-match t
- :fc-transformer '(helm-adaptive-sort
- helm-grep-ack-types-transformer)
- :buffer "*helm ack-types*")))
- (mapconcat (lambda (type) (concat "--type=" type)) types " ")))
-
- ;;; grep extensions
- ;;
- ;;
- (defun helm-grep-guess-extensions (files)
- "Try to guess file extensions in FILES list when using grep recurse.
- These extensions will be added to command line with --include arg of grep."
- (cl-loop with ext-list = (list helm-grep-preferred-ext "*")
- with lst = (if (file-directory-p (car files))
- (directory-files
- (car files) nil
- directory-files-no-dot-files-regexp)
- files)
- for i in lst
- for ext = (file-name-extension i 'dot)
- for glob = (and ext (not (string= ext ""))
- (concat "*" ext))
- unless (or (not glob)
- (and glob-list (member glob glob-list))
- (and glob-list (member glob ext-list))
- (and glob-list (member glob helm-grep-ignored-files)))
- collect glob into glob-list
- finally return (delq nil (append ext-list glob-list))))
-
- (defun helm-grep-get-file-extensions (files)
- "Try to return a list of file extensions to pass to '--include' arg of grep."
- (require 'helm-adaptive)
- (let* ((all-exts (helm-grep-guess-extensions
- (mapcar 'expand-file-name files)))
- (extensions (helm-comp-read "Search Only in: " all-exts
- :marked-candidates t
- :fc-transformer 'helm-adaptive-sort
- :buffer "*helm grep exts*"
- :name "*helm grep extensions*")))
- (when (listp extensions) ; Otherwise it is empty string returned by C-RET.
- ;; If extensions is a list of one string containing spaces,
- ;; assume user entered more than one glob separated by space(s) and
- ;; split this string to pass it later to mapconcat.
- ;; e.g '("*.el *.py")
- (cl-loop for i in extensions
- append (split-string-and-unquote i " ")))))
-
- ;;; Set up source
- ;;
- ;;
- (defvar helm-grep-before-init-hook nil
- "Hook that runs before initialization of the helm buffer.")
-
- (defvar helm-grep-after-init-hook nil
- "Hook that runs after initialization of the helm buffer.")
-
- (defclass helm-grep-class (helm-source-async)
- ((candidates-process :initform 'helm-grep-collect-candidates)
- (filter-one-by-one :initform 'helm-grep-filter-one-by-one)
- (keymap :initform helm-grep-map)
- (pcre :initarg :pcre :initform nil
- :documentation
- " Backend is using pcre regexp engine when non--nil.")
- (nohighlight :initform t)
- (nomark :initform t)
- (backend :initarg :backend
- :initform nil
- :documentation
- " The grep backend that will be used.
- It is actually used only as an internal flag
- and don't set the backend by itself.
- You probably don't want to modify this.")
- (candidate-number-limit :initform 9999)
- (help-message :initform 'helm-grep-help-message)
- (history :initform 'helm-grep-history)
- (action :initform 'helm-grep-actions)
- (persistent-action :initform 'helm-grep-persistent-action)
- (persistent-help :initform "Jump to line (`C-u' Record in mark ring)")
- (requires-pattern :initform 2)
- (before-init-hook :initform 'helm-grep-before-init-hook)
- (after-init-hook :initform 'helm-grep-after-init-hook)
- (group :initform 'helm-grep)))
-
- (defvar helm-source-grep nil)
-
- (defmethod helm--setup-source ((source helm-grep-class))
- (call-next-method)
- (helm-aif (and helm-follow-mode-persistent
- (if (eq (slot-value source 'backend) 'git)
- helm-source-grep-git
- helm-source-grep))
- (setf (slot-value source 'follow)
- (assoc-default 'follow it))))
-
- (cl-defun helm-do-grep-1 (targets &optional recurse backend exts
- default-input input (source 'helm-source-grep))
- "Launch helm using backend BACKEND on a list of TARGETS files.
-
- When RECURSE is given and BACKEND is 'grep' use -r option of
- BACKEND and prompt user for EXTS to set the --include args of BACKEND.
- Interactively you can give more than one arg separated by space at prompt.
- e.g
- $Pattern: *.el *.py *.tex
-
- From lisp use the EXTS argument as a list of extensions as above.
- If you are using ack-grep, you will be prompted for --type
- instead and EXTS will be ignored.
- If prompt is empty `helm-grep-ignored-files' are added to --exclude.
-
- Argument DEFAULT-INPUT is use as `default' arg of `helm' and INPUT
- is used as `input' arg of `helm', See `helm' docstring.
-
- Arg BACKEND when non--nil specify which backend to use
- It is used actually to specify 'zgrep' or 'git'.
- When BACKEND 'zgrep' is used don't prompt for a choice
- in recurse, and ignore EXTS, search being made recursively on files matching
- `helm-zgrep-file-extension-regexp' only."
- (let* (non-essential
- (ack-rec-p (helm-grep-use-ack-p :where 'recursive))
- (exts (and recurse
- ;; [FIXME] I could handle this from helm-walk-directory.
- (not (eq backend 'zgrep)) ; zgrep doesn't handle -r opt.
- (not ack-rec-p)
- (or exts (helm-grep-get-file-extensions targets))))
- (include-files
- (and exts
- (mapconcat (lambda (x)
- (concat "--include="
- (shell-quote-argument x)))
- (if (> (length exts) 1)
- (remove "*" exts)
- exts) " ")))
- (types (and (not include-files)
- (not (eq backend 'zgrep))
- recurse
- ack-rec-p
- ;; When %e format spec is not specified
- ;; ignore types and do not prompt for choice.
- (string-match "%e" helm-grep-default-command)
- (helm-grep-read-ack-type)))
- (src-name (capitalize (helm-grep-command recurse backend)))
- (com (cond ((eq backend 'zgrep) helm-default-zgrep-command)
- ((eq backend 'git) helm-grep-git-grep-command)
- (recurse helm-grep-default-recurse-command)
- ;; When resuming, the local value of
- ;; `helm-grep-default-command' is used, only git-grep
- ;; should need this.
- (t helm-grep-default-command))))
- ;; When called as action from an other source e.g *-find-files
- ;; we have to kill action buffer.
- (when (get-buffer helm-action-buffer)
- (kill-buffer helm-action-buffer))
- ;; If `helm-find-files' haven't already started,
- ;; give a default value to `helm-ff-default-directory'
- ;; and set locally `default-directory' to this value . See below [1].
- (unless helm-ff-default-directory
- (setq helm-ff-default-directory default-directory))
- ;; We need to store these vars locally
- ;; to pass infos later to `helm-resume'.
- (helm-set-local-variable
- 'helm-zgrep-recurse-flag (and recurse (eq backend 'zgrep))
- 'helm-grep-last-targets targets
- 'helm-grep-include-files (or include-files types)
- 'helm-grep-in-recurse recurse
- 'helm-grep-use-zgrep (eq backend 'zgrep)
- 'helm-grep-default-command com
- 'helm-input-idle-delay helm-grep-input-idle-delay
- 'default-directory helm-ff-default-directory) ;; [1]
- ;; Setup the source.
- (set source (helm-make-source src-name 'helm-grep-class
- :backend backend
- :pcre (string-match-p "\\`ack" com)))
- (helm
- :sources source
- :buffer (format "*helm %s*" (helm-grep-command recurse backend))
- :default default-input
- :input input
- :keymap helm-grep-map
- :history 'helm-grep-history
- :truncate-lines helm-grep-truncate-lines)))
-
- ;;; zgrep
- ;;
- ;;
- (defun helm-ff-zgrep-1 (flist recursive)
- (unwind-protect
- (let* ((def-dir (or helm-ff-default-directory
- default-directory))
- (only (if recursive
- (or (gethash def-dir helm-rzgrep-cache)
- (puthash
- def-dir
- (helm-walk-directory
- def-dir
- :directories nil
- :path 'full
- :match helm-zgrep-file-extension-regexp)
- helm-rzgrep-cache))
- flist)))
- (helm-do-grep-1 only recursive 'zgrep))
- (setq helm-zgrep-recurse-flag nil)))
-
- ;;; transformers
- ;;
- ;;
- (defun helm-grep-split-line (line)
- "Split a grep output line."
- ;; The output of grep may send a truncated line in this chunk,
- ;; so don't split until grep line is valid, that is
- ;; once the second part of the line comes with next chunk
- ;; send by process.
- (when (string-match helm-grep-split-line-regexp line)
- ;; Don't use split-string because buffer/file name or string
- ;; may contain a ":".
- (cl-loop for n from 1 to 3 collect (match-string n line))))
-
- (defun helm-grep--filter-candidate-1 (candidate &optional dir)
- (let* ((root (or dir (and helm-grep-default-directory-fn
- (funcall helm-grep-default-directory-fn))))
- (ansi-p (string-match-p helm--ansi-color-regexp candidate))
- (line (if ansi-p (helm--ansi-color-apply candidate) candidate))
- (split (helm-grep-split-line line))
- (fname (if (and root split)
- ;; Filename should always be provided as a local
- ;; path, if the root directory is remote, the
- ;; tramp prefix will be added before executing
- ;; action, see `helm-grep-action' and issue #2032.
- (expand-file-name (car split)
- (or (file-remote-p root 'localname)
- root))
- (car-safe split)))
- (lineno (nth 1 split))
- (str (nth 2 split))
- (display-fname (cl-ecase helm-grep-file-path-style
- (basename (and fname (file-name-nondirectory fname)))
- (absolute fname)
- (relative (and fname root
- (file-relative-name fname root))))))
- (if (and display-fname lineno str)
- (cons (concat (propertize display-fname
- 'face 'helm-grep-file
- 'help-echo (abbreviate-file-name fname)
- 'helm-grep-fname fname)
- ":"
- (propertize lineno 'face 'helm-grep-lineno)
- ":"
- (if ansi-p str (helm-grep-highlight-match str t)))
- line)
- "")))
-
- (defun helm-grep-filter-one-by-one (candidate)
- "`filter-one-by-one' transformer function for `helm-do-grep-1'."
- (let ((helm-grep-default-directory-fn
- (or helm-grep-default-directory-fn
- (lambda () (or helm-ff-default-directory
- (and (null (eq major-mode 'helm-grep-mode))
- (helm-default-directory))
- default-directory)))))
- (if (consp candidate)
- ;; Already computed do nothing (default as input).
- candidate
- (and (stringp candidate)
- (helm-grep--filter-candidate-1 candidate)))))
-
- (defun helm-grep-highlight-match (str &optional multi-match)
- "Highlight in string STR all occurences matching `helm-pattern'."
- (let (beg end)
- (condition-case-unless-debug nil
- (with-temp-buffer
- (insert (propertize str 'read-only nil)) ; Fix (#1176)
- (goto-char (point-min))
- (cl-loop for reg in
- (if multi-match
- ;; (m)occur.
- (cl-loop for r in (helm-mm-split-pattern
- helm-pattern)
- unless (string-match "\\`!" r)
- collect
- (helm-aif (and helm-migemo-mode
- (assoc r helm-mm--previous-migemo-info))
- (cdr it) r))
- ;; async sources (grep, gid etc...)
- (list helm-input))
- do
- (while (and (re-search-forward reg nil t)
- (> (- (setq end (match-end 0))
- (setq beg (match-beginning 0))) 0))
- (helm-add-face-text-properties beg end 'helm-grep-match))
- do (goto-char (point-min)))
- (buffer-string))
- (error nil))))
-
- ;;; Grep from buffer list
- ;;
- ;;
- (defun helm-grep-buffers-1 (candidate &optional zgrep)
- "Run grep on all file--buffers or CANDIDATE if it is a file--buffer.
- If one of selected buffers is not a file--buffer,
- it is ignored and grep will run on all others file--buffers.
- If only one candidate is selected and it is not a file--buffer,
- switch to this buffer and run `helm-occur'.
- If a prefix arg is given run grep on all buffers ignoring non--file-buffers."
- (let* ((prefarg (or current-prefix-arg helm-current-prefix-arg))
- (helm-ff-default-directory
- (if (and helm-ff-default-directory
- (file-remote-p helm-ff-default-directory))
- default-directory
- helm-ff-default-directory))
- (cands (if prefarg
- (buffer-list)
- (helm-marked-candidates)))
- (win-conf (current-window-configuration))
- ;; Non--fname and remote buffers are ignored.
- (bufs (cl-loop for buf in cands
- for fname = (buffer-file-name (get-buffer buf))
- when (and fname (not (file-remote-p fname)))
- collect (expand-file-name fname))))
- (if bufs
- (if zgrep
- (helm-do-grep-1 bufs nil 'zgrep)
- (helm-do-grep-1 bufs))
- ;; bufs is empty, thats mean we have only CANDIDATE
- ;; and it is not a buffer-filename, fallback to occur.
- (switch-to-buffer candidate)
- (when (get-buffer helm-action-buffer)
- (kill-buffer helm-action-buffer))
- (helm-occur)
- (when (eq helm-exit-status 1)
- (set-window-configuration win-conf)))))
-
- (defun helm-grep-buffers (candidate)
- "Action to grep buffers."
- (helm-grep-buffers-1 candidate))
-
- (defun helm-zgrep-buffers (candidate)
- "Action to zgrep buffers."
- (helm-grep-buffers-1 candidate 'zgrep))
-
- ;;; Helm interface for pdfgrep
- ;; pdfgrep program <http://pdfgrep.sourceforge.net/>
- ;; and a pdf-reader (e.g xpdf) are needed.
- ;;
- (defvar helm-pdfgrep-default-function 'helm-pdfgrep-init)
- (defun helm-pdfgrep-init (only-files &optional recurse)
- "Start an asynchronous pdfgrep process in ONLY-FILES list."
- (let* ((default-directory (or helm-ff-default-directory
- default-directory))
- (fnargs (helm-grep-prepare-candidates
- (if (file-remote-p default-directory)
- (mapcar (lambda (x)
- (file-remote-p x 'localname))
- only-files)
- only-files)
- default-directory))
- (cmd-line (format (if recurse
- helm-pdfgrep-default-recurse-command
- helm-pdfgrep-default-command)
- helm-pattern
- fnargs))
- process-connection-type)
- ;; Start pdf grep process.
- (helm-log "Starting Pdf Grep process in directory `%s'" default-directory)
- (helm-log "Command line used was:\n\n%s"
- (concat ">>> " (propertize cmd-line 'face 'helm-grep-cmd-line) "\n\n"))
- (prog1
- (start-file-process-shell-command
- "pdfgrep" helm-buffer cmd-line)
- (message nil)
- (set-process-sentinel
- (get-buffer-process helm-buffer)
- (lambda (_process event)
- (if (string= event "finished\n")
- (with-helm-window
- (setq mode-line-format
- '(" " mode-line-buffer-identification " "
- (:eval (format "L%s" (helm-candidate-number-at-point))) " "
- (:eval (propertize
- (format "[Pdfgrep Process Finish - %s result(s)] "
- (max (1- (count-lines
- (point-min) (point-max))) 0))
- 'face 'helm-grep-finish))))
- (force-mode-line-update)
- (when helm-allow-mouse
- (helm--bind-mouse-for-selection helm-selection-point)))
- (helm-log "Error: Pdf grep %s"
- (replace-regexp-in-string "\n" "" event))))))))
-
- (defun helm-do-pdfgrep-1 (only &optional recurse)
- "Launch pdfgrep with a list of ONLY files."
- (unless (executable-find "pdfgrep")
- (error "Error: No such program `pdfgrep'."))
- (let (helm-grep-in-recurse) ; recursion is implemented differently in *pdfgrep.
- ;; When called as action from an other source e.g *-find-files
- ;; we have to kill action buffer.
- (when (get-buffer helm-action-buffer)
- (kill-buffer helm-action-buffer))
- (setq helm-pdfgrep-targets only)
- (helm
- :sources (helm-build-async-source "PdfGrep"
- :init (lambda ()
- ;; If `helm-find-files' haven't already started,
- ;; give a default value to `helm-ff-default-directory'.
- (setq helm-ff-default-directory (or helm-ff-default-directory
- default-directory)))
- :candidates-process (lambda ()
- (funcall helm-pdfgrep-default-function
- helm-pdfgrep-targets recurse))
- :nohighlight t
- :nomark t
- :filter-one-by-one #'helm-grep-filter-one-by-one
- :candidate-number-limit 9999
- :history 'helm-grep-history
- :keymap helm-pdfgrep-map
- :help-message 'helm-pdfgrep-help-message
- :action #'helm-pdfgrep-action
- :persistent-help "Jump to PDF Page"
- :requires-pattern 2)
- :buffer "*helm pdfgrep*"
- :history 'helm-grep-history)))
-
- (defun helm-pdfgrep-action (candidate)
- (helm-grep-action candidate 'pdf))
-
- (defun helm-pdfgrep-action-1 (_split pageno fname)
- (save-selected-window
- (start-file-process-shell-command
- "pdf-reader" nil
- (format-spec helm-pdfgrep-default-read-command
- (list (cons ?f fname) (cons ?p pageno))))))
- ;;; AG - PT - RG
- ;;
- ;; https://github.com/ggreer/the_silver_searcher
- ;; https://github.com/monochromegane/the_platinum_searcher
- ;; https://github.com/BurntSushi/ripgrep
-
- (defcustom helm-grep-ag-command
- "ag --line-numbers -S --hidden --color --nogroup %s %s %s"
- "The default command for AG, PT or RG.
-
- Takes three format specs, the first for type(s), the second for pattern
- and the third for directory.
-
- You can use safely \"--color\" (used by default) with AG RG and PT.
-
- For ripgrep here is the command line to use:
-
- rg --color=always --smart-case --no-heading --line-number %s %s %s
-
- NOTE: Old versions of ripgrep was not supporting colors in emacs and a
- workaround had to be used (i.e prefixing command line with
- \"TERM=eterm-color\"), this is no more needed.
- See issue <https://github.com/BurntSushi/ripgrep/issues/182> for more infos.
-
- You must use an output format that fit with helm grep, that is:
-
- \"filename:line-number:string\"
-
- The option \"--nogroup\" allow this.
- The option \"--line-numbers\" is also mandatory except with PT (not supported).
- For RG the options \"--no-heading\" and \"--line-number\" are the ones to use.
-
- When modifying the default colors of matches with e.g \"--color-match\" option of AG
- you may want to modify as well `helm-grep-ag-pipe-cmd-switches' to have all matches
- colorized with same color in multi match."
- :group 'helm-grep
- :type 'string)
-
- (defun helm-grep--ag-command ()
- (car (helm-remove-if-match
- "\\`[A-Z]*=" (split-string helm-grep-ag-command))))
-
- (defun helm-grep-ag-get-types ()
- "Returns a list of AG types if available with AG version.
- See AG option \"--list-file-types\"
- Ripgrep (rg) types are also supported if this backend is used."
- (with-temp-buffer
- (let* ((com (helm-grep--ag-command))
- (ripgrep (string= com "rg"))
- (regex (if ripgrep "^\\(.*\\):" "^ *\\(--[a-z]*\\)"))
- (prefix (if ripgrep "-t " "")))
- (when (equal (call-process com
- nil t nil
- (if ripgrep
- "--type-list" "--list-file-types")) 0)
- (goto-char (point-min))
- (cl-loop while (re-search-forward regex nil t)
- for type = (match-string 1)
- collect (cons type (concat prefix type)))))))
-
- (defun helm-grep-ag-prepare-cmd-line (pattern directory &optional type)
- "Prepare AG command line to search PATTERN in DIRECTORY.
- When TYPE is specified it is one of what returns `helm-grep-ag-get-types'
- if available with current AG version."
- (let* ((patterns (helm-mm-split-pattern pattern t))
- (pipe-switches (mapconcat 'identity helm-grep-ag-pipe-cmd-switches " "))
- (pipe-cmd (helm-acase (helm-grep--ag-command)
- (("ag" "pt")
- (format "%s -S --color%s" it (concat " " pipe-switches)))
- ("rg" (format "rg -N -S --color=always%s"
- (concat " " pipe-switches)))))
- (cmd (format helm-grep-ag-command
- (mapconcat 'identity type " ")
- (shell-quote-argument (car patterns))
- (shell-quote-argument directory))))
- (helm-aif (cdr patterns)
- (concat cmd (cl-loop for p in it concat
- (format " | %s %s"
- pipe-cmd (shell-quote-argument p))))
- cmd)))
-
- (defun helm-grep-ag-init (directory &optional type)
- "Start AG process in DIRECTORY maybe searching only files of type TYPE."
- (let ((default-directory (or helm-ff-default-directory
- (helm-default-directory)
- default-directory))
- (cmd-line (helm-grep-ag-prepare-cmd-line
- helm-pattern (or (file-remote-p directory 'localname)
- directory)
- type))
- (start-time (float-time))
- (proc-name (helm-grep--ag-command)))
- (set (make-local-variable 'helm-grep-last-cmd-line) cmd-line)
- (helm-log "Starting %s process in directory `%s'"
- proc-name directory)
- (helm-log "Command line used was:\n\n%s"
- (concat ">>> " cmd-line "\n\n"))
- (prog1
- (start-file-process-shell-command
- proc-name helm-buffer cmd-line)
- (set-process-sentinel
- (get-buffer-process helm-buffer)
- (lambda (process event)
- (let* ((err (process-exit-status process))
- (noresult (= err 1)))
- (cond (noresult
- (with-helm-buffer
- (insert (concat "* Exit with code 1, no result found,"
- " command line was:\n\n "
- (propertize helm-grep-last-cmd-line
- 'face 'helm-grep-cmd-line)))
- (setq mode-line-format
- `(" " mode-line-buffer-identification " "
- (:eval (format "L%s" (helm-candidate-number-at-point))) " "
- (:eval (propertize
- (format
- "[%s process finished - (no results)] "
- ,(upcase proc-name))
- 'face 'helm-grep-finish))))))
- ((string= event "finished\n")
- (helm-log "%s process finished with %s results in %fs"
- proc-name
- (helm-get-candidate-number)
- (- (float-time) start-time))
- (helm-maybe-show-help-echo)
- (with-helm-window
- (setq mode-line-format
- `(" " mode-line-buffer-identification " "
- (:eval (format "L%s" (helm-candidate-number-at-point))) " "
- (:eval (propertize
- (format
- "[%s process finished in %.2fs - (%s results)] "
- ,(upcase proc-name)
- ,(- (float-time) start-time)
- (helm-get-candidate-number))
- 'face 'helm-grep-finish))))
- (force-mode-line-update)
- (when helm-allow-mouse
- (helm--bind-mouse-for-selection helm-selection-point))))
- (t (helm-log
- "Error: %s %s"
- proc-name
- (replace-regexp-in-string "\n" "" event))))))))))
-
- (defclass helm-grep-ag-class (helm-source-async)
- ((nohighlight :initform t)
- (pcre :initarg :pcre :initform t
- :documentation
- " Backend is using pcre regexp engine when non--nil.")
- (keymap :initform helm-grep-map)
- (history :initform 'helm-grep-ag-history)
- (help-message :initform 'helm-grep-help-message)
- (filter-one-by-one :initform 'helm-grep-filter-one-by-one)
- (persistent-action :initform 'helm-grep-persistent-action)
- (persistent-help :initform "Jump to line (`C-u' Record in mark ring)")
- (candidate-number-limit :initform 99999)
- (requires-pattern :initform 2)
- (nomark :initform t)
- (action :initform 'helm-grep-actions)
- (group :initform 'helm-grep)))
-
- (defvar helm-source-grep-ag nil)
-
- (defmethod helm--setup-source ((source helm-grep-ag-class))
- (call-next-method)
- (helm-aif (and helm-follow-mode-persistent
- helm-source-grep-ag
- (assoc-default 'follow helm-source-grep-ag))
- (setf (slot-value source 'follow) it)))
-
- (defun helm-grep-ag-1 (directory &optional type)
- "Start helm ag in DIRECTORY maybe searching in files of type TYPE."
- (setq helm-source-grep-ag
- (helm-make-source (upcase (helm-grep--ag-command)) 'helm-grep-ag-class
- :header-name (lambda (name)
- (format "%s [%s]"
- name (abbreviate-file-name directory)))
- :candidates-process
- (lambda () (helm-grep-ag-init directory type))))
- (helm-set-local-variable 'helm-input-idle-delay helm-grep-input-idle-delay)
- (helm :sources 'helm-source-grep-ag
- :keymap helm-grep-map
- :history 'helm-grep-ag-history
- :truncate-lines helm-grep-truncate-lines
- :buffer (format "*helm %s*" (helm-grep--ag-command))))
-
- (defun helm-grep-ag (directory with-types)
- "Start grep AG in DIRECTORY.
- When WITH-TYPES is non-nil provide completion on AG types."
- (require 'helm-adaptive)
- (helm-grep-ag-1 directory
- (helm-aif (and with-types
- (helm-grep-ag-get-types))
- (helm-comp-read
- "Ag type: " it
- :must-match t
- :marked-candidates t
- :fc-transformer 'helm-adaptive-sort
- :buffer "*helm ag types*"))))
-
- ;;; Git grep
- ;;
- ;;
- (defvar helm-source-grep-git nil)
-
- (defcustom helm-grep-git-grep-command
- "git --no-pager grep -n%cH --color=always --full-name -e %p -- %f"
- "The git grep default command line.
- The option \"--color=always\" can be used safely.
- The color of matched items can be customized in your .gitconfig
- See `helm-grep-default-command' for more infos.
-
- The \"--exclude-standard\" and \"--no-index\" switches allow
- skipping unwanted files specified in ~/.gitignore_global
- and searching files not already staged (not enabled by default).
-
- You have also to enable this in global \".gitconfig\" with
- \"git config --global core.excludesfile ~/.gitignore_global\"."
- :group 'helm-grep
- :type 'string)
-
- (defun helm-grep-git-1 (directory &optional all default input)
- "Run git-grep on DIRECTORY.
- If DIRECTORY is not inside or part of a git repo exit with error.
- If optional arg ALL is non-nil grep the whole repo otherwise start
- at DIRECTORY.
- Arg DEFAULT is what you will have with `next-history-element',
- arg INPUT is what you will have by default at prompt on startup."
- (require 'vc)
- (let* (helm-grep-default-recurse-command
- ;; Expand filename of each candidate with the git root dir.
- ;; The filename will be in the helm-grep-fname prop.
- (helm-grep-default-directory-fn (lambda ()
- (vc-find-root directory ".git")))
- (helm-ff-default-directory (funcall helm-grep-default-directory-fn)))
- (cl-assert helm-ff-default-directory nil "Not inside a Git repository")
- (helm-do-grep-1 (if all '("") `(,(expand-file-name directory)))
- nil 'git nil default input 'helm-source-grep-git)))
-
- ;;;###autoload
- (defun helm-do-grep-ag (arg)
- "Preconfigured helm for grepping with AG in `default-directory'.
- With prefix-arg prompt for type if available with your AG version."
- (interactive "P")
- (require 'helm-files)
- (helm-grep-ag (expand-file-name default-directory) arg))
-
- ;;;###autoload
- (defun helm-grep-do-git-grep (arg)
- "Preconfigured helm for git-grepping `default-directory'.
- With a prefix arg ARG git-grep the whole repository."
- (interactive "P")
- (require 'helm-files)
- (helm-grep-git-1 default-directory arg))
-
-
- (provide 'helm-grep)
-
- ;; Local Variables:
- ;; byte-compile-warnings: (not obsolete)
- ;; coding: utf-8
- ;; indent-tabs-mode: nil
- ;; End:
-
- ;;; helm-grep.el ends here
|