Vim: Working Directory Autocompletion and File Finding from Project Root

04/11/20191 Min Read — In DevTools, Vim

When working with Vim, I open the editor from the project root. This is the parent directory which also contains the .git directory.
For file navigation, I heavily rely on fzf.vim. Opening Vim from the root directory means that I have access to all project files via FZF. I mapped the command to <ctrl> + p (VS Code) and can find the file I would like to edit.

For autocompletion, I use different plugins. One of them is VimCompletesMe, a minimalist tab-completion plugin for Vim.

Problem: I would like to import files with tab completion relative to the current file

Let's say we have a standard Create-React-App project structure:

MyApp/
-- client/
      --src/
        --components/
                --Header.js
                --App.js
-- README.md
-- package.json

I open Vim from the root folder: MyApp.
With FZF I can now open different files from this directory.

But when I'm in App.js and I want to import something, tab autocompletion starts from MyApp instead of App.js (which is already nested deeply in the project structure).

// client/src/components/App.js
import React from 'react'

import Header from '.   ' /* <- here autocompletion goes wrong and
                                starts suggesting files and folders
                                relative to the parent directory */

This can get confusing.

Solution: Set Working Directory to Current File And Make FZF Smarter

One solution is to set the working directory to the current file.
I used this snippet in my .vimrc:

autocmd BufEnter * silent! lcd %:p:h

This autocmd changes the window-local current directory to be the same as the directory of the current file. It fails silently to prevent error messages when you edit files via ftp or new files.

This "breaks" FZF file searching. FZF will now search for files in the current directory instead of the parent/root folder.

What if we could tell FZF to search from the project folder instead?

Here's a simple command from FZF's author:

function! s:find_git_root()
  return system('git rev-parse --show-toplevel 2> /dev/null')[:-2]
endfunction

command! ProjectFiles execute 'Files' s:find_git_root()

This assumes that you have a .git folder in your parent directory.

Alternatively, you can use a plugin like ProjectRoot. It tries to guess the main folder and offers simple mappings and commands.

Now, remap your current keybinding for FZF to use the function above. For example:

nnoremap <c-p> :<c-u>ProjectFiles<cr>

Addendum:

If you would like to use ag or rg for finding text within files (instead of grep), you can also adjust the command to search from the project root. I couldn't get it working with the function above. But it works with ProjectRoot.

call minpac#add('dbakker/vim-projectroot') " install the plugin
nnoremap <Leader>rg :<C-u>ProjectRootExe Rg<CR> " key mapping for Rg

Further Reading