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, '

'.join(myrange[:])) myrange[:] = new_string.split('

') EOF command! -range IndexBuf :<line1>,<line2>py my_replace_with_numbers(vim.current.range)

Some notes:

Note the use of join and split 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: