Support using ripgrep in project-find-regexp and friends

Performance results vary here.  Some projects and search terms
don't see much of a change, but for some (including Emacs sources
checkout and case-insensitive search) the switch to ripgrep shows
~2-3x speed improvement.  Another piece of anecdata here:
https://lists.gnu.org/archive/html/emacs-devel/2020-06/msg00802.html

* lisp/progmodes/xref.el (xref-search-program-alist)
(xref-search-program): New user options.
(xref-matches-in-files): Use them.
This commit is contained in:
Dmitry Gutov 2020-12-04 03:37:10 +02:00
parent 25df2375db
commit f2a3d6e28d
2 changed files with 58 additions and 7 deletions

View file

@ -1187,6 +1187,9 @@ project's root directory, respectively.
So typing 'C-u RET' in the "*xref*" buffer quits its window
before navigating to the selected location.
*** New option xref-search-program.
So far Grep and ripgrep are supported.
** json.el
---

View file

@ -1262,12 +1262,56 @@ IGNORES is a list of glob patterns for files to ignore."
(declare-function tramp-tramp-file-p "tramp")
(declare-function tramp-file-local-name "tramp")
;; TODO: Experiment with 'xargs -P4' (or any other number).
;; This speeds up either command, even more than rg's '-j4' does.
;; Ripgrep gets jumbled output, though, even with --line-buffered.
;; But Grep seems to be stable. Even without --line-buffered.
(defcustom xref-search-program-alist
'((grep
.
;; '-s' because 'git ls-files' can output broken symlinks.
"xargs -0 grep <C> -snHE -e <R>")
(ripgrep
.
;; Note: by default, ripgrep's output order is non-deterministic
;; (https://github.com/BurntSushi/ripgrep/issues/152)
;; because it does the search in parallel. You can use the template
;; without the '| sort ...' part if GNU sort is not available on
;; your system and/or stable ordering is not important to you.
;; Note#2: '!*/' is there to filter out dirs (e.g. submodules).
"xargs -0 rg <C> -nH --no-messages -g '!*/' -e <R> | sort -t: -k1 -k2n"
))
"Associative list mapping program identifiers to command templates.
Program identifier should be a symbol, named after the search program.
The command template must be a shell command (or usually a
pipeline) that will search the list of files which will be piped
from stdin. The template should have following fields:
<C> for extra arguments such as -i and --color
<R> for the regexp itself (in Extended format)"
:type '(repeat
(cons (symbol :tag "Program identifier")
(string :tag "Command template"))))
(defcustom xref-search-program 'grep
"The program to use to search inside files.
This must reference a corresponding entry in `xref-search-program-alist'."
:type `(choice
(const :tag "Use Grep" grep)
(const :tag "Use ripgrep" ripgrep)
(symbol :tag "User defined")))
;;;###autoload
(defun xref-matches-in-files (regexp files)
"Find all matches for REGEXP in FILES.
Return a list of xref values.
FILES must be a list of absolute file names."
(cl-assert (consp files))
(require 'grep)
(defvar grep-highlight-matches)
(pcase-let*
((output (get-buffer-create " *project grep output*"))
(`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist))
@ -1277,13 +1321,17 @@ FILES must be a list of absolute file names."
;; first file is remote, they all are, and on the same host.
(dir (file-name-directory (car files)))
(remote-id (file-remote-p dir))
;; 'git ls-files' can output broken symlinks.
(command (format "xargs -0 grep %s -snHE -e %s"
(if (and case-fold-search
(isearch-no-upper-case-p regexp t))
"-i"
"")
(shell-quote-argument (xref--regexp-to-extended regexp)))))
;; The 'auto' default would be fine too, but ripgrep can't handle
;; the options we pass in that case.
(grep-highlight-matches nil)
(command (grep-expand-template (cdr
(or
(assoc
xref-search-program
xref-search-program-alist)
(user-error "Unknown search program `%s'"
xref-search-program)))
(xref--regexp-to-extended regexp))))
(when remote-id
(require 'tramp)
(setq files (mapcar