Talk: Writing NeoVim Plugins using Python Plugin API

This talk will be held at Vimfest 2017 in Berlin. I’ll show how to write plugins for Nvim using the new Python Plugin API. The API is async and wraps the new msgpack. The API is called “Remote Plugin API”.

As an example I will use the plugin neotags, which is my first Nvim plugin using the new API. It’s completely written in Python3 and covered with tests. Of course there are already some tag update plugins out there, but I wanted to try out the new API. Also most of the plugins did not work with my setup, or contain to much code and no tests.

Note

This is an early version of the talk. Some parts might change until Vimfest 2017.

What’s the difference?

All you have to do, except setting up the environment, is to write a single Python file and place it rplugin/python3/file.py. Afterwards run :UpdateRemotePlugins from within Nvim once, to generate the necessary Vimscript to make your Plugin available.

Example of generated ~/.local/share/nvim/rplugin.vim:

" python3 plugins
call remote#host#RegisterPlugin('python3', '/Users/siepmann/.dotfiles/.vim/bundle/neotags/rplugin/python3/neotags.py', [
    \ {'sync': v:false, 'name': 'BufWritePost', 'type': 'autocmd', 'opts': {'pattern': '*', 'eval': 'expand("<afile>:p")'}},
    \ ])

There is no need to write any Vimscript, you can fully stick to your preferred language. Beside Python also Ruby is supported via Ruby Plugin API.

For full Nvim documentation on the remote plugin API, take a look at the official docs at https://neovim.io/doc/user/remote_plugin.html.

Most communication is async by default.

Setting up the environment

  1. Install Neovim

  2. Install Python3

  3. Install Python3 Neovim module:

    pip3 install --upgrade neovim
    

First example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import neovim

@neovim.plugin
class NeotagsPlugin(object):

    def __init__(self, nvim):
        self.nvim = nvim

    @neovim.autocmd('BufWritePost', pattern='*', eval='expand("<afile>:p")')
    def update_tags_for_file(self, filename):
        self.nvim.out_write('neotags > ' + message + "\n")

First of all you have to import the neovim module to get access to the API. This will also import decorators for classes and methods.

By decorating the class as a plugin, it will become a plugin.

By decorating a method as autocmd it will be registered as an auto command.

In our above example all writes to files will trigger our python method and display the file name as a message inside Nvim.

To have access to the current Nvim instance, we keep a relation to the instance when we receive it in the constructor.

How to test your plugin

Of course you can add Unittests to test the code. But vim and nvim can be started with the -c option. This will execute the provided command, e.g. calling you plugin, so for above example:

rm tags; nvim someCodeFile -c ':w'

This will first delete a generated tags file and open a file with code inside neovim and save it, triggering our auto command.

Executing Nvim functions

Just use the API:

self.nvim.funcs.execute('pwd')

The neovim instance has a instance of Funcs which will pass the method name as function call to nvim. This way all nvim functions are available.

Getting options from Nvim

Beside functions the nvim instance provides vars as an array containing all existing variables and options.

self.nvim.vars.['neotags_logging']

This will return the let g:neotags_logging value.

The API is documented through code and https://neovim.io/doc/user/api.html#nvim_get_var().

Defining functions and commands

You can define functions and commands the same way as autocommands. Examples are provided in the official docs at https://neovim.io/doc/user/remote_plugin.html#remote-plugin-example.

Further reading

Thanks to the implementation of Nvim it’s possible to create plugins in every single language. Just one has to provide a wrapper around the msgpack to allow communication with Nvim.