DEV Community

Cover image for Build a Neovim plugin in Lua πŸŒ™
Max Shen
Max Shen

Posted on • Edited on • Originally published at m4xshen.dev

Build a Neovim plugin in Lua πŸŒ™

TL;DR

In this article, you'll learn how to develop a Neovim plugin in Lua, understand the structure of Neovim plugin, Lua module, and create a simple plugin.

Let’s set it up πŸ”₯

Before developing the plugin, we need to set up the project. There are two different approaches:

  1. setup with GitHub repository
  2. setup locally

Setup with GitHub repository

  1. Create a new repository on GitHub (e.g.: example.nvim)
  2. Install it with a plugin manager
-- lazy.nvim
{
   "m4xshen/example.nvim" -- replace this with your {user_name}/{repo_name}
}
Enter fullscreen mode Exit fullscreen mode
  1. cd to the directory where the plugin is installed
cd ~/.local/share/nvim/lazy/example.nvim
Enter fullscreen mode Exit fullscreen mode

Setup locally

  1. Create a new directory on your local machine
mkdir ~/example.nvim
Enter fullscreen mode Exit fullscreen mode
  1. Install it with a plugin manager
-- lazy.nvim
{
   dir = "~/example.nvim"
}
Enter fullscreen mode Exit fullscreen mode
  1. cd to the directory
cd ~/example.nvim
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Rules of 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 print hello 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
Enter fullscreen mode Exit fullscreen mode

You need to call setup after installing plugin:

require("example").setup()
Enter fullscreen mode Exit fullscreen mode

Or if you are using lazy.nvim:

{
   "m4xshen/example.nvim",
   opts = {}
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
})
Enter fullscreen mode Exit fullscreen mode

Or if you are using lazy.nvim:

{
   "m4xshen/example.nvim",
   opts = {
      name = "Max",
   }
}
Enter fullscreen mode Exit fullscreen mode

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.


That’s a wrap. Thanks for reading. If you like this kind of stuff, consider following for more.

You can also see what I am working on my Personal Website and GitHub.

Top comments (0)