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 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!

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")
if l:something == 1
     return 'hi'
     return 'bye'


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.

August 10, 2008, 6:22 p.m. More (548 words) 10 comments Feed
Previous entry: How to NOT approach people
Next entry: A git-svn tutorial



Comment by Thiago , 8 years, 5 months ago :

Hey there, I found a error in your script, when I start vim:

Erro detectado ao processar /home/thiago/.vim/plugin/simple_pairs.vim:
linha 89:
Traceback (most recent call last):
File "<string>", line 2, in <module>
NameError: name 'vim' is not defined
Aperte o ENTER ou digite um comando para continuar

But I fixed, I only added the line "import vim" in 7th line, before declareting the pair.


Comment by Orestis Markou , 8 years, 5 months ago :

Whoops! It worked for me because I had other plugins that used python as well.

I've uploaded a new version (that's pretty max your fix), could you download and give it a go?

Thanks very much!


Comment by Thiago , 8 years, 5 months ago :

It's OK


Comment by jonathan hartley , 8 years, 5 months ago :

Hey. I tend to have one pure python file in which I define all my python functions, such as:

open_quickfix = False

def toggleQuickfix():
global open_quickfix
open_quickfix = not open_quickfix
if open_quickfix:
cmd = "copen"
cmd = "cclose"

and then in my .vimrc file, I run that file to define the python functions and call the functions like so:

pyfile ~/.vim/functions.py
map <silent> ` :python toggleQuickfix()<cr>

This command lets me toggle the visibility of the quickfix window using the [ ` ] key, a la the Quake console.


Comment by Panos Laganakos , 8 years, 5 months ago :

Nice one Orestis.

Works similarly to http://www.vim.org/scripts/script.php...
but I'll support your version, being a python zealot :)

I do have a suggestion, but not sure how I'd like it work. If you have any revelations on this, let me know.

most of the time when I want {, (, [, to be closed and put on a separate line, ie:


With simple_pairs, I type { <Enter> <Esc> <Ctrl_O>

Do you think you can add a hook to automate that? A Ctr_Something or whatever.


Comment by Orestis Markou , 8 years, 5 months ago :

It's not hard to add, but I really don't want to put unnecessary functionality in there...

I'm sure you can fix it yourself though! Eg. imap <C-CR> <cr><esc><s-o>


Comment by Marlun , 8 years, 5 months ago :


I've tried the script but the moving over part doesn't work with ' and ", if I for example in html write <div id=" I get the second " but when I hit " again I get another pair instead of it moving over the closing one.

any ideas? :)


Comment by Orestis Markou , 8 years, 5 months ago :

Quotes are tricky in general. This behaviour is exhibited because of the nesting logic, ie. if you want to type (()) you only have to hit ( twice. Unfortunately with quotes you don't know if the second hit is meant to move over or to insert a second pair.

I'm planning on uploading an updated version that has customizable behaviour, so you could tailor it to specific needs. I'll post a comment here, so you can subscribe to the comments feed to get a notification.

A more advanced solution is to change the behaviour by filetype. I'm sure the only place that nested quotes are needed are in python triple-quoted strings, so I may special case that bit.

Thanks for the feedback!


Comment by Marlun , 8 years, 5 months ago :

Yeah that would be great :) I tried to look at the code but havn't figured out how to change it :) I'll wait for your update.


Comment by Alexander Artemenko , 8 years, 4 months ago :

Hi. How can I debug my python plugin for vim? Can I use pdb?

This post is older than 30 days and comments have been turned off.