Fast, Local, Written-by-you Code Search
Tags: coding, development, learning • Categories: Software
I work with many disparate open-source repos in many different programming languages. I often encounter a problem that I know I’ve solved before, but I can’t remember where I solved it.
When I worked at Stripe, we had livegrep. It searched all of the repositories across Stripe in real-time. GitHub has an awesome code search tool now too. I love code search: often, it’s easier to "grep the internet" for obscure usage examples than asking ChatGPT or Google.
However, what was missing was a code search tool I could quickly run on the command line to search through local Git repositories that were mostly written by me. I didn’t want to see a bunch of code from other open-source repos I’ve cloned but not contributed to or repos that I’ve cloned + opened a PR against. I wanted to ask my past self how I solved a particular problem.
Implementing Local Code Search
Turns out, ripgrep is blazingly fast, even over many (50+) repos. Here’s what the two scripts below do:
- Only index repos (a) without a remote or (b) owned by me on GitHub. You’ll probably want to customize this.
- Use fzf for displaying the results. Use bat to highlight the line matching the search query.
You can view them on my dotfiles as well:
index_local_repositories() {
fd --hidden --type=directory "\.git$" ~/Projects -x sh -c '
repo=$(dirname "$0")
origin=$(git -C "$repo" remote get-url origin 2>/dev/null)
if [[ -z "$origin" ]] || [[ "$origin" == *"github.com/iloveitaly/"* ]]; then
if [[ -z "$origin" ]] || ! git -C "$repo" remote get-url upstream &>/dev/null; then
echo "$repo"
fi
fi
' {} \; > ~/.local-git-repos
}
# ripgrep code search
rgc() {
if [[ ! -f ~/.local-git-repos ]]; then
index_local_repositories
fi
if [[ $(find ~/.local-git-repos -mtime +7) ]]; then
# reindex async if it hasn't been done in awhile
index_local_repositories &
fi
rg "$@" $(cat ~/.local-git-repos) | \
# we don't want column, just file and line
cut -d: -f1-2 | \
# now that we only have file + line, we can remove duplicates
uniq | \
# nth will still display `:`, which is annoying
fzf --ansi --delimiter : --with-nth '1' \
--keep-right \
--preview 'height=$(tput lines); start={2}; context=$((height / 2)); bat --color=always --paging=never --highlight-line {2} --line-range $((start > context ? start - context : 1)):$((start + context)) {1}'
}
By itself, this is pretty useful. However, you can improve the experience quite a bit by tweaking the fzf env configuration variables:
# - always show preview on the right
# - ctrl-{u,d} scroll the preview
# - ctrl-o opens the file in vscode (or the default application)
# - ctrl-y copies the file path to the clipboard
# - ctrl-e opens the file in a CLI editor (nano)
# - ctrl-b copies the contents of the file path, at the specified line, with context, to the clipboard
export FZF_DEFAULT_OPTS="\
--bind='ctrl-u:preview-half-page-up' \
--bind='ctrl-d:preview-half-page-down' \
--bind='ctrl-o:execute-silent(~/.open-file-path.sh {})' \
--bind='ctrl-y:execute-silent(pbcopy <<< {})' \
--bind='ctrl-e:execute(~/.open-file-path.sh EDITOR {})' \
--bind='ctrl-b:execute-silent(~/.copy-with-context.sh {})' \
--preview-window 'right:70%' \
--bind='?:toggle-preview' \
--bind='+:toggle-header'
"
Open File Path
The ~/.open-file-path.sh
command above is a wrapper around a zsh function that opens up a file path in VS code at a line specified in the path.
open_in_vscode() {
# git-fuzzy will pass a string that looks like `M bin/run.js` or ` M bin/run.js`
# write a ripgrep command which:
# * ` M bin/run.js` => `bin/run.js`
# * `test/scraper.test.js:25:40` => `test/scraper.test.js:25:40`
# * `test/scraper.test.js` => `test/scraper.test.js`
# * `test/scraper.test.js:25:40: something` => `test/scraper.test.js:25
# assume each of inputs are passed on a single line and are only passed one at a time
local file=$(pcregrep -o2 "(^\s?M\s)?(.*)" <<< "$1")
IFS=':' read file line column _other <<< "$file"
# remove multiple slashes that could have been passed from `file:///` references
file=$(echo "$file" | sed 's|^/{2,}|/|')
code --goto "$file:$line:$column"
}
This is helpful for processing output from rg
but also many other tools that use the file:line:column:other content
format.
So, why ~/.open-file-path.sh
? You can’t use zsh functions in fzf –execute, –preview, etc so a wrapper executable file must be created.
Copy File Content with Context
After searching for some source code, a common operation I want to do is copy the code and use it in my project. Here’s a little shell script I wrote to take a file + line reference and copy the content to the macOS clipboard:
copy_with_context() {
local file=$1
local line=$2
if [[ -z "$file" ]]; then
echo "Usage: copy_with_context <file> <line> | <file-with-line>"
return 1
fi
# if line DNE, then it must be embedded in the first argument
if [[ -z "$line" ]]; then
IFS=':' read file line _other <<< "$file"
fi
# check if $line is a number
if ! [[ $line =~ ^[0-9]+$ ]]; then
echo "Error: Line number must be a number (extracted line: $line)."
return 1
fi
local context=10
local start=$((line - context > 0 ? line - context : 1))
local end=$((line + context))
sed -n "${start},${end}p" "$file" | pbcopy
}