What

Simply jump between any nested git directory in a root directory using simple linux utilities and FZF.

Type :Project and pick the project you want to work on next.

Why

The more we move towards microservices, the more repositories you have to jump between every day.

This tip lets you hop between git projects in the same Vim instance. This workflow is much faster than closing the window, jumping to the other directory and starting a new editor instance. In some cases it may also allow re-using plugins like an LSP to reduce use of system resources.

How

First install the fzf and fzf.vim plugins by junegunn and append the following script to your ~/.vimrc

~/.vimrc
  • vim
  • plugins
1
2
3
4
5
6
function! s:change_to_project(project)
exec 'tabedit '.a:project
exec 'lcd '.a:project
endfunction

command! Project call fzf#run(fzf#wrap({'sink': function('s:change_to_project'), 'source': "find ~/projects -type d -exec test -e '{}/.git' ';' -print -prune"}))

Explanation

this stack overflow question goes over the most efficient way to query for git repos. I used a top answer to construct the following find command.

find git repositories
  • sh
  • output
1
find ~/projects -type d -exec test -e '{}/.git' ';' -print -prune

You can run this command yourself, replacing ~/projects with wherever you keep your repositories. The output can be seen by clicking output in the codepane above.

If the command fails because you don’t have find installed you can add it with brew on MacOS with brew install findutils

Using this output we’re able to construct the command we’d like to execute. tabedit /home/james/projects/guard causes us to create a new tab if it does not exist, and select it. Then we are able to call lcd /home/james/projects/guard which causes vim to change directory for the current pane to the new project.

lcd causes all your commands to be executed in the target project directory and substitutions like % to return the new directory.

We can’t directly call lcd with the path so we use string interpolation to evaluate the command like in exec 'lcd '.a:project

a:project is an argument scoped variable. It is only available in the function due to it being listing in the arguments s:change_to_project(project).

s:function_name causes the function to only be available with the script. This prevents it from polluting the global namespace or being called adhoc later on.

command! Project ... defines a new command that you can call with :Project.

fzf#run(fzf#wrap(...) allows you to send the results of any shell command that returns a list of strings to be selected and then operated on by a VimL function.

You can see that there is a { source: '...command', sink: function('s:change_to_project') } hash. function('s:change_to_project') returns a function reference by looking up the passed variable. When the FZF command completes it will send the single string result to any function that is passed in the sink property of the hash.