Develop a Neovim plugin in Lua
As the author of hardtime.nvim, autoclose.nvim and smartcolumn.nvim, I’ve been developing Neovim plugins for a while. In this tutorial, I’ll break down the process and share my insights to help you start your own journey!
Setup project
Before developing the plugin, we need to set it up. There are two different approaches:
- setup with GitHub repository
- setup locally
Setup with GitHub repository
- Create a new repository on GitHub (e.g.: example.nvim)
- Install it with a plugin manager
-- lazy.nvim
{
"m4xshen/example.nvim" -- replace this with your {user_name}/{repo_name}
}
cd
to the directory where the plugin is installed
cd ~/.local/share/nvim/lazy/example.nvim
Setup locally
- Create a new directory on your local machine
mkdir ~/example.nvim
- Install it with a plugin manager
-- lazy.nvim
{
dir = "~/example.nvim"
}
cd
to the directory
cd ~/example.nvim
Plugin structure
Now the plugin is setup and installed. Let’s learn about its folder structure.
At the root of your plugin, create a structure like this (replace the example
with your plugin name):
example.nvim/
├── lua
│ └── example
│ └── init.lua
└── plugin
└── example.lua
plugin/
The files inside plugin/
folder will be executed when Neovim starts. Try to add the following line to example.lua
:
print("plugin/example.lua is executed!")
Open a new Neovim instance after that, you’ll see the string printed because Neovim runs the Lua code of the file.
lua/
Most of the time you don’t want the plugin to execute everything at startup. You want it to run some functions when events happened, command called, etc. and organize the code in a structured way. That’s where Lua module comes into play.
A Lua module is a regular Lua table that is used to contain functions and data. The table is declared local not to pollute the global scope. For example:
local M = {} -- M stands for module, a naming convention
function M.setup()
print("hello")
end
return M
If you want to load Lua module on demand, you can place them inside the lua/
directory in your 'runtimepath'
and load them with require
.
Here are the rules when require
finding files:
- Any
.
in the module name is treated as a directory separator when searching. - When the file with module name is not searched, it then searches for
init.lua
inside the folder with module name. - You don’t need to type the
.lua
extension.
Example: For a module foo.bar
, each directory inside 'runtimepath'
is searched for lua/foo/bar.lua
, then lua/foo/bar/init.lua
.
So if you put the code above into lua/example/init.lua
, you can run :lua require("example").setup()
to print hello. That is because the plugin manager added the folder of the plugins into 'runtimepath'
for you. Therefore the require
can find this file.
Example
After understanding the structure of Neovim plugin, let’s finish the example.nvim! This plugin does 2 simple things:
- It maps the
<Leader>h
to printhello
to the user. - If user specify their name when setting up plugins, it prints
hello, {user_name}
instead.
Here’s the lua/example/init.lua
:
local M = {}
function M.setup(opts)
opts = opts or {}
vim.keymap.set("n", "<Leader>h", function()
if opts.name then
print("hello, " .. opts.name)
else
print("hello")
end
end)
end
return M
You need to call setup
after installing plugin:
require("example").setup()
Or if you are using lazy.nvim:
{
"m4xshen/example.nvim",
opts = {}
}
Notice that we use vim.keymap.set
which is only available after Neovim 0.7.0. We need to add a version checker and it should be executed automatically at startup, so it should be put inside plugin/
folder.
Here’s the plugin/example.lua
:
if vim.fn.has("nvim-0.7.0") ~= 1 then
vim.api.nvim_err_writeln("Example.nvim requires at least nvim-0.7.0.")
end
After that this simple plugin is finished. Now try pressing <Leader>h
and the greeting message should be printed! You can also set your name inside setup
function so that it prints greeting message with name:
require("example").setup({
name = "Max",
})
Or if you are using lazy.nvim:
{
"m4xshen/example.nvim",
opts = {
name = "Max",
}
}
Check out the complete source code.
Final Words
Getting started is the hardest part when developing a Neovim plugin first time. I hope this tutorial helps you out. If you have any questions, feel free to ask in the comment section.