Why if_pyth
We might as well admit that Vimscript has its share of limitations and quirky behaviours. Luckily, Vim (and NeoVim) provides binding to Python, Perl, Ruby, and other programming languages. Using them also allows you to use packages from these languages’ package repositories such as PyPI and to write or use extensions in C, C++ or similar.
This article aims to be a cookbook for using if_pyth which is Vim’s Python interface. I do not wish to particularly endorse Python out of all possible languages, it’s just that I am now trying to learn it better and so am trying to use it as much as possible.
Processing/filtering each line in a range of lines
For this example, we will replace all occurrences of the raw string (token)
with the replacement string MyReplacement
in each line in a range:
py << EOF
import vim
import string
def my_replace_string(s, needle, repl):
return string.replace(s, needle, repl)
EOF
command! -range ReplaceToken :<line1>,<line2>pydo return my_replace_string(line, "(token)", "MyReplacement");
Save this as if_pyth-linewise.vim
and then :source
it and you can do
:%ReplaceToken
or similar, like this:
Processing an entire subrange / selection of lines at once
For this example, we will replace all occurrences of the raw string (index)
with consecutive indices in a range:
py << EOF
import vim
import re
def my_replace_with_numbers(myrange):
idx = [0]
def _replace(m):
idx[0] += 1
return str(idx[0])
new_string = re.sub("\\(index\\)", _replace, '\n'.join(myrange[:]))
myrange[:] = new_string.split('\n')
EOF
command! -range IndexBuf :<line1>,<line2>py my_replace_with_numbers(vim.current.range)
Some notes:
Note the use of
join
andsplit
to convert from a list/array of lines to a single, multi-line, string and back.Wrapping the index inside a list is needed so it can be changed by the inner subroutine.
vim.current.range
contains the current range.
Accessing Vimscript variables and registers
The only reliable way I found to access Vimscript variables and registers is by using
vim.eval('myvar')
or vim.eval('@a')
(for accessing the a
register). E.g.:
py << EOF
import vim
import string
def var_replace(s):
needle = vim.eval('needle')
replacement = vim.eval('@a')
return string.replace(s, needle, replacement)
EOF
command! -range VarBasedReplace :<line1>,<line2>pydo return var_replace(line);
Writing Vimscript functions in python.
In order to this reliably, I was able to pass values using vim global variables,
and use pyeval()
and vim.eval()
. E.g:
py << EOF
import vim
import string
def vim_replace():
s = vim.eval('g:for_py_string')
needle = vim.eval('g:for_py_needle')
repl = vim.eval('g:for_py_repl')
ret = string.replace(s, needle, repl)
return ret
EOF
function! MyReplace(str, needle, repl)
let g:for_py_string = a:str
let g:for_py_needle = a:needle
let g:for_py_repl = a:repl
return pyeval('vim_replace()')
endfunction
Now we are able to do:
:echo MyReplace("rescue Brian", "r", "w")