Skip to content

Fast, Local, Written-by-you Code Search

Tags: coding, development, learning • Categories: Software

Table of Contents

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
}

Keep in Touch

Subscribe to my email list to keep in touch. I’ll send you new blog posts and other thoughts.