Creating My First Vim Plugin

related.vim

Why would anyone ever...?

While I'm at work or hacking away at home, I spend most of my development time in Vim.

I tend to edit a lot of PHP classes and the associated test files at the same time...but finding the files and manually opening them, sucks.

It takes some mental energy to find the file and open it, which can interrupt your train of thought. I do use the excellent ctrlp.vim plugin, but the codebase I work in contains way too many files, making the autocomplete kind of awkward to use at times.

I've been thinking of mapping some command to open the related file but have heard bad things about it.

Until I came across this: Optimize Your TDD Workflow by Writing Vim Plugins

Plugins can be easy

The article made a script to do exactly what I wanted...except it was based on a Rails convention. Nonetheless, the amount of code there is quite small and being a developer, I figured I could make sense of it.

For my version, I ended up replacing the regex with something more complicated for PHP PSR-0 style naming conventions.

The rails format looked like this:

app/<type>/<model>.rb
spec/<type>/<model>_spec.rb

The PSR-0 / PHPUnit standard looks like this:

src/<vendor>/<namespace>/<class>.php
tests/<vendor>/Tests/<namespace>/<class>Test.php

The PHP one is a bit more complicated.

Here is the crazy regex I came up with:

" Converting from src to tests
substitute(a:file, 'src/\([^/]\+\)/\(\([^/]\+/\)\+\)\?\(.*\).php$', 'tests/\1/Tests/\2\4Test.php', '')
" Converting from tests to src
substitute(a:file, 'tests/\([^/]\+\)/Tests/\(\([^/]\+/\)\+\)\?\(.*\)Test.php$', 'src/\1/\2\4.php', '')

Hmmm...not too pretty to look at but it works.

Testing

Looking at some repos for plugins, I'm not sure how people test them.

The regex was pretty complicated and it wasn't productive to constantly change it and test it on a new file.

I decided to create a RunTests function and an Equals function, as a quick solution.

RunTests

function! s:RunTests()
  let s:test_number = 0
  let s:passes = 0

  echo "Test s:GetRelated"
  " Not in the proper format
  call s:Equals('', s:GetRelated('blah.php'))
  " Proper format, source file, no namespace
  call s:Equals('tests/Vendor/Tests/ClassTest.php', s:GetRelated('src/Vendor/Class.php'))
  "...more tests

  echo printf("\nFinished test suite - %d of %d tests passed (%d%%)", s:passes, s:test_number, s:passes*100/s:test_number)
endfunction

Equals

function! s:Equals(expect, actual)
  let s:test_number = s:test_number + 1

  if a:expect ==# a:actual
    echo printf("%10s %s", "Test " . s:test_number . ":", "PASS")
    let s:passes = s:passes + 1
  else
    echo printf("%10s %s", "Test " . s:test_number . ":", "FAIL")
    echo printf("%20s %s", "Expected:", a:expect)
    echo printf("%20s %s", "Actual:", a:actual)
  endif
endfunction

Output

Test s:GetRelated
   Test 1: PASS
   Test 2: PASS
   Test 3: PASS
   Test 4: PASS
   Test 5: PASS
   Test 6: PASS
   Test 7: FAIL
           Expected: apath/to/src/Vendor/Namespace/A/B/Class.php
             Actual: path/to/src/Vendor/Namespace/A/B/Class.php
   Test 8: PASS
   Test 9: PASS

Finished test suite - 8 of 9 tests passed (88%)

Running the Tests

I exposed a function called RelatedTests to call the internal RunTests function.

command! RelatedTests :call <SID>RunTests()

To quickly run the tests, I bound the ; key to perform the following tasks:

  1. save the file
  2. source the script
  3. run the tests
:map ; :w<cr>:so %<cr>:RelatedTests<cr>

I would make a change, then press ; to perform all those actions.

The result

A :RelatedFile vim command that finds the PHP source or test class and opens it in a new vertical split.

To use it, I added the following to my .vimrc:

" Assuming the use of https://github.com/gmarik/vundle
Bundle 'donaldducky/related.vim'

" Find related file
map <Leader>rf :RelatedFile<CR>

Now, I can open any class or test file and press ,rf to instantly go to the related file.

After a couple days of using it, I already see the benefits of this plugin and have a few ideas to make it more useful.

It's on github at: https://github.com/donaldducky/related.vim

  Vim PHP