Quick notes search with Fish Shell, fzf, and ripgrep
Switching from ZSH to Fish Shell has had me migrating/rewriting shell functions that I use frequently.
Near the top of the list of my most essential shell functions: interactive searching of my notes.
I’ve been using a shell function like this for years, but the switch to fish gave me a good excuse to make a few tweaks.
The core idea is simple: search through my notes with ripgrep, filter interactively with fzf, and jump to the matching line in my editor.
What makes it nice is the bat preview window showing context around each match as I navigate through results.
The fish version is cleaner than my old ZSH one, which is pretty much how I’d sum-up my entire experience switching to fish: everything is just a little cleaner and more joyful.
Fish’s argparse makes handling options actually pleasant (if a teeny-bit verbose…), so I added flags for --direction, file --extension, and --language for syntax highlighting in bat.
Probably the biggest change is that I’ve made the function default to not opening the file, instead it assembles the command to open the file in the command buffer so I can see it, but won’t trigger until I hit Enter.
This frees me to use the function to pick out a file path, but then tweak the prepared command (say, swap $EDITOR for rm if I actually want to delete the file) before running it if needed.
If I do just want to jump in though, I included an --execute flag that opens the editor immediately instead of just staging the command in my buffer, and I have an alias for this.
Speaking of alias’s, I’ve set up a few for searching the different file-types and directories I’m always flitting through: notes, journal, and manifests. They’re just thin wrappers, but they’re what I’ll be typing dozens of times a day, so brevity is welcome. The full function with its options is there when I need it, but usually I just want to search my notes and these make that seamless.
function sn --wraps search-notes --description "Search notes (short alias)"
search-notes --directory ~/notes --extension md --language toml $argv
end
function sm --wraps search-notes --description "Search manifests (short alias)"
search-notes --directory ~/records/manifests --extension toml --language toml $argv
end
function sj --wraps search-notes --description "Search manifests (short alias)"
search-notes --directory ~/journal --extension toml --language toml $argv
end
Here’s the actual script:
function search-notes --description "Search notes with ripgrep and fzf, open in editor"
# Default values
set -l dir ~/notes
set -l ext md
set -l lang toml
set -l execute 0
# Parse options
argparse 'd/directory=' 'e/extension=' 'l/language=' x/execute -- $argv
or return
if set -q _flag_directory
set dir $_flag_directory
end
if set -q _flag_extension
set ext $_flag_extension
end
if set -q _flag_language
set lang $_flag_language
end
if set -q _flag_execute
set execute 1
end
# Perform the search
set -l selected (rg --color=always --line-number --no-heading --smart-case --glob "*.$ext" '' $dir \
| fzf --ansi \
--exact \
--delimiter ':' \
--preview "bat --color=always --language=$lang --highlight-line {2} {1}" \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3')
if test -n "$selected"
set -l file (echo $selected | cut -d: -f1)
set -l line (echo $selected | cut -d: -f2)
if test $execute -eq 1
# Directly execute the editor command
eval "e +$line $file"
else
# Insert into commandline buffer (default behavior)
commandline -r "e +$line $file"
end
end
end