mkaz.blog

Neovim plugin in Python

The Vimwiki plugin is quite useful, but I only use a small subset of its features, primarily the daily notes and checking off of todo lists. See my Vimwiki page in my Working with Vim guide.

However, the daily notes don't organize how I want them to, I want to store them in YYYY/MM/note.md. The plugin has limited customization so I figured I could write my own plugin. I call it Neojot.

Initially I started writing the plugin in Lua, which I don't know at all. Plus looks like no batteries are included with Lua, the standard library is thin. Anorexic.

So why not Python.

The documentation for writing a Neovim plugin in Python is a bit limited, but once the plugin is stubbed out, relatively straight forward to follow the rest of the Neovim API documentation.

So here is what I figured out on how to stub out a plugin, hopefully it gets you further faster.

Directory Structure

I used the remote plugin interface and pynvim client using the following directory structure.

In the lua directory, I created a plugin file neojot.lua that creates my keybindings, and creates a hook for Python to grab the setup config.

In the rplugin/python3 directory, I created a Python file neojot.py that creates the individual functions that does all the voodoo.

Example Plugin

Here is a minimal example plugin with this structure. The example maps <Leader>i to a function in the Python code that echoes a configuration value to the command line in Vim.

In example_plugin/lua/example_plugin.lua

local config = {}
 
local function setup(cfg)
    config = cfg
    -- add keymaps here
    vim.keymap.set('n', '<Leader>i', ':call ExPlugEcho()<CR>', { silent = true })
end
 
-- Used in Python to get config
local function getConfig()
    return config
end
 
return { setup=setup, getConfig=getConfig }

In example_plugin/rplugin/python3/example_plugin.py

import pynvim
 
@pynvim.plugin
class ExPlugin(object):
 
    def __init__(self, nvim):
        self.nvim = nvim
        self.cfg = nvim.exec_lua('return require("example_plugin").getConfig()')
 
    @pynvim.function("ExPlugEcho", sync=True)
    def echo(self, args):
        self.nvim.command(f"echomsg 'From config: {self.cfg['path']}'")

That is all that is needed for plugin code.

Install plugin using vim-plug to refer to directory ~/src/example_plugin location.

Config plugin using:

require("example_plugin").setup({
    path = "/home/mkaz/Documents",
})

Relaunch Neovim and run :UpdateRemotePlugin

Open a buffer and type <Leader>i to see message.

Development Tips

While developing, when updating the Python files Neovim needs to be quit and restarted to have the changes take effect.

Run :UpdateRemotePlugin when making changes to Lua file that loads keymaps.

See my Neojot repo for the plugin I built. The plugin is rather basic but shows a few features and commands.

Read the Neovim API documentation to figure out what can be done.

The Pynvim documentation is barely useful.