Vim autocomplete, Django and virtualenv

One of the features I quite missed when I first moved from Komodo to Textmate as my main editor was autocompletion. Although I didn't use it very much, it was occasionally useful to be reminded of the methods available on a class, without having to look up the documentation or open the source module.

Now I've moved to vim, which has its own version of autocomplete: omnicompletion. This is activated by pressing Ctrl-X Ctrl-O in insert mode after typing the name of a class or instance, and displays a nice drop-down menu with all the available members of that object. The Python plugins which come with vim allow this function to not only complete items from the standard library, but also to parse your own files which are open in the editor, reading the modules you import and adding those elements to the completion dictionaries.

However, this wasn't working for me in my Django projects. After a lot of investigation, I found that this was down to three issues.

The first two of these related to the fact that I was working within a virtualenv. Despite the fact that I was starting MacVim after activating the virtual environment, the specific site-packages directory was not being added to the path and it was defaulting to the system-wide one, which on my system doesn't contain any of the packages I was using. The solution to this is to use the activate_this.py script which is provided with virtualenv to activate it within another process - it's intended for use with mod_wsgi, but works just as well here. You can run it from the vim command line, using python to tell vim that the following commands are in Python. (Note that the Python interpreter is actually persistent, so you can import modules or define variables in one command and they are still available in subsequent ones.)

:python activate_this = '/path/to/virtualenv/bin/activate_this.py'
:python execfile(activate_this, dict(__file__=activate_this))

This sets up the paths properly, but it was still not working. After a lot of investigation, I finally realised that this was because vim uses its own built-in Python interpreter, which is version 2.5, while my Snow Leopard machine was using 2.6. The confusion arose because doing this:

:python import sys; print sys.executable

does return the path to my virtualenv's version of Python. To make it even worse, this:

:python print sys.version

returned 2.5.2, when the executable printed in the previous command was actually Python 2.6.1! I can't explain that bit of weirdness, and would be interested to hear in the comments if anyone else can, but the fact that vim was clearly using a 2.5 Python did at least explain why it wasn't picking up the local packages which were installed in the virtualenv's lib/python2.6/site-packages directory.

The version of Python included is decided at compile time, and the pre-built versions of MacVim are actually compiled against Python 2.5, so to make it work with the 2.6 directories I had to build my own binaries. Luckily this is very easy.

The third and final piece of the puzzle was Django-specific. Anything that uses a Django model, or indeed imports anything that references django.db, needs a settings module. Normally when running from manage.py this is set automatically, but obviously that doesn't work within vim. So you just need to set the DJANGO_SETTINGS_MODULE environment variable, which again can be done from the vim command line:

:let $DJANGO_SETTINGS_MODULE='mysite.settings'

With all that in place, plus some further Python path manipulation to ensure it found all my project's code, I was now able to complete code within Django projects.

There's some work to be done to automate this. At the moment, I've put all the above commands into a .vimrc file at the base of the virtual env, and added some code to my main ~/.vimrc to load it based on the value of the VIRTUAL_ENV environment variable (which is set by bin/activate):

if filereadable($VIRTUAL_ENV . '/.vimrc')
    source $VIRTUAL_ENV/.vimrc
endif

This probably isn't ideal, as it involves remembering to create that file with all its specific hard-coded paths each time I set up a new virtualenv. On the other hand, trying to do something more automatic will be difficult, as my settings files are not always in a predictable location - eg for work they are often under projectname.configs.development.settings - so maybe this is the best I can do.

Comments !

social