Grep in a git repository using ivy
19 Apr 2015Just got this request a few minutes ago, and now this feature is in
the swiper
repository (available as counsel
from MELPA).
The motivation was to write an ivy equivalent of helm-git-grep. I didn't use this feature before, but the only thing that I needed to get me started was this shell command:
git --no-pager grep --full-name -n --no-color -i -e foobar
The rest of the code (just 20 lines) followed all by itself:
(defun counsel-git-grep-function (string &optional _pred &rest _u)
"Grep in the current git repository for STRING."
(split-string
(shell-command-to-string
(format
"git --no-pager grep --full-name -n --no-color -i -e \"%s\""
string))
"\n"
t))
(defun counsel-git-grep ()
"Grep for a string in the current git repository."
(interactive)
(let ((default-directory (locate-dominating-file
default-directory ".git"))
(val (ivy-read "pattern: " 'counsel-git-grep-function))
lst)
(when val
(setq lst (split-string val ":"))
(find-file (car lst))
(goto-char (point-min))
(forward-line (1- (string-to-number (cadr lst)))))))
Thanks to the push from Stefan Monnier, ivy-read
also supports a
function to be passed instead of a static collection of strings. In
this case, it's counsel-git-grep-function
that basically takes one
argument: the thing that we're looking for.
After this, shell-command-to-string
is my go-to function to quickly
bring some shell output into Elisp. As you can see, it's enough to
pass it a shell command in a string form to get a string response. I
transform the response into a list of line strings with
split-string
, making sure to pass the t
argument to avoid empty
strings.
One final trick that you can learn for your own Elisp programming is
the let
/ default-directory
/ (locate-dominating-file default-directory ".git")
combo.
It's quite useful for dealing with git shell commands.
And that's it, it only remains to open a file and jump to the selected line.
I think that counsel-git-grep
might complement and slightly displace rgrep
or ag
in my setup.
So I've given it a nice binding:
(global-set-key (kbd "C-c j") 'counsel-git-grep)
I hope that I've made a good case of how easy it is to quickly write something in Elisp, especially if it's just a shell command wrapper. So if you're on the fence of whether to learn Elisp or not, do yourself a favor and learn it: it pays off quickly and is a lot of fun.
Side note: I've mentioned \bfun\b
in this blog 182 times, mostly as a variable representing a function (courtesy of counsel-git-grep
).