From 332e4c9817c25b579c4d1ee0abff8fe1db7f9656 Mon Sep 17 00:00:00 2001 From: Eric Danan Date: Tue, 7 Nov 2017 23:52:29 +0100 Subject: [PATCH] new command: counsel-projectile-grep --- counsel-projectile.el | 136 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/counsel-projectile.el b/counsel-projectile.el index dccf5de..c5a08d6 100644 --- a/counsel-projectile.el +++ b/counsel-projectile.el @@ -62,6 +62,21 @@ candidates list of `counsel-projectile-switch-project'." :type 'boolean :group 'counsel-projectile) +(defcustom counsel-projectile-grep-initial-input nil + "Initial minibuffer input for `counsel-projectile-grep'. If +non-nil, it should be a Lisp expression whose evaluation yields +the initial input string. + +Note that you can always insert the value +of `(ivy-thing-at-point)' by hitting \"M-n\" in the minibuffer." + :type '(choice + (const :tag "None" nil) + (const :tag "Symbol at point (generic)" '(thing-at-point 'symbol t)) + (const :tag "Symbol or selection at point (projectile)" '(projectile-symbol-or-selection-at-point)) + (const :tag "Thing at point (ivy)" '(ivy-thing-at-point)) + (sexp :tag "Custom expression")) + :group 'counsel-projectile) + (defcustom counsel-projectile-ag-initial-input nil "Initial minibuffer input for `counsel-projectile-ag'. If non-nil, it should be a Lisp expression whose evaluation yields @@ -203,6 +218,8 @@ afterwards to apply your changes." "open in vc-dir / magit / monky") ("e" counsel-projectile-switch-project-action-run-eshell "start eshell") + ("G" counsel-projectile-switch-project-action-grep + "search with grep") ("a" counsel-projectile-switch-project-action-ag "search with ag") ("R" counsel-projectile-switch-project-action-rg @@ -391,6 +408,114 @@ names as in `ivy--buffer-list'." 'counsel-projectile-switch-to-buffer 'ivy-switch-buffer-transformer) +;;; counsel-projectile-grep + +(defvar counsel-projectile-grep-base-command "grep -rnE %s -- %%s ." + "Format string to use in `cousel-projectile-grep-function' to +construct the command.") + +(defvar counsel-projectile-grep-base-command nil) + +(defun counsel-projectile-grep-function (string) + "Grep in the current project for STRING." + (if (< (length string) 3) + (counsel-more-chars 3) + (let ((default-directory counsel--git-dir) + (regex (counsel-unquote-regex-parens + (setq ivy--old-re + (ivy--regex string))))) + (counsel--async-command (format counsel-projectile-grep-command + (shell-quote-argument regex))) + nil))) + +(defun counsel-projectile-grep-transformer (str) + "Higlight file and line number in STR, first removing the +\"./\" prefix from the filename." + ;; This makes the display consistent with `counsel-git-grep' and + ;; `counsel-ag'-like commands. + (counsel-git-grep-transformer (string-remove-prefix "./" str))) + +(defun counsel-projectile-grep-occur () + "Generate a custom occur buffer for `counsel-projectile-grep'." + ;; Copied from `counsel-grep-like-occur', except that we don't + ;; prepend "./" to the candidates since grep already does so. + (unless (eq major-mode 'ivy-occur-grep-mode) + (ivy-occur-grep-mode) + (setq default-directory counsel--git-dir)) + (setq ivy-text + (and (string-match "\"\\(.*\\)\"" (buffer-name)) + (match-string 1 (buffer-name)))) + (let* ((cmd (format counsel-projectile-grep-command + (shell-quote-argument + (counsel-unquote-regex-parens + (ivy--regex ivy-text))))) + (cands (split-string (shell-command-to-string cmd) "\n" t))) + ;; Need precise number of header lines for `wgrep' to work. + (insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n" + default-directory)) + (insert (format "%d candidates:\n" (length cands))) + (ivy--occur-insert-lines cands))) + +(defun counsel-projectile-grep (&optional options-or-cmd) + "Run a grep search in the project. + +In a git project, use `counsel-git-grep'. In a non-git project, +use grep recursively. + +OPTIONS-OR-CMD, if non-nil, is a string containing either +additional options to be passed to grep, or an alternative git +grep command. It is read from the minibuffer if the function is +called with a prefix argument." + (interactive) + (if (projectile-project-p) + (if (and (eq (projectile-project-vcs) 'git) + projectile-use-git-grep) + (let ((counsel-prompt-function + (lambda () + (ivy-add-prompt-count + (format "%s: " (projectile-prepend-project-name (ivy-state-prompt ivy-last))))))) + (counsel-git-grep (or current-prefix-arg options-or-cmd) + counsel-projectile-grep-initial-input)) + (counsel-require-program (car (split-string counsel-projectile-grep-base-command))) + (let* ((options (if current-prefix-arg + (read-string "options: ") + options-or-cmd)) + (ignored-files + (cl-union (projectile-ignored-files-rel) grep-find-ignored-files)) + (ignored-dirs + (cl-union (projectile-ignored-directories-rel) grep-find-ignored-directories)) + (options + (concat options " " + (mapconcat (lambda (i) + (concat "--exclude=" (shell-quote-argument i))) + ignored-files + " ") + " " + (mapconcat (lambda (i) + (concat "--exclude-dir=" (shell-quote-argument i))) + ignored-dirs + " ")))) + (setq counsel-projectile-grep-command + (format counsel-projectile-grep-base-command options)) + (ivy-set-prompt 'counsel-projectile-grep counsel-prompt-function) + (setq counsel--git-dir (projectile-project-root)) + (ivy-read (projectile-prepend-project-name "grep") + #'counsel-projectile-grep-function + :initial-input counsel-projectile-grep-initial-input + :dynamic-collection t + :keymap counsel-ag-map + :history 'counsel-git-grep-history + :action #'counsel-git-grep-action + :unwind (lambda () + (counsel-delete-process) + (swiper--cleanup)) + :caller 'counsel-projectile-grep))) + (user-error "You're not in a project"))) + +(counsel-set-async-exit-code 'counsel-projectile-grep 1 "No matches found") +(ivy-set-occur 'counsel-projectile-grep 'counsel-projectile-grep-occur) +(ivy-set-display-transformer 'counsel-projectile-grep 'counsel-projectile-grep-transformer) + ;;; counsel-projectile-ag ;;;###autoload @@ -545,6 +670,12 @@ in vc-dir / magit / monky." (let ((projectile-switch-project-action 'projectile-run-eshell)) (counsel-projectile-switch-project-action project))) +(defun counsel-projectile-switch-project-action-grep (project) + "Action for `counsel-projectile-switch-project' to search +PROJECT with `grep'." + (let ((projectile-switch-project-action 'counsel-projectile-ag)) + (counsel-projectile-switch-project-action project))) + (defun counsel-projectile-switch-project-action-ag (project) "Action for `counsel-projectile-switch-project' to search PROJECT with `ag'." @@ -659,6 +790,9 @@ With a prefix ARG invalidates the cache first." (def-projectile-commander-method ?b "Switch to project buffer." (counsel-projectile-switch-to-buffer)) + (def-projectile-commander-method ?g + "Run grep on project." + (counsel-projectile-grep)) (def-projectile-commander-method ?A "Search project files with ag." (counsel-projectile-ag)) @@ -675,6 +809,7 @@ With a prefix ARG invalidates the cache first." (define-key projectile-mode-map [remap projectile-find-file] #'counsel-projectile-find-file) (define-key projectile-mode-map [remap projectile-find-dir] #'counsel-projectile-find-dir) (define-key projectile-mode-map [remap projectile-switch-project] #'counsel-projectile-switch-project) + (define-key projectile-mode-map [remap projectile-grep] #'counsel-projectile-grep) (define-key projectile-mode-map [remap projectile-ag] #'counsel-projectile-ag) (define-key projectile-mode-map [remap projectile-switch-to-buffer] #'counsel-projectile-switch-to-buffer) (counsel-projectile-commander-bindings)) @@ -684,6 +819,7 @@ With a prefix ARG invalidates the cache first." (define-key projectile-mode-map [remap projectile-find-file] nil) (define-key projectile-mode-map [remap projectile-find-dir] nil) (define-key projectile-mode-map [remap projectile-switch-project] nil) + (define-key projectile-mode-map [remap projectile-grep] nil) (define-key projectile-mode-map [remap projectile-ag] nil) (define-key projectile-mode-map [remap projectile-switch-to-buffer] nil) (projectile-commander-bindings))))