Lessons on writing vim plugins

Posted on Mon 12 September 2011 in Technology • 2 min read

<p>Last week I was hacking on a vim plugin to post automatically to <a href="https://github.com/codito/vim-blogit/tree/refactor">posterous</a>. Vim-blogit is basically a python script at its core and is bound to the vim interface using vim&rsquo;s python interop support.</p>

My initial workflow was like: Fire vim up, test something and hit a non-actionable error (since I won’t have the entire stack trace, nor could I use pdb.set_trace/attach debugger :(). There is no way to debug python code running as part of vim (for good or bad), which means I cannot simply attach to vim and set breakpoints on the python script.Ah, most of my time went in setting up the environment, trial and error to figure root cause.

Thus began the quest to figure out alternatives.

Isolate the problem

Our problem is the interop between vim and python. We are tightly coupling the interface/view vim with the logic.

It’s obvious. Keep your core logic separate from vim module as much as possible, and unit test it out. Write tests for each function that is an entrypoint from vim To your core logic. And then run your script outside vim.

But there is no “import vim” outside vim…

So why not mock it up with a mock testing piece. Take a look at the sample mock vim implementations available.

Following provides a failover for your script outside vim, and gets the mock vim:

try: import vim except ImportError: # Used outside of vim (for testing) import doctest, minimock from minimock import Mock, mock from tests.mock_vim import vim

On splitting the code

Python provides awesome support for packages and modules. Use it. Yes, it is possible to put all code/unit tests in your .vim file with a python >>>here string. But it will become a pain point to browse through and maintain it.

Following code snippet provides a way to implement your plugin as a package:

python << EOF import os, vim # Get the blogit in python module path for p in vim.eval(‘&runtimepath’).split(‘,’): sys.path.append(os.path.join(p, ‘plugin’)) sys.path.append(os.path.join(os.getcwd(), ‘plugin’)) from blogit import core blogit = core.BlogIt() EOF

Include that in your plugin .vim script. Simple stuff, we just add our python package to the package search path.

Credits: much of this understanding came reading through source codes of well written plugins. Open source, for the win! BTW Steve has written a great article on Writing Vim Plugins, I highly recommend it.