Ripgrepping with Helm


Sometimes curiosity leads me to use alternative packages for built-in solutions which I should have explored better. For instance, it took me moving through helm-ag and helm-rg1 before realising Helm already comes with a functionality suitable for my needs: helm-do-grep-ag.

By now I expect you to be familiar with the wonders of ripgrep. I was heavily relying on counsel-rg back in my Ivy days, and I needed a similar command in Helm. Truth be told, helm-do-grep-ag is not the replacement I was looking for, but understanding its code helped me devise my own utilities for super fast searches.

First of all, let’s use ripgrep instead of ag:

(setq helm-grep-ag-command (concat "rg"
                                   " --color=never"
                                   " --smart-case"
                                   " --no-heading"
                                   " --line-number %s %s %s")
      helm-grep-file-path-style 'relative)

Note that the string for helm-grep-ag-command comes from its documentation. I chose relative for helm-grep-file-path-style because I find relative file paths in the search result buffer more useful than their base name.

Now, helm-do-grep-ag defers to helm-grep-ag, which in turns defers to helm-grep-ag-1, and this is the Helm facility I can leverage for my own ripgrep variant.

(defun mu-helm-rg (directory &optional with-types)
  "Search in DIRECTORY with RG.
With WITH-TYPES, ask for file types to search in."
  (interactive "P")
  (require 'helm-adaptive)
  (helm-grep-ag-1 (expand-file-name directory)
                  (helm-aif (and with-types
                                 (helm-grep-ag-get-types))
                      (helm-comp-read
                       "RG type: " it
                       :must-match t
                       :marked-candidates t
                       :fc-transformer 'helm-adaptive-sort
                       :buffer "*helm rg types*"))))

It’s basically helm-grep-ag with minor aesthetic changes, but you know me. Note the with-types parameter. If I use this command with C-u I get to pick one or more file types to narrow the search down to. I don’t remember if this is possible with counsel-rg, but in large projects it is an absolute must have. Furthermore, the symbol at point, if any, is the default search parameter. Why is this awesome? Keep reading.

With mu-helm-rg in place, I can build a couple of commands to make my life easier.

(defun mu-helm-project-search (&optional with-types)
  "Search in current project with RG.
With WITH-TYPES, ask for file types to search in."
  (interactive "P")
  (mu-helm-rg (mu--project-root) with-types))

(defun mu-helm-file-search (&optional with-types)
  "Search in `default-directory' with RG.
With WITH-TYPES, ask for file types to search in."
  (interactive "P")
  (mu-helm-rg default-directory with-types))

In case you are wondering, mu–-project-root is a trivial function to figure out the current project’s root directory.

(defun mu--project-root ()
  "Return the project root directory or `helm-current-directory'."
  (require 'helm-ls-git)
  (if-let (dir (helm-ls-git-root-dir))
      dir
    (helm-current-directory)))

I don’t need mu-helm-file-search as often as mu-helm-project-search, which is why I’ve bound the latter to M-?. I use this key binding so much it must be a quick one. And this is why having the symbol at point automatically picked up by mu-helm-rg is so important: it just makes the whole search operation as simple as possible.

Oh wait, the symbol at point is not what you are after? No problem, you can start typing whatever you want and have Helm ripgrepping your files.

Notes

  1. Both packages seem hardly maintained these days.