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.)