Emacs and ripgrep
I've been using ripgrep for a number of years now. Before it came on the scene I
remember using the early versions of ack and ag, but it appears ripgrep won in
the end and deservedly so. I'm guessing BurntSushi's relentless focus on making
it fast is why it eventually became the most popular.
Since ripgrep is so exceptionally fast, I use it multiple times a day to find
references in code, track down dead code and general searching in large code
repositories.
However, I've noticed that I usually call it from the terminal and as an avid
Emacs user that cannot stand. Emacs has excellent integration with Grep, but it
doesn't support ripgrep by default. It does however support git grep through
its Version Control integration, which is what I've been using this far.
Due to the popularity of ripgrep there are multiple packages that provide
integration with Emacs, such as deadgrep, rg.el and counsel-ag. However, since
my needs for rigrep integration are basic I wanted to see if I could just
implement it myself.
Considerations
Before switching to ripgrep I used to call git grep by using the vc-git
package in Emacs. When invoked the function prompts for the pattern and then
runs git grep in the root directory of the Git repository of the current file.
This is basically how I generally want Grep integration in Emacs to work: I want to grep interactively from any file and have it default to recursively searching the repository root.
This was easy enough to implement with git grep as it's just a thin wrapper
around vc-git-grep:
(defun mpolden/vc-git-grep () "Run git grep interactively in the current VC tree." (interactive) (vc-git-grep (grep-read-regexp) "" (vc-root-dir)))
A few searches for "emacs ripgrep" led me to this useful blog post: Blazing-fast
jump-to-grep in Emacs using ripgrep - apparently you can customize the command
used by grep.el by setting the grep-template variable.
While the examples in the blog post were fine I didn't copy them outright. I use Emacs on multiple machines and therefore I try to ensure that any customizations I add to my emacs.d continue to work even if preferred programs are not installed.
I therefore decided to my custom grep code should support both ripgrep, git
grep and grep, preferring them in that order and falling back gracefully if
e.g. ripgrep isn't installed.
Implementation
After a few iterations I ended up with the following:
(defvar mpolden/rg-template "rg -nH --no-heading <C> -e <R> -- <F>"
"The grep template to use when rg (ripgrep) is installed.")
(defvar mpolden/git-grep-template "git --no-pager grep -n <C> -e <R> -- <F>"
"The grep template to use when git is installed.
This is only used when running grep in a Git repository.")
(defun mpolden/grep ()
"Recursively grep in `default-directory' or current Git repository.
This tries to use either rg or git grep if available, and
otherwise falls back to regular grep."
(interactive)
(let* ((git-root-dir (locate-dominating-file default-directory ".git"))
(dir (or git-root-dir default-directory))
(use-rg (executable-find "rg"))
(use-git (and git-root-dir (executable-find "git")))
(grep-template (cond (use-rg mpolden/rg-template)
(use-git mpolden/git-grep-template))))
(grep-apply-setting 'grep-template grep-template)
;; never use null device as all programs support -H
(grep-apply-setting 'grep-use-null-device nil)
(if (or use-rg use-git)
(lgrep (grep-read-regexp) "" dir)
(rgrep (grep-read-regexp) "*" dir))))
First, we detect if the file we're visiting is inside a Git repository. If it is we recursively grep starting at the root of the repository, if not we recursively grep in the directory of the file we're visiting.
Second, we prefer using ripgrep if it's in PATH and otherwise we fall back
to git grep if we're in a Git repository and git is installed. Finally we
fall back to regular grep if neither of the programs are available.
Finally we call either lgrep or rgrep depending on if the program we
selected searches recursively by default. Since we now detect the Git repository
directly and only use functions from grep we no longer need vc-git at all.
That's it. Now I can use ripgrep from within Emacs with just a few lines of
Elisp!
(My complete Grep integration is available here.)