Writing Neovim Plugins — A Beginner Guide (Part 2)

A beginner guide on writing Neovim plugins in Lua.

Writing Neovim Plugins

Overview

In Part 1 of the article, I developed a very simple Neovim plugin. In this article let’s explore Neovim Lua's libraries and use them to enhance the plugin.

Development Setup

Let’s get started by setting up a proper plugin development environment.

Help

Type :nvim_ followed tab and you should be able to see the Neovim built-in Lua APIs. However, navigating the help documentation is a bit cumbersome so let’s set up LSP.

Telescope. nvim

If you install telescope.nvim, type :Telescope help_tags and you can easily search for the Neovim APIs that you need.

telescope.nvim Help

Lua Language Server — sumneko_lua

Set up Lua language server following the instructions here.

luadev.nvim

For this tutorial, I am going to use luadev.nvim.luadev.nvim enables development setup for init.lua and plugin development with full signature help, documentation, and completion for the Neovim Lua APIs.

Lua LSP

I am using packer.nvimto install the plugin.

use {'folke/lua-dev.nvim'}

Run :luafile % and :PackerInstall to install the plugin, and use the configuration below to set up the plugin.

LSP Setup

You can refer to this file for the full listing of the code. Note that I also set up LSP for Vimscript.

With the plugin setup, we should be able to get proper LSP support for using Neovim libraries as well as Vimscript.

Tip: Type:LspInfo to check if LSP is configured correctly.

Lua Scratch Pad

Let’s also install a debug console to help to develop Lua plugins. I am going to use nvim-luapad which provides three different commands, that will help you with developing Neovim plugins in Lua:

  • Luapad: which opens an interactive scratch buffer with real-time evaluation.
  • LuaRun: which runs the content of the current buffer as Lua script in the new scope. You do not need to write a file to disk or have to worry about overwriting functions in the global scope.
  • Lua: which is an extension of the native Lua command with function completion.
Luapad
Luapad + Luadev

Let’s proceed to develop a simple plugin using Neovim APIs.

Plugin to Generate Debug Profile

In previous articles, I walked through with you on using vimspector and nvim-dap for debugging.

For vimspector, we need to configure debug profile by creating .vimspector.json. Let’s develop a plugin feature to automatically generate this file based on the file type.

Create the following file.

  • lua/alpha/vimspector/init.lua

Add the lines below to the file.

vimspector init.lua

This is a Lua module with a method called generateDebugProfile.

Getting File Type for Current Buffer

-- Get current file type
local buf = vim.api.nvim_get_current_buf()
local ft = vim.api.nvim_buf_get_option(buf, "filetype")
  • I call the Neovim API to get the current buffer handle.
  • With the returned buffer handle, I check the file type.

Getting Python Path

-- Get Python path
local python3 = vim.fn.exepath("python")
local debugProfile = string.format(vimspectorPython, python3)
  • If the file type is “python”, I use the Neovim function to check the Python 3 executable path.
  • With the returned path, I replace the Python path in the debug profile configuration.

Generate a New Buffer

-- Generate debug profile in a new window
vim.api.nvim_exec('vsp', true)
local win = vim.api.nvim_get_current_win()local bufNew = vim.api.nvim_create_buf(true, false)vim.api.nvim_buf_set_name(bufNew, ".vimspector.json")vim.api.nvim_win_set_buf(win, bufNew)lines = {}
for s in debugProfile:gmatch("[^\r\n]+") do
table.insert(lines, s)
end
vim.api.nvim_buf_set_lines(bufNew, 0, -1, false, lines)
  • Create a vertical split.
  • Get the window handle of the new split window.
  • Create a new buffer (:h nvim_create_buf).
  • Set the buffer name to .vimspector.json.
  • Set the buffer in the current window.
  • Convert the debug profile string into a list and assign it to the new buffer.

Define Plugin Command

Now open plugin/alpha.vim and define a command to invoke the Lua module.

if exists('g:loaded_alpha') | finish | endiflet s:save_cpo = &cpo 
set cpo&vim
" command to run our plugin
command! AlphaDebugProfile lua require("alpha.vimspector").generateDebugProfile()
let &cpo = s:save_cpo
unlet s:save_cpo
let g:loaded_alpha = 1

Testing

Restart Neovim, open a Python file, and type :AlphaDebugProfile

Testing

You can see a new vertical split is created with the debug profile.

You can experiment with the plugin to extend it to support other languages.

Health Check

Our plugin needs the latest version of Neovim. Let’s create a health check function.

Create the following file

  • autoload/health/alpha.nvim

Add the health check into this file. I only check for the Neovim version. Depending on your requirements, you can add additional checks here.

function! health#alpha#check()
if !has('nvim-0.5')
call health#report_warn("please install nvim > 0.5")
else
call health#report_ok("nvim 0.5 installed")
endif
"check more health conditions here
"if !executable('python')
" health#report_error("Python not installed")
"endif
endfunction

Now type :checkhealth

checkhealth

Summary

In this article, I used Neovim Lua libraries to develop a very simple plugin. In future articles let’s continue to explore more advanced features.

The files I used can be found in this repository.

References

Programmer and occasional blogger.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store