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
).
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:
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, 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!"
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
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
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, feel free to open issues, PR's and such!
Have you already developed a plugin for Neovim or Vim? How did you do it?
Top comments (1)
Hi, this was a handy guide for getting me started writing a neovim plugin, but in the example I copied below, the missing
_
in__init__
slowed me down a bit! Could you amend it for future users please?