For my first post, I want to share a bit of my workflow. I'm a vim user, and I had this problem at work: there's a huge codebase to navigate, and a ton of file or folder locations to remember. Not only that, I want to navigate to a specific line where an error was caught or some debug logging took place. There's many tools an individual can use and I'm gonna talk about fzf, rg, and bat.
If you're asking what FZF is, practically it's a fuzzy file finder that let's you do live greps from your terminal and provides a live preview of the file, as shown in the gif above. And what is rg? rg is a recursive grep that searches for a specified regex pattern and, out of the box, respects .gitignore rules. So you may be asking how rg fits into the big picture? The answer is we'll pipe the output from rg to fzf so we can perform a fuzzy search on the output generated from rg. Lastly, bat will help provide us a cool color theme for our preview window as show in the gif.
Back to the problem, I wanted to quickly open up neovim/vim with the file I sought for or at the line I was looking for. I have some snippets for that.
First, let's provide the snippet for finding files.
function displayFZFFiles {
echo $(fzf --preview 'bat --theme=gruvbox-dark --color=always --style=header,grid --line-range :400 {}')
}
function nvimGoToFiles {
nvimExists=$(which nvim)
if [ -z "$nvimExists" ]; then
return;
fi;
selection=$(displayFZFFiles);
if [ -z "$selection" ]; then
return;
else
nvim $selection;
fi;
}
I'll provide a high-level view of what's going on in the snippet above. The meat and potatoes is in the function displayFZFFiles
. It does as it says, displays all of the files in our current working directory via fzf. When we type into the search bar hit enter to select our desired file, fzf will generate the file's relative path concatenated with it's name, ex: ./myfolder/myfile.txt
. We then execute nvim $selection
to open up the file in nvim.
Yes, the magic is real, and it's gonna get better. Next is navigating to a specific line.
Here's the snippet for navigating to the line via rg
:
function displayRgPipedFzf {
echo $(rg . -n --glob "!.git/" --glob "!vendor/" --glob "!node_modules/" | fzf);
}
function nvimGoToLine {
nvimExists=$(which nvim)
if [ -z "$nvimExists" ]; then
return;
fi
selection=$(displayRgPipedFzf)
if [ -z "$selection" ]; then
return;
else
filename=$(echo $selection | awk -F ':' '{print $1}')
line=$(echo $selection | awk -F ':' '{print $2}')
nvim $(printf "+%s %s" $line $filename) +"normal zz";
fi
}
Whoa, that's a lot to unpack right now. Again, I'll provide a high level view. In displayRgPipedFzf
, we want to get the output of rg
and pipe it fzf
so we can have it displayed and perform a live grep on the output. You'll notice the --glob
flags and the purpose they serve is to filter out some output. We don't want to perform live greps on node_modules
or any packages/libraries we have installed for our project (vendor
is specific to golang and though we have go modules, some teams at your company may still use them). Similar for selecting our files, we'll record our selection when we hit enter on our keyboard. The thing is, the recorded selection is not in a format that is nvim-friendly, ex:
main.js:const {Writable} = require('stream')
ch2/os-basics.js:1:const os = require('os')
As we can see, we need to process this output and parse for some data. awk
to the rescue. awk
will help us split our output by a delimeter. That delimeter will be the :
character because when we split by :
we can get the relative path and file name, along with the line number of our file we selected. Once we get filename
and line
, we can then use nvim to open the file and jump to the specified line. The usual way to do that with nvim/vim is:
nvim $lineNumber $filename
Hence, why we have:
nvim $(printf "+%s %s" $line $filename)
You may be asking what's the +"normal zz"
all about. When we have neovim open and we're in normal mode and hit zz
, it will redraw our cursor and cursorline in the middle of the buffer. This is more of a personal preference because I wanted to keep the cursorline at eye-level when I perform this script. To each their own, you can include or exclude this part of the script based upon your personal preference.
We've made it this far, and I didn't forget about bat
. Again, it will provide us a color scheme of our live grep preview of our file. Recalling displayFZFFiles
:
function displayFZFFiles {
echo $(fzf --preview 'bat --theme=gruvbox-dark --color=always --style=header,grid --line-range :400 {}')
}
Here we're telling fzf to use a theme set by bat, and bat has further control on what that preview window will look like. The theme bat chooses is gruvbox-dark
, and it also adjusts the style and sets a max limit of lines display at a height of 400. A neat thing is that the preview is scrollable, so we can hover over it with our mouse and scroll up or down.
One last thing is FZF_DEFAULT_OPTS
and FZF_DEFAULT_COMMAND
. We need this declared in our bashrc/zshrc because it's a global option that adds some additional configuration or behavior for fzf.
export FZF_DEFAULT_OPTS='--prompt="π " --height 80% --layout=reverse --border'
export FZF_DEFAULT_COMMAND='rg --files --no-ignore --hidden --follow --glob "!.git/" --glob "!node_modules/" --glob "!vendor/" --glob "!undo/" --glob "!plugged/"'
To further explain, the default opts will allow us to have that neat prompt shown in the gif. And, touching upon the default command, by default, fzf uses find
to perform a search of files. I chose to instead use rg
as our file finder. This command gets overridden when we piped rg
to fzf for displayRgFzf
.
So, this pretty much concludes what I wanted to share. To sum it up, nvim+fzf+rg+bat is an option to speed up your workflow, quickly navigate across a code base by fuzzy finding files or performing live greps and jump to a desired line in a file. I hope this helps. My whole script is found here, which also includes some aliases for my functions. Feel free to rename the aliases to something that works for you. I hope this speeds up your productivity, as it did for me :)
My script on github:
https://github.com/manofthelionarmy/vimFzf-Rg
Please checkout fzf, rg, and bat at their repos for installation instructions.
FZF:
https://github.com/junegunn/fzf
Rg:
https://github.com/BurntSushi/ripgrep
Bat:
https://github.com/sharkdp/bat
Part of my script, I also use fd to do live greps so I can navigate files. Again, here's the repo and follow installation instructions :)
Top comments (2)
Hey cool. I did something similar a while ago, but instead of doing complex things to get the line number, I pass
+/${search_term}
to vim and it goes to the first match.Awesome!! Thank you ππ