How I wrote my first neovim plugin

Pavel Savchuk
3 min readAug 4, 2022

--

The other day I was pulled into a conversation about some piece of code and, of course, we were sending links pointing to a specific lines. It was a particularly annoying for me, because I’m very quick in my editor (of course), and very slow in browser. And, after finding a file in my editor, I had to go into the browser , click “find file”, go through the file to find that line… All of that just to get the link.

Lua to the rescue!

Well, not only lua, neovim’s plugin system as well. It’s rather simple and I was able to slap together a bunch of code that works (sources). Here is how I did this and what you’ll need to do in case you want to write your own plugin:

  1. Create your plugin’s working directory. For me it was
mkdir link-remote-line && cd link-remote-line

2. You need two directories (or three, depending on how to count)

mkdir plugin && mkdir lua/link-remote-line

The convention is that you lua code is placed into a directory with the same name as you plugin, this makes it easier and clearer for users when they’re trying to `require` something from plugin.

3. In the plugin directory create .vim file named after your plugin (th esame reason for naming). This file will be loaded by neovim and you can create new user command and expose to the users whatever it is your plugin is doing.

echo 'command! LinkRemoteLine lua require("link-remote-line").generateLink()' > plugin/link-remote-line.vim

Here I’m using command! to create a new LinkRemoteLine command. Whenever user executes :LinkRemoteLine the second argument of the command! will be evaluated. In this case it’ll require my lua module and call generateLink.

4. In you lua folder, create init.lua and do whatever it is you want to do there. This file will be loaded when someone is requiring your module, like in the example above: require(“link-remote-line”) . You might want to put your code into various different file to makes things nice and clean, in this case you can load(require) them in init.lua later. I didn’t do this yet, because it took me just a few lines to do everything I wanted.

This is pretty much it. I’m not going to go over my lua code, because there is not much to it and you can go and read it for yourself. I left a few comments for my future self there (and for you).

Just want to mention a few points:

  • There is lui API and you’d think that it implement pretty much everything and you’ll be wrong. Many vim functions are not exposed there directly, and instead you need to call vim.fn.funcationName .
  • Calling a method on an object in Lua is done with : (colon) character, e.g. myString:gsub("s", "b") . You can tell I never worked with lua before.
  • Lua’s documentation is rather bad. Or it’s just my first expression. Maybe it’s just because of the lack of good examples. Sometimes I had to re-read the same paragraph a few times to really get what a function is doing.
  • Almost forgot to mention how to load the plugin while you’re working on it: add lua vim.opt.runtimepath:append(“~/link-remote-line”) to your init.vim or (init.lua , just drop the “lua” in the beginning). Basically, you want to add plugin’s working directory to the runtimepath, so that vim will load it.

Conclusion

Writing my first vim plugin was a fun experience and I’m already have some ideas for another one. I only with the whole “write a plugin” thing would’ve been more streamlined: there is a bunch of lua docs, even more vim docs, and not a single place where everything would be combined and explained in cohesive manner. At least I haven’t found such a place. If you know about one, hit me up. I’ll appreciate it!

--

--