From 0d3fa647fd6a0c3a27bc5f9905e24103a3d6b617 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Thu, 16 Sep 2021 17:14:33 +0200
Subject: [PATCH] xref ripgrep support

Auto-detect presence of ripgrep and use it if available.
It speeds up `xref-find-references` (M-q), between a little and very much.
---
 lisp/cedet/semantic/symref/grep.el | 121 +++++++++++++++++++++--------
 1 file changed, 88 insertions(+), 33 deletions(-)

diff --git a/lisp/cedet/semantic/symref/grep.el b/lisp/cedet/semantic/symref/grep.el
index 1e282c3052..106d46dc76 100644
--- a/lisp/cedet/semantic/symref/grep.el
+++ b/lisp/cedet/semantic/symref/grep.el
@@ -79,16 +79,7 @@ semantic-symref-derive-find-filepatterns
                    ;; Only take in simple patterns, so try to convert this one.
                    (string-match "\\\\\\.\\([^\\'>]+\\)\\\\'" (car X)))
           (push (concat "*." (match-string 1 (car X))) pat))))
-    ;; Convert the list into some find-flags.
-    (if (null pat)
-        (error "Customize `semantic-symref-filepattern-alist' for %S"
-               major-mode)
-      (let ((args `("-name" ,(car pat))))
-        (if (null (cdr pat))
-            args
-          `("(" ,@args
-            ,@(mapcan (lambda (s) `("-o" "-name" ,s)) (cdr pat))
-            ")"))))))
+    pat))
 
 (defvar semantic-symref-grep-flags)
 
@@ -133,12 +124,91 @@ semantic-symref-grep-shell
   :group 'semantic
   :type 'string)
 
+(defcustom semantic-symref-grep-tool 'auto-detect
+  "The tool to use for searching in files."
+  :type '(choice (const :tag "Auto-detect" auto-detect)
+                 (const :tag "Ripgrep" ripgrep)
+                 (const :tag "find + grep" find-grep))
+  :group 'semantic)
+
+(defcustom semantic-symref-grep-ripgrep-command "rg"
+  "Name of the ripgrep command to use."
+  :type 'file
+  :group 'semantic)
+
+(defun semantic-symref-grep--auto-detect-tool ()
+  (let ((have-rg
+         (with-temp-buffer
+           (let ((hello-file (grep-hello-file)))
+             (let ((process-file-side-effects nil))
+               (unwind-protect
+                   (eql (ignore-errors
+                          (process-file
+                           semantic-symref-grep-ripgrep-command
+                           nil t nil "^Copyright"
+                           (file-local-name hello-file)))
+                        0)
+                 (when (file-remote-p hello-file)
+                   (delete-file hello-file))))))))
+    (if have-rg
+        'ripgrep
+      'find-grep)))
+
 (defun semantic-symref-grep--quote-extended (string)
   "Quote STRING as an extended-syntax regexp."
   (replace-regexp-in-string (rx (in ".^$*+?|{}[]()|\\"))
                             (lambda (s) (concat "\\" s))
                             string nil t))
 
+(defun semantic-symref-grep--command (rootdir filepatterns file-name-only
+                                      pattern-type pattern)
+  "Compute search command to run.
+ROOTDIR is the root of the tree to search.  FILEPATTERNS is a
+list of glob patterns that match the files to search.
+If FILE-NAME-ONLY is non-nil, the search only wants the names of files.
+PATTERN-TYPE indicates the type of PATTERN:
+ `regexp'     -- PATTERN is an extended (egrep) regexp.
+ `symbol'     -- PATTERN is a literal identifier.
+
+Return the command and arguments as a list of strings."
+  (when (eq semantic-symref-grep-tool 'auto-detect)
+    (setq semantic-symref-grep-tool
+          (semantic-symref-grep--auto-detect-tool)))
+  (cond
+   ((eq semantic-symref-grep-tool 'ripgrep)
+    (let ((filepat-args
+           (mapcan (lambda (s) (list "-g" s)) filepatterns))
+          (flags (cond (file-name-only "-l")
+                       ((eq pattern-type 'symbol) "-nw")
+                       (t "-n")))
+          (pat-arg (if (eq pattern-type 'symbol)
+                       (semantic-symref-grep--quote-extended pattern)
+                     pattern))
+          (dir (expand-file-name rootdir)))
+      `(,semantic-symref-grep-ripgrep-command
+        ,@filepat-args ,flags "-e" ,pat-arg ,dir)))
+   (t
+    (let* ((filepat-args
+            ;; Convert the list into some find-flags.
+            (let ((args `("-name" ,(car filepatterns))))
+              (if (cdr filepatterns)
+                  `("(" ,@args
+                    ,@(mapcan (lambda (s) (list "-o" "-name" s))
+                              (cdr filepatterns))
+                    ")")
+                args)))
+           (filepattern (mapconcat #'shell-quote-argument filepat-args " "))
+           (grepflags (cond (file-name-only "-l ")
+                            ((eq pattern-type 'regexp) "-nE ")
+                            (t "-nwE ")))
+           (pat-arg (if (eq pattern-type 'symbol)
+                        (semantic-symref-grep--quote-extended pattern)
+                      pattern))
+           (cmd (semantic-symref-grep-use-template
+                 (directory-file-name (file-local-name rootdir))
+                 filepattern grepflags pat-arg)))
+      `(,semantic-symref-grep-shell ,shell-command-switch ,cmd)))))
+
 (cl-defmethod semantic-symref-perform-search ((tool semantic-symref-tool-grep))
   "Perform a search with Grep."
   ;; Grep doesn't support some types of searches.
@@ -150,33 +220,18 @@ semantic-symref-perform-search
   (let* (;; Find the file patterns to use.
 	 (rootdir (semantic-symref-calculate-rootdir))
 	 (filepatterns (semantic-symref-derive-find-filepatterns))
-         (filepattern (mapconcat #'shell-quote-argument filepatterns " "))
-	 ;; Grep based flags.
-	 (grepflags (cond ((eq (oref tool resulttype) 'file)
-                           "-l ")
-                          ((eq (oref tool searchtype) 'regexp)
-                           "-nE ")
-                          (t "-nw ")))
-         (searchfor (oref tool searchfor))
-         (greppat (if (eq (oref tool searchtype) 'regexp)
-                      searchfor
-                    (semantic-symref-grep--quote-extended searchfor)))
-	 ;; Misc
-	 (b (get-buffer-create "*Semantic SymRef*"))
-	 (ans nil)
-	 )
+         (greppat (oref tool searchfor))
+         (file-names-only (eq (oref tool resulttype) 'file))
+         (search-type (oref tool searchtype))
+         (command (semantic-symref-grep--command
+                   rootdir filepatterns file-names-only search-type greppat))
+	 (b (get-buffer-create "*Semantic SymRef*")))
 
     (with-current-buffer b
       (erase-buffer)
       (setq default-directory rootdir)
-      (let ((cmd (semantic-symref-grep-use-template
-                  (directory-file-name (file-local-name rootdir))
-                  filepattern grepflags greppat)))
-        (process-file semantic-symref-grep-shell nil b nil
-                      shell-command-switch cmd)))
-    (setq ans (semantic-symref-parse-tool-output tool b))
-    ;; Return the answer
-    ans))
+      (apply #'process-file (car command) nil b nil (cdr command)))
+    (semantic-symref-parse-tool-output tool b)))
 
 (cl-defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-grep))
   "Parse one line of grep output, and return it as a match list.
-- 
2.21.1 (Apple Git-122.3)

