So you want to write a neovim plugin with lua

April 6, 2024

I’ve recently been messing around with writing neovim plugins. When I initially got going I found it a little tricky to know how to get started. There’s the official neovim docs which are great; but in my beginner experience exhaustive to the point of slight impenetrability. Beyond that, the thing I found most useful was simply reading the source of some popular plugins to get an idea of how things worked. I would recommend sticking to plugins with a smaller scope though.

As a demostrative MVP (minimal viable plugin) jumping-off-point, I’m going to make a very simple note-taking plugin. It will provide a command to neovim which when run opens a file titled with the date and time in a specific notes directory. Vamos.

This is what you will want your directory structure to look like.

├── lua
│   └── note
│       └── init.lua
└── plugin
    └── note.vim

The plugin/note.vim file will look like this.

command! Note lua require("note").main()

This creates a custom command Note which when run will call a lua function. Now on to where that function and the meat of the plugin logic will live: the lua/note/init.lua file. With more complex plugins this section will often be split into many files but we’ve just got one here as it’s so simple.

First things first we create a plugin object.

local note = {}

Then we will define some default options for the plugin in a table. These are variables you want the user to be able to change when they call the setup function.

local defaults = {
  note_directory = "~/notes/",
  date_format = "%Y-%m-%d %H:%M",
  file_extension = ".md",
}

Next we need the setup function. This takes the user’s options and merges them with our default options.

function note.setup(user_options)
  options = vim.tbl_deep_extend("force", defaults, user_options or {})
end

This is the main function where the magic happens.

function note.main()
  local dir = options.note_directory
  local name = os.date(options.date_format)
  local ext = options.file_extension
  local filename = string.format("%s%s%s", dir, name, ext)
  local command = string.format("edit %s", filename)
  vim.api.nvim_command(command)
end

Finally we return the plugin obect.

return note

At this point you should have a working plugin :) As a little coda, this is how you can use your fancy new plugin using lazy.nvim.

require("lazy").setup({
  {
    -- local
    dir = "~/neovim-note-plugin",

    -- github
    -- "me/neovim-note-plugin",

    -- alternative non github hosting
    -- url = "https://git.example.com/me/neovim note-plugin",

    config = fucntion()
      require("note").setup({
        file_extension = ".org",
      })
    end,

  }
})

Hope you’ve enjoyed.