Martin Polden

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

Date: 2021-07-18 Sun 09:38

Author: Martin Polden