branch: externals/urgrep commit 35586c85b915adbcdcee0f88fbf9adb96a61abfb Author: Jim Porter <jporterb...@gmail.com> Commit: Jim Porter <jporterb...@gmail.com>
Add support for passing `:directory` to `urgrep-command` This also fixes the old limitation with the Eshell builtin so now you can pass directories as arguments to it. --- NEWS.md | 6 ++- urgrep-tests.el | 126 ++++++++++++++++++++++++++++++++++++++++++-------------- urgrep.el | 107 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 175 insertions(+), 64 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0b6e08182b..14a2b8fe98 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,11 +5,15 @@ ### New features - Add support for toggling whether to search in hidden files (`M-s h` in the search prompt, or `urgrep-search-hidden-files` globally) +- Add `:directory` key to `urgrep-command`, allowing you to specify zero or more + directories to search in +- `urgrep` builtin for Eshell now supports specifying search directories as + arguments - Allow setting the search tool to use on the fly when reading the query (`M-s t` in the search prompt) ### Breaking changes -- `urgrep-run-command` now takes `directory` and `tool` as optional keys +- `urgrep-run-command` now takes `:tool` as an optional key to match `urgrep` --- diff --git a/urgrep-tests.el b/urgrep-tests.el index e4aca926ce..6883cab221 100644 --- a/urgrep-tests.el +++ b/urgrep-tests.el @@ -135,7 +135,7 @@ joined to compare against COMMAND." (let ((tool (assq 'ugrep urgrep-tools)) (common-args '("ugrep" "--color=always" "--colors=mt=01;31:fn=35:ln=:bn=:se=:sl=:cx=:ne" - "-n" "--ignore-files"))) + "-rn" "--ignore-files"))) ;; String/case (urgrep-tests/check-command (urgrep-command "foo" :tool tool) @@ -201,10 +201,18 @@ joined to compare against COMMAND." (urgrep-command "foo" :tool tool :files '("*.c" "*.h")) (append common-args '("--include=*.c" "--include=*.h" "--heading" "--break" "-i" "-F" "-e" "foo"))) + ;; Directory + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory "dir") + (append common-args '("--heading" "--break" "-i" "-F" "-e" "foo" "dir"))) + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory '("dir1" "dir2")) + (append common-args '("--heading" "--break" "-i" "-F" "-e" "foo" "dir1" + "dir2"))) ;; Color (urgrep-tests/check-command (urgrep-command "foo" :tool tool :color nil) - (append '("ugrep" "--color=never" "-n" "--ignore-files" "--heading" + (append '("ugrep" "--color=never" "-rn" "--ignore-files" "--heading" "--break" "-i" "-F" "-e" "foo"))))) (ert-deftest urgrep-tests/command/ripgrep () @@ -275,6 +283,13 @@ joined to compare against COMMAND." (urgrep-command "foo" :tool tool :files '("*.c" "*.h")) (append common-args '("-g" "*.c" "-g" "*.h" "--heading" "-i" "-F" "--" "foo"))) + ;; Directory + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory "dir") + (append common-args '("--heading" "-i" "-F" "--" "foo" "dir"))) + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory '("dir1" "dir2")) + (append common-args '("--heading" "-i" "-F" "--" "foo" "dir1" "dir2"))) ;; Color (urgrep-tests/check-command (urgrep-command "foo" :tool tool :color nil) @@ -343,6 +358,13 @@ joined to compare against COMMAND." (urgrep-command "foo" :tool tool :files '("*.c" "*.h")) (append common-args '("-G" "^[^\\000]*\\.(c|h)$" "--group" "-i" "-Q" "--" "foo"))) + ;; Directory + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory "dir") + (append common-args '("--group" "-i" "-Q" "--" "foo" "dir"))) + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory '("dir1" "dir2")) + (append common-args '("--group" "-i" "-Q" "--" "foo" "dir1" "dir2"))) ;; Color (urgrep-tests/check-command (urgrep-command "foo" :tool tool :color nil) @@ -421,6 +443,15 @@ joined to compare against COMMAND." (urgrep-command "foo" :tool tool :files '("*.c" "*.h")) (append common-args no-hidden-args '("-G" "^[^\\000]*\\.(c|h)$" "--group" "-i" "-Q" "--" "foo"))) + ;; Directory + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory "dir") + (append common-args no-hidden-args '("--group" "-i" "-Q" "--" "foo" + "dir"))) + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory '("dir1" "dir2")) + (append common-args no-hidden-args '("--group" "-i" "-Q" "--" "foo" "dir1" + "dir2"))) ;; Color (urgrep-tests/check-command (urgrep-command "foo" :tool tool :color nil) @@ -510,6 +541,26 @@ joined to compare against COMMAND." (urgrep-command "foo" :tool tool :files '("*.c" "*.h")) (append common-args group-args '("-i" "-F" "-e" "foo" "--") no-hidden-args '("*.c" "*.h"))) + ;; Directory + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory "dir") + (append common-args group-args '("-i" "-F" "-e" "foo" "--") no-hidden-args + '("dir"))) + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :directory '("dir1" "dir2")) + (append common-args group-args '("-i" "-F" "-e" "foo" "--") no-hidden-args + '("dir1" "dir2"))) + ;; File wildcard + Directory + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :files "*.el" :directory "dir") + (append common-args group-args '("-i" "-F" "-e" "foo" "--") no-hidden-args + '(":(glob)dir/**/*.el"))) + (urgrep-tests/check-command + (urgrep-command "foo" :tool tool :files '("*.c" "*.h") + :directory '("dir1" "dir2")) + (append common-args group-args '("-i" "-F" "-e" "foo" "--") no-hidden-args + '(":(glob)dir1/**/*.c" ":(glob)dir2/**/*.c" ":(glob)dir1/**/*.h" + ":(glob)dir2/**/*.h"))) ;; Color (urgrep-tests/check-command (urgrep-command "foo" :tool tool :color nil) @@ -520,59 +571,74 @@ joined to compare against COMMAND." (ert-deftest urgrep-tests/command/grep () (let ((tool (assq 'grep urgrep-tools)) - (template (concat "^find \\(\\|.+ \\)\\. \\(\\|.+ \\)%s\\(\\|.+ \\)" + (template (concat "^find \\(\\|.+ \\)%s \\(\\|.+ \\)%s\\(\\|.+ \\)" "grep %s\\(\\|.+ \\)%s")) (escape (lambda (i) (regexp-quote (shell-quote-argument i))))) ;; String/case - (should (string-match (format template "" "--color=always -i -F" "foo") + (should (string-match (format template "." "" "--color=always -i -F" "foo") (urgrep-command "foo" :tool tool))) - (should (string-match (format template "" "--color=always -F" "Foo") + (should (string-match (format template "." "" "--color=always -F" "Foo") (urgrep-command "Foo" :tool tool))) (let ((case-fold-search nil)) - (should (string-match (format template "" "--color=always -F" "foo") + (should (string-match (format template "." "" "--color=always -F" "foo") (urgrep-command "foo" :tool tool)))) - (should (string-match (format template "" "--color=always -i -F" "foo") + (should (string-match (format template "." "" "--color=always -i -F" "foo") (urgrep-command "foo" :tool tool :case-fold t))) - (should (string-match (format template "" "--color=always -F" "foo") + (should (string-match (format template "." "" "--color=always -F" "foo") (urgrep-command "foo" :tool tool :case-fold nil))) - (should (string-match (format template "" "--color=always -i -F" "foo") + (should (string-match (format template "." "" "--color=always -i -F" "foo") (urgrep-command "foo" :tool tool :case-fold 'smart))) - (should (string-match (format template "" "--color=always -F" "Foo") + (should (string-match (format template "." "" "--color=always -F" "Foo") (urgrep-command "Foo" :tool tool :case-fold 'smart))) ;; Group - (should (string-match (format template "" "--color=always -i -F" "foo") + (should (string-match (format template "" "" "--color=always -i -F" "foo") (urgrep-command "foo" :tool tool :group nil))) ;; Regexp (let ((query (funcall escape "(foo)"))) - (should (string-match (format template "" "--color=always -i -G" query) - (urgrep-command "(foo)" :tool tool :regexp t))) - (should (string-match (format template "" "--color=always -i -G" query) - (urgrep-command "(foo)" :tool tool :regexp 'bre))) - (should (string-match (format template "" "--color=always -i -E" query) - (urgrep-command "(foo)" :tool tool :regexp 'ere))) - (should (string-match (format template "" "--color=always -i -P" query) - (urgrep-command "(foo)" :tool tool :regexp 'pcre)))) + (should (string-match + (format template "." "" "--color=always -i -G" query) + (urgrep-command "(foo)" :tool tool :regexp t))) + (should (string-match + (format template "." "" "--color=always -i -G" query) + (urgrep-command "(foo)" :tool tool :regexp 'bre))) + (should (string-match + (format template "." "" "--color=always -i -E" query) + (urgrep-command "(foo)" :tool tool :regexp 'ere))) + (should (string-match + (format template "." "" "--color=always -i -P" query) + (urgrep-command "(foo)" :tool tool :regexp 'pcre)))) ;; Context - (should (string-match (format template "" "--color=always -C3 -i -F" "foo") - (urgrep-command "foo" :tool tool :context 3))) - (should (string-match (format template "" "--color=always -C3 -i -F" "foo") - (urgrep-command "foo" :tool tool :context '(3 . 3)))) - (should (string-match (format template "" "--color=always -B2 -A4 -i -F" - "foo") - (urgrep-command "foo" :tool tool :context '(2 . 4)))) + (should (string-match + (format template "." "" "--color=always -C3 -i -F" "foo") + (urgrep-command "foo" :tool tool :context 3))) + (should (string-match + (format template "." "" "--color=always -C3 -i -F" "foo") + (urgrep-command "foo" :tool tool :context '(3 . 3)))) + (should (string-match + (format template "." "" "--color=always -B2 -A4 -i -F" "foo") + (urgrep-command "foo" :tool tool :context '(2 . 4)))) ;; File wildcard (let ((escape (lambda (i) (regexp-quote (shell-quote-argument i))))) (should (string-match - (format template (concat "-i?name " (funcall escape "*.el") " ") + (format template "." + (concat "-i?name " (funcall escape "*.el") " ") "--color=always -i -F" "foo") (urgrep-command "foo" :tool tool :files "*.el"))) (should (string-match - (format template (concat "-i?name " (funcall escape "*.c") " -o " - "-i?name " (funcall escape "*.h") " ") + (format template "." + (concat "-i?name " (funcall escape "*.c") " -o " + "-i?name " (funcall escape "*.h") " ") "--color=always -i -F" "foo") (urgrep-command "foo" :tool tool :files '("*.c" "*.h"))))) + ;; Directory + (should (string-match + (format template "dir" "" "--color=always -i -F" "foo") + (urgrep-command "foo" :tool tool :directory "dir"))) + (should (string-match + (format template "dir1 dir2" "" "--color=always -i -F" "foo") + (urgrep-command "foo" :tool tool :directory '("dir1" "dir2")))) ;; Color - (should (string-match (format template "" "+-i -F" "foo") + (should (string-match (format template "." "" "+-i -F" "foo") (urgrep-command "foo" :tool tool :color nil))))) (ert-deftest urgrep-tests/get-tool/default () diff --git a/urgrep.el b/urgrep.el index 7fb2c639d2..1ce6b98ba4 100644 --- a/urgrep.el +++ b/urgrep.el @@ -225,15 +225,19 @@ one for each `:abbreviate' key found." (`(,b . ,a) (list (format "-B%d" b) (format "-A%d" a))))) (cl-defun urgrep--rgrep-command (query &key tool regexp case-fold hidden files - context color &allow-other-keys) + directory context color + &allow-other-keys) "Get the command to run for QUERY when using rgrep. -Optional keys TOOL, REGEXP, CASE-FOLD, HIDDEN, FILES, CONTEXT, -and COLOR are as in `urgrep-command'." +Optional keys TOOL, REGEXP, CASE-FOLD, HIDDEN, FILES, DIRECTORY, +CONTEXT, and COLOR are as in `urgrep-command'." (grep-compute-defaults) ;; Locally add options to `grep-find-template' that grep.el isn't aware of. (let ((grep-find-template grep-find-template) (grep-highlight-matches (if color 'always nil)) - (files (if files (mapconcat #'identity files " ") "*"))) + (files (if files (mapconcat #'identity files " ") "*")) + (directory (when directory + (mapconcat #'urgrep--maybe-shell-quote-argument + directory " ")))) (pcase-dolist (`(,k . ,v) `((regexp . ,regexp) (case-fold . ,case-fold) (context . ,context))) @@ -253,7 +257,7 @@ and COLOR are as in `urgrep-command'." grep-find-ignored-files (cons ".*" (seq-filter (lambda (s) (not (string-prefix-p "." s))) grep-find-ignored-files)))) - (let ((command (rgrep-default-command query files nil))) + (let ((command (rgrep-default-command query files directory))) (save-match-data ;; Hide excessive part of rgrep command. (when (string-match @@ -263,6 +267,40 @@ and COLOR are as in `urgrep-command'." 'abbreviated-command t command))) command)))) +(cl-defun urgrep--git-grep-command (query &key tool regexp case-fold hidden + files directory group context color) + "Get the command to run for QUERY when using git grep. +Optional keys TOOL, REGEXP, CASE-FOLD, HIDDEN, FILES, DIRECTORY, +CONTEXT, and COLOR are as in `urgrep-command'." + ;; XXX: This is very similar to the implementation in `urgrep-command', except + ;; that we do some extra work to generate pathspecs. Can we factor out some of + ;; this? + (let* ((arguments (urgrep--get-prop 'arguments tool)) + (abbrev (urgrep--get-prop 'abbreviations tool)) + (pathspecs + (if (and files directory) + (mapcan + (lambda (file) + (mapcar (lambda (dir) + (concat ":(glob)" (file-name-concat dir "**" file))) + directory)) + files) + (or files directory))) + (props `((executable . ,(urgrep--get-prop 'executable-name tool)) + (query . ,query) + ,@(mapcar (pcase-lambda (`(,k . ,v)) + (cons k (urgrep--get-prop-pcase + k tool v "-arguments"))) + `((regexp . ,regexp) + (case-fold . ,case-fold) + (hidden-file . ,hidden) + (pathspec . ,pathspecs) + (group . ,group) + (context . ,context) + (color . ,color)))))) + (urgrep--flatten-arguments (cl-sublis props arguments) + abbrev))) + (defun urgrep--rgrep-process-setup () "Set up environment variables for rgrep. See also `grep-process-setup'." @@ -278,9 +316,9 @@ See also `grep-process-setup'." `((ugrep (executable-name . "ugrep") (regexp-syntax bre ere pcre) - (arguments executable (:abbreviate color "-n" "--ignore-files") + (arguments executable (:abbreviate color "-rn" "--ignore-files") hidden-file file-wildcards group context case-fold regexp "-e" - query) + query directory) (regexp-arguments ('bre '("-G")) ('ere '("-E")) ('pcre '("-P")) @@ -290,6 +328,7 @@ See also `grep-process-setup'." (file-wildcards-arguments ((and x (pred identity)) (mapcar (lambda (i) (concat "--include=" i)) x))) + (directory-arguments (x x)) (group-arguments ((pred identity) '("--heading" "--break"))) (context-arguments . ,urgrep--context-arguments) (color-arguments @@ -300,13 +339,14 @@ See also `grep-process-setup'." (executable-name . "rg") (regexp-syntax pcre) (arguments executable (:abbreviate color) hidden-file file-wildcards group - context case-fold regexp "--" query) + context case-fold regexp "--" query directory) (regexp-arguments ('nil '("-F"))) (case-fold-arguments ((pred identity) '("-i"))) (hidden-file-arguments ((pred identity) '("--hidden"))) (file-wildcards-arguments ((and x (pred identity)) (flatten-list (mapcar (lambda (i) (cons "-g" i)) x)))) + (directory-arguments (x x)) (group-arguments ('nil '("--no-heading")) (_ '("--heading"))) (context-arguments . ,urgrep--context-arguments) @@ -320,7 +360,7 @@ See also `grep-process-setup'." (executable-name . "ag") (regexp-syntax pcre) (arguments executable (:abbreviate color) hidden-file file-wildcards group - context case-fold regexp "--" query) + context case-fold regexp "--" query directory) (regexp-arguments ('nil '("-Q"))) (case-fold-arguments ('nil '("-s")) (_ '("-i"))) @@ -328,6 +368,7 @@ See also `grep-process-setup'." (file-wildcards-arguments ((and x (pred identity)) (list "-G" (urgrep--wildcards-to-regexp x 'pcre)))) + (directory-arguments (x x)) (group-arguments ('nil '("--nogroup")) (_ '("--group"))) (context-arguments . ,urgrep--context-arguments) @@ -338,7 +379,7 @@ See also `grep-process-setup'." (executable-name . "ack") (regexp-syntax pcre) (arguments executable (:abbreviate color) hidden-file file-wildcards group - context case-fold regexp "--" query) + context case-fold regexp "--" query directory) (regexp-arguments ('nil '("-Q"))) (case-fold-arguments ((pred identity) '("-i"))) (hidden-file-arguments ('nil '("--ignore-dir=match:/^\\./" @@ -346,6 +387,7 @@ See also `grep-process-setup'." (file-wildcards-arguments ((and x (pred identity)) (list "-G" (urgrep--wildcards-to-regexp x 'pcre)))) + (directory-arguments (x x)) (group-arguments ('nil '("--nogroup")) (_ '("--group"))) (context-arguments . ,urgrep--context-arguments) @@ -360,10 +402,11 @@ See also `grep-process-setup'." ;; with people who want to customize the arguments. (vc-backend . "Git") (regexp-syntax bre ere pcre) + (command-function . ,#'urgrep--git-grep-command) (arguments executable (:abbreviate "--no-pager" color "--no-index" "--exclude-standard" "-n") group context case-fold regexp "-e" query "--" hidden-file - file-wildcards) + pathspec) (abbreviations "grep") (regexp-arguments ('bre '("-G")) ('ere '("-E")) @@ -371,7 +414,7 @@ See also `grep-process-setup'." (_ '("-F"))) (case-fold-arguments ((pred identity) '("-i"))) (hidden-file-arguments ('nil '(":!.*"))) - (file-wildcards-arguments (x x)) + (pathspec-arguments (x x)) (group-arguments ((pred identity) '("--heading" "--break"))) (context-arguments . ,urgrep--context-arguments) ;; git is a bit odd in that color specification happens *before* the @@ -546,7 +589,7 @@ in `urgrep-tools'. Otherwise, return TOOL as-is." ;;;###autoload (cl-defun urgrep-command (query &key tool regexp (case-fold 'inherit) hidden - files (group t) (context 0) (color t)) + files directory (group t) (context 0) (color t)) "Return a command to use to search for QUERY. Several keyword arguments can be supplied to adjust the resulting command: @@ -577,6 +620,7 @@ COLOR: non-nil (the default) if the output should use color." (with-connection-local-variables (let* ((regexp-syntax (if (eq regexp t) urgrep-regexp-syntax regexp)) (files (if (listp files) files (list files))) + (directory (if (listp directory) directory (list directory))) (tool (or (urgrep-get-tool tool) (error "unknown tool %s" tool))) (tool-re-syntax (urgrep--get-best-syntax regexp-syntax tool)) @@ -591,7 +635,8 @@ COLOR: non-nil (the default) if the output should use color." (if cmd-fun (funcall cmd-fun query :tool tool :regexp tool-re-syntax :case-fold case-fold :hidden hidden :files files - :group group :context context :color color) + :directory directory :group group :context context + :color color) (let ((arguments (urgrep--get-prop 'arguments tool)) (abbrev (urgrep--get-prop 'abbreviations tool)) (props `((executable . ,(urgrep--get-prop 'executable-name tool)) @@ -603,6 +648,7 @@ COLOR: non-nil (the default) if the output should use color." (case-fold . ,case-fold) (hidden-file . ,hidden) (file-wildcards . ,files) + (directory . ,directory) (group . ,group) (context . ,context) (color . ,color)))))) @@ -1280,33 +1326,28 @@ This is meant to be used as a command in Eshell." :usage "[OPTION]... PATTERN [PATH] Recursively search for PATTERN within PATH.") (unless args (error "Expected a search pattern")) - ;; TODO: Add support for multiple paths. - (when (> (length args) 2) (error "Only one path supported currently")) - (let ((query (car args)) - (directory (or (cadr args) default-directory)) - (context - (cond (context-around (string-to-number context-around)) - ((or context-before context-after) - (cons (if context-before (string-to-number context-before) 0) - (if context-after (string-to-number context-after) 0))))) - options) + (let* ((query (car args)) + (directory (cdr args)) + (context + (cond + (context-around (string-to-number context-around)) + ((or context-before context-after) + (cons (if context-before (string-to-number context-before) 0) + (if context-after (string-to-number context-after) 0))))) + options) ;; Fill the options to pass to `urgrep'. - (when context (setq options `(:context ,context . ,options))) - (when group (setq options `(:group ,(car group) . ,options))) + (when directory (setq options `(:directory ,directory . ,options))) + (when context (setq options `(:context ,context . ,options))) + (when group (setq options `(:group ,(car group) . ,options))) (when case-fold (setq options `(:case-fold ,(car case-fold) . ,options))) - (when regexp (setq options `(:regexp ,(car regexp) . ,options))) + (when regexp (setq options `(:regexp ,(car regexp) . ,options))) ;; Run `urgrep'. (if (and (not (bound-and-true-p eshell-plain-grep-behavior)) (eshell-interactive-output-p) (not eshell-in-pipeline-p) (not eshell-in-subcommand-p)) - ;; FIXME: We probably shouldn't do this, since it results in confusing - ;; relative file names. - (let ((default-directory directory)) - (apply #'urgrep query options)) + (apply #'urgrep query options) ;; Return the arguments to run directly. - (when (not (equal directory default-directory)) - (error "Can't use plain urgrep with a non-default directory yet")) (unless (eshell-interactive-output-p) (setq options `(:color nil . ,options))) (throw 'eshell-replace-command