How I wrote my first Neovim plugin

evilcel3ri

chris

Posted on December 15, 2020

How I wrote my first Neovim plugin

I am a big user of Vim and Neovim. I probably don't know all the bindings and all the magic inside, but I use it daily and it has become one of my favorite app.

In previous articles (here and here), I've already went through some plugins I (used) to use and some configuration tips.

But today, we are going to take one step further: writing a plugin!

The pain

We use Splunk at work a data-driven cloud app which has its own query language. Writing queries for Splunk can get cumbersome especially when they are starting to become larger and larger and you get lost in the amount of parenthesizes.

The solution I found was to write a small python script that would indent and separate the different blocks allowing a more convenient way to read the queries.

And then...

What about making it a Neovim plugin? As Neovim has a python interface, it should work right? Spoiler: yes.

Let's dive in!

Writing a plugin

First, we need to set a couple of things up. Python binaries must be in your shell PATH (you can check that with :checkhealth).

Screenshot of the result of :checkhealth

As you can see here, the python binaries are missing from the PATH and Neovim cannot run anything with it. So let's add those lines in our init.vim:

let g:python3_host_prog = '/usr/bin/python3'
let g:python_host_prog = '/usr/bin/python2'
Enter fullscreen mode Exit fullscreen mode

Alternatively you could add the binaries to you path:

export PATH=$PATH:/usr/bin/python:/usr/bin/python3
Enter fullscreen mode Exit fullscreen mode

And check it with echo $PATH.

Then check that you have both version of pynvim, the python Neovim library:

python3 -m pip install pynvim
python -m pip install pynvim
Enter fullscreen mode Exit fullscreen mode

After that we can start setting up our repository and our folders:

# do your git magic here
mkdir -p rplugin/python
touch rplugin/python/nvim-test.py
Enter fullscreen mode Exit fullscreen mode

According to the documentation, remote plugins are the new way to do things so we're going to follow that.

Let's look into nvim-test.py and get some inspiration from this Jacob Simpson's example:

import pynvim

@pynvim.plugin
class TestPlugin(object):
    def _init__(self, nvim):
        self.nvim = nvim

    @pynvim.function("TestFunction")
    def testFunction(self, args):
        self.nvim.current.line = "Hello from your plugin!"
Enter fullscreen mode Exit fullscreen mode

Breaking up the code:

  • We initialize the class and the subclass nvim
  • We create a function that will overwrite the current line

To test this, you could update your plugin on a git instance and update the plugins form your init.vim. But... you could also use a test vimrc:

echo "let &runtimepath.=','.escape(expand('<sfile>:p:h'), '\,')" > testvimrc
Enter fullscreen mode Exit fullscreen mode

Now start nvim -u testvimrc and run :UpdateRemotePlugins this will register you plugin as a remote plugin and generate the manifest. Check that with: cat ~/.local/share/nvim/rplugin.vim, the file should not be empty and you should see your plugin there.

Now, running after reloading Neovim, :call TestFunction() should normally work and you can test your work. I think that so far you need to reboot Neovim every time you update the remove plugins, but maybe there's a way to bypass that.

Now for the work I did:

import re

import pynvim


@pynvim.plugin
class SplunkLinter(object):
    def __init__(self, nvim):
        self.nvim = nvim

    @pynvim.function("LintSplunk")
    def lintSplunk(self, args):
        current_line = self.nvim.current.line
        front = current_line.split("(")
        i = 0
        j = 0
        res = ""
        for line in front:
            p = ""
            if j != 0:
                p = "("
            back = re.findall("\)", line)
            line = ((i) * "\t") + p + line
            self.nvim.current.buffer.append(line, -1)
            if len(back) == 0:
                i = i + 1
            elif len(back) > 1:
                i = i - len(back)
                if i < 0:
                    i = 0
            j = j + 1
        self.nvim.current.line = ""
        return

Enter fullscreen mode Exit fullscreen mode

Before you say anything, I know this can be improved. Basically this code is going to indent a string based on the level of parenthesizes that it contains. It's basically what I need and it does the job!

test (on) (one) (one (two (three))) (one) ((two))

# to

test
    (on)
    (one)
    (one
        (two
            (three)))
        (one)
        (
            (two))
Enter fullscreen mode Exit fullscreen mode

The Github repo is here: https://github.com/christalib/nvim-splunk-linter, feel free to open issues, PR's and such!

Have you already developed a plugin for Neovim or Vim? How did you do it?

💖 💪 🙅 🚩
evilcel3ri
chris

Posted on December 15, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related