I’ll admit it, I’m a Vim addict. I started using Vim while programming Ruby on Rails and quickly got hooked on vim-rails’ project navigation shortcuts. Commands like :A (meaning “alternate ”) to jump between a Ruby file and its spec were huge timesavers. There were also other cool commands to jump to a specific type of file.

:Econtroller - Jump to controller
:Emodel - Jump to model
:Emigration - Jump to migration
:Einitializer - Jump to initializer

In the past year, my day job has helped me venture beyond Ruby into Elixir and Angular. It’s fun getting to work in these new languages and frameworks, but I sorely missed my shortcuts to jump around projects. Fortunately, Tim Pope has written vim-projectionist to tackle just this problem.

Quick install

I use vim-plug to manage my dotfiles, but any Vim plugin manager will do. If you happen to be using vim-plug, all you have to do is add

Plug 'tpope/vim-projectionist'

to your .vimrc, then open Vim and run :BundleInstall.

Your first .projections.json

To try out vim-projectionist let’s clone the angular.js repo and open it up in vim. As you look around, notice that for almost every file in the src/ folder, there’s a matching spec file in the test/ directory.

src/ngMock/angular-mocks.js
test/ngMock/angular-mocksSpec.js

Since we know this is the pattern of how files are named in this project, let’s create a .projections.json file in the root of the project that allows use :A to toggle between the src file and its corresponding test.

{
 "src/*.js": {
 "alternate": "test/{}Spec.js"
 },
 "test/*Spec.js": {
 "alternate": "src/{}.js"
 }
}

Now open up src/ngMock/angular-mocks.js and type :A to watch it switch to test/ngMock/angular-mocksSpec.js. Type :A again and you should be switched back to the original file.

Here’s how it works.

1."src/*.js" uses the * to capture the string between src/ and .js. In the src/ngMock/angular-mocks.js example, the * captures ngMock/angular-mocks.

  1. "alternate": "test/{}Spec.js" specifies the file to switch to when using :A. {} is replaced with the value captured in Step 1. If the captured value was ngMock/angular-mocks then the alternate file would be test/ngMock/angular-mocksSpec.js.
  2. To be able to switch back from the test file to the src file, we have to define the reverse association with a "test/*Spec.js" section.

More advanced example

Now that you have made your first .projections.json file, let’s try some more advanced features. For this example, clone the hex-web elixir project and open it up in vim. Take a look around and notice for every file in the web folder, there is usually a matching file in the test folder.

web/controllers/dashboard_controller.ex
test/controllers/dashboard_controller_test.exs

We can use this pattern to build a basic .projections.json file.

{
 "web/*.ex": {
 "alternate": "test/{}_test.exs"
 },
 "test/*_test.exs": {
 "alternate": "web/{}.ex"
 }
}

Now open up web/controllers/api/docs_controller.ex and type :A to watch it switch to test/controllers/api_docs_controller_test.exs. Type :A again and you should be switched back to the original controller file.

Now, let’s make it so we can jump to the controllers using an :Econtroller shortcut like we could in vim-rails. Reopen the .projections.json, and add the following section:

{
 "web/controllers/*\_controller.ex": {
 "type": "controller"
 },
 "web/*.ex": {
 "alternate": "test/{}\_test.exs"
 },
 "test/*\_test.exs": {
 "alternate": "web/{}.ex"
 }
}

Now type the :Econtroller api/ command and hit Tab to see your options. Projectionist will show you all of the possible controllers that match what you’ve typed so far.

:Econtroller api/
api/docs        api/key        api/package        api/retirement
api/index       api/owner      api/release        api/user
:Econtroller api/

You can tab through all of the available options and hit enter to open. Once it’s open you can use :A to switch to the test file and :A again to switch back. You can also use the following variations of the :Econtroller command to control how the file is opened.

:Econtroller - Open the controller in the current window
:Tcontroller - Open the controller in a new tab
:Vcontroller - Open the controller in a vertical split
:Scontroller - Open the controller in a horizontal split

You can add as many of these “type” definitions to your .projections.json as you like. For example, the following section would add the :Emodel, :Tmodel, :Vmodel, and :Smodel commands.

{
 "web/models/*.ex": {
 "type": "model"
 },
 /* Rest of the .projections.json file */
}

Takeways

These are just a few of the features offered by vim-projectionist. Other features include using templates for new files and transforming the path captured by *. Run :help projectionist to see the full documentation.

Provided a project has a standard structure, you can easily build a .projections.json file that allows you to quickly navigate a new repo. I’m definitely indebted to Tim Pope for putting this plugin together, and can’t fathom the amount of time it’s saved me.

Thanks so much for reading this post and please follow my blog or share this article if you found it helpful.