Scripting Vim with Python

I've created and released a small Vim plugin that will try to mimic TextMate's behaviour to insert the closing pair of quotes, brackets, parentheses, braces etc. Download simple pairs! (I've also created one for Emacs)

I used Vim's built-in python scripting support, and seeing that it wasn't documented very well, I present some useful patterns here.

Python inside Vim script

The first thing to know is how to define a block as Python code inside Vim script. You can do it like that:

python << endpython

# your python code here

endpython

endpython can be whatever you want, as long as it is in the beginning of the line by itself. Other people use EOF, but I find it confusing.

Interfacing with Vim

Inside Python code, you can import vim and you have access to a lot of vim objects, the most useful IME being:

  • vim.command - execute a vim command
  • vim.eval - evaluate a vim expression and return the result
  • vim.current.window.cursor - get the cursor position as (row, col) (row is 1-based, col is 0-based)
  • vim.current.buffer - a list of the lines in the current buffer (0-based, unfortunately)

So to get the current line, you can use:

import vim
(row, col) = vim.current.window.cursor
line = vim.current.buffer[row-1] # 0 vs 1 based
prevChar, nextChar = line[col-1], line[col] # will IndexError if at start or end of line

Of course, you can also update the cursor position and change the line contents:

vim.current.window.cursor = (1, 0) # move to top left
vim.current.buffer[0] = "hello, world" # change first line
vim.current.buffer.append("last line!")
del vim.current.buffer[3] # also works with slices

You have to always check the bounds, or you will get IndexErrors, which are very annoying.

Python inside a Vim function

This is where things become interesting. In the simple pairs, I wanted to use the <C-R>=expression functionality, that will eval the expression and insert the results into the buffer, from insert mode. Turns out that python expr is not a valid Vim expression, so I had to wrap my Python functions inside Vim functions. This is the way to do it:

function! MyCoolFunction(anArg)
python << endpython
import vim
anArg = vim.eval("a:anArg")
# do important stuff
vim.command("return 1") # return from the Vim function!
endpython
endfunction

You use vim.eval to get the values of the vim function arguments, then you use vim.command to return from the vim function. Doing a plain Python return will not work.

You can of course set the values of variables as well, and use them later in vim script:

python << endpython
vim.command("let l:something = 1")
endpython
if l:something == 1
     return 'hi'
else
     return 'bye'
endif

Miscellaneous

You can freely mix and match vim script and python code inside a vim file, as long as you use the python << endpython markers. Each Python block is executed in the same context, so you can define your helper functions and do whatever initialization you want in a big python block in the beginning, and then just call functions and access global state as needed.

One drawback is that the tracebacks you get when you have an error don't give you line numbers (the filename is <string>), but you can still understand what went wrong from the messages themselves.