How I wrote my first Neovim plugin
Tags: vim, todayilearned, tutorial
Published: 2020-12-15 22:41:55

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](https://dev.to/christalib/i-spent-3-years-configuring-n-vim-and-this-what-i-learnt-22on) and [here](https://dev.to/christalib/n-vim-shortcuts-for-the-lazy-2pll)), 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](https://www.splunk.com/en_us) 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](https://dev-to-uploads.s3.amazonaws.com/i/kjp4wek69inmz6psim64.png)

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

Alternatively you could add the binaries to you path:

```sh
export PATH=$PATH:/usr/bin/python:/usr/bin/python3
```

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
```

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
```

According to the [documentation](https://pynvim.readthedocs.io/en/latest/usage/remote-plugins.html), 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](https://github.com/jacobsimpson/nvim-example-python-plugin):

```python
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!"
```
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`:

```sh
echo "let &runtimepath.=','.escape(expand('<sfile>:p:h'), '\,')" > testvimrc
```

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:

```python
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

```

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))
```

The Github repo is here: [https://github.com/christalib/nvim-splunk-linter](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?