Creating My First Vim Plugin
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:
The PSR-0 / PHPUnit standard looks like this:
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.
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.
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
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
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
command! RelatedTests :call <SID>RunTests()
To quickly run the tests, I bound the
; key to perform the following tasks:
- save the file
- source the script
- run the tests
:map ; :w<cr>:so %<cr>:RelatedTests<cr>
I would make a change, then press
; to perform all those actions.
: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
" 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