DEV Community

Cover image for Nvim + FZF from Terminal
Armando Leon
Armando Leon

Posted on

Nvim + FZF from Terminal

A gif displaying opening files in nvim via fuzzy file finding

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;
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Hence, why we have:

nvim $(printf "+%s %s" $line $filename)
Enter fullscreen mode Exit fullscreen mode

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 {}')
}
Enter fullscreen mode Exit fullscreen mode

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/"'
Enter fullscreen mode Exit fullscreen mode

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 :)

Fd:
https://github.com/sharkdp/fd

Oldest comments (2)

Collapse
 
moopet profile image
Ben Sinclair

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.

Collapse
 
manofthelionarmy profile image
Armando Leon

Awesome!! Thank you 😌🙏