Neovim Plugins - Enhancing Your Neovim Editor with Awesome Plugins
Neovim comes with full support for Lua as a language for authoring neovim plugins. Here are some of my favorite neovim plugins:
Plugins
- Lua
- The nlua.nvim improves your lua development experience in Vim
- The plenary.nvim is a Neovim plugin to help you write plugins for neovim in lua.
- neodev.nvim is a plugin for setting up Neovim setup for init.lua and plugin development with full signature help, docs and completion for the nvim lua API. This is an alternative to nlua.nvim.
- nvim-luaref gives you a reference for builtin Lua functions within neovim.
- More lua development plugins
- Fuzzy finders
- telescope.nvim is a fuzzy finder for Neovim
- Package managers
- packer.nvim is a feature rich package manager for Neovim.
- lazy.nvim is a modern plugin manager for Neovim by the famed [email protected]
- More neovim plugins can be found in awesome-neovim.
Telescope nvim
Telescope.nvim is a fuzzy find matcher for Neovim centered around modularity and extensibility. It builds on the mental model of pickers, sorters and previewers, all of which can be combined with each other. It comes with a great number of built-in pickers and supports additional extensions.
Here is an example of some helpful built-in pickers:
-- File system
:Telescope find_files -- fuzzy file find in current working directory
:Telescope live_grep -- search for a string in current working directory live as you type
...
-- Vim
:Telescope buffers -- fuzzy finding over opened buffers
:Telescope oldfiles -- fuzzy finding over previously opened files
:Telescope help -- fuzzy finding over neovim help keywords
:Telescope quickfix -- fuzzy finding over your quickfix list
:Telescope command_history -- fuzzy finding over your command history
:Telescope search_history -- fuzzy finding over your search history
...
-- LSP
:Telescope lsp_references -- fuzzy find for references for word under cursor
:Telescope diagnostics -- fuzzy find over diagnostics
...
-- Treesitter
:Telescope treesitter -- fuzzy find over treesitter objects
Some of the most useful extensions are:
- telescope-fzf-native.nvim: Native FZF sorter that uses compiled C to do the matching, supports fzf syntax
- telescope-coc.nvim: Integration between coc.nvim and telescope. It allows you to use telescope to find, filter, preview and pick results from coc.nvim.
- telescope-emoji.nvim. An emoji picker for telescope.
- telescope-tmux.nvim. A telescope.nvim extension for finding tmux targets like sessions or panes.
For additional extensions take a look at the telescope wiki.
Packer.nvim
packer.nvim is a feature rich package manager for neovim that takes advantage of vim/neovim built-in package system (:h packages
). An example configuration for packer looks like this:
-- This file can be loaded by calling `lua require('plugins')` from your init.vim
-- Only required if you have packer configured as `opt`
vim.cmd [[packadd packer.nvim]]
return require('packer').startup(function(use)
-- Packer can manage itself
use 'wbthomason/packer.nvim'
-- Simple plugins can be specified as strings
use 'rstacruz/vim-closer'
-- etc
end)
When you’ve configured it you can run the following commands to manage and update your plugins:
-- You must run this or `PackerSync` whenever you make changes to your plugin configuration
-- Regenerate compiled loader file
:PackerCompile
-- Remove any disabled or unused plugins
:PackerClean
-- Clean, then install missing plugins
:PackerInstall
-- Clean, then update and install plugins
-- supports the `--preview` flag as an optional first argument to preview updates
:PackerUpdate
-- Perform `PackerUpdate` and then `PackerCompile`
-- supports the `--preview` flag as an optional first argument to preview updates
:PackerSync
-- Show list of installed plugins
:PackerStatus
-- Loads opt plugin immediately
:PackerLoad completion-nvim alek
See packer.nvim docs on GitHub for more information about how to install and configure it.
Writing plugins for neovim
The official way to write neovim plugins is to use the lua programming language.
Creating a simple plugin
A simple way to get started writing plugins in lua is to start with a simple function:
function Hello() print("hello world!") end
You can make neovim aware of this function by either sourcing the buffer :so %
or by loading it with the :lua
command like :lua function Hello() print("....") end
.
Now you can call the function :lua Hello()
and you’ll see how it prints the "hello world!
as a message.
A simple way to reuse this functionality and have it be ready to use the next time you open neovim is to create a user command:
Creating a custom user command
In traditional vim we can create user commands using the :command
ex-command. With neovim, in addition to that, we can create user commands from lua using the nvim_create_user_command
command:
vim.api.nvim_create_user_command("Hello", Hello, {})
Again to execute this code either source the buffer :so %
or use the :lua
command :lua vim.api.nvim_create_user_command("Hello", Hello, {})
.
Now you can use the :Hello
user defined command to print the "Hello world!"
message.
Creating auto commands
In traditional vim we can create autocommands using the :autocmd
or :au
ex-command. With neovim, in addition to that, we can create autocommands from lua using the nvim_create_autocmd
api:
vim.api.nvim_create_autocmd("CursorHold", { callback = Hello})
This will call the Hello
function every time you hold your cursor for a little bit. You can find which events you can subscribe to using :h {event}
.
Creating a key mapping
In traditional vim we can create mappings using the family of mapping commands :map
, :nmap
, :vmap
, :noremap
, :nnoremap
, etc. With neovim, in addition to that, we can create key mappings using the keymap.set
api:
vim.keymap.set("n", "<leader>h", Hello());
The anatomy of a neovim plugin
Now let’s say that you want to distribute your plugin so others can use it. In order to achieve that we first need to create the plugin in a shape that neovim can understand.
The most minimal lua neovim plugin has the following anatomy:
-- helloworld/
|
|-- lua/ # this is where you put the source code of your plugin
| |
| |
| |------- helloworld.lua
|
|-- plugin/ # lua files in this folder gets executed when neovim loads your plugin
| # this is where you'd put initialization logic like registering mappings
# or commands for your plugin
|
|------- helloworld.lua
Where the lua/helloworld.lua
is a lua module that encapsulates the source code for your plugin:
-- This is the convention lua uses to define modules
local M = {}
-- Here comes your plugin code
M.hello = function() print("hello world!") end
-- here we return the lua table that represents the API of this module to consumers of the plugin
return M
And the plugin/helloworld.lua
is a lua module that gets executed by neovim when your plugin is loaded (often on neovim startup) and which can setup your plugin:
-- Here we can do plugin initialization
-- Like register custom commands or mappings
-- We start by importing our plugin functionality using require
-- require("helloworld") will look for lua modules named:
-- * helloworld.lua
-- * helloworld/init.lua
-- require("helloworld.strings") will look for lua modules named:
-- * helloworld/strings.lua
-- * helloworld/strings/init.lua
local hello = require("helloworld").hello
vim.api.nvim_create_user_command("Hello", hello, {})
vim.keymap.set("n", "<leader>h", hello);
Installing your plugin
Everytime you start neovim, neovim looks in special paths (the runtime path and packpath) and loads plugins that it finds at those locations. So in order to install your plugin (make neovim aware of your plugin) you need to add your plugin to your runtime path or packpath. There’s different ways to achieve this, you can do it through a package manager which normally simplifies things, or manually add it to your runtime path or packpath (by creating a new package for your plugin). In summary:
- Use a package manager. Some package managers like packer.nvim allow you to specify path. You can tell the package manager to install your plugin by using relative path to wherever you’re working on it.
- Create a package
my-plugins
where you can develop your own plugins (e.g.{neovim_config_dir}/pack/my-plugins/start/my-plugin
). Any plugin that lives in that package will be automatically loaded by neovim on startup. See:h package
for more information. - Add the plugin folder to your runtime path by opening neovim with a
--cmd "set rtp+=~/plugin-folder"
or running:set rtp+=~/plugin-folder
from within neovim.
Testing plugins
plenary.nvim is a neovim plugin to help you write plugins for neovim in lua. It comes with a simple lua testing framework that let’s you write tests for your lua plugins.
Start by creating a folder for your tests and a file for your test:
-- helloworld/
|
|-- lua/
| |------- helloworld.lua
|-- plugin/
|------- helloworld.lua
|-- tests/
|------- helloworld_spec.lua -- every plenary spec must end with _spec.lua
Now you can write the actual test:
describe("hello", function()
it("can be required", function()
require("helloworld")
end)
it("prints hello world", function()
require("helloworld").hello()
end)
it("hello world returns hello world", function()
-- after updating the function to return the message so we
-- can assert it and showcase how this works :D
local helloWorld = require("helloworld").hello()
-- asserts work with the luassert library:
-- https://github.com/lunarmodules/luassert
assert.are.same("hello world", helloWorld)
end)
end)
To run this spec you can use the :PlenaryTestFile
that runs the test in the current buffer. This spec will run in a new instance of neovim in isolation from your current session.
You can find the full documentation for writing tests with plenary on GitHub.
Additional tips
Useful lua utilities
:lua vim.inspect(table) -- prints a table
:source % -- source current file. Useful to run lua script under development.
Reloading cached modules
When using require
to require lua modules the required module is cached. That means that if you’re working developing a plugin and want to have neovim evaluate your updated function calling require
again won’t work since the lua runtime will have cached the module already. Under these circumstances you’ll likely want to refresh the cache so you can re-require that module with your latest changes. You can do that like so:
package.loaded.mymodule = nil
-- alternatively:
-- package.loaded['mymodule'] = nil
-- loads latest version of module 'mymodule'
require('mymodule')
plenary.nvim is a neovim plugin to help you write plugins for neovim in lua. It comes with a number of helper functions amongst which there’s one to refresh a lua module: reload:
require("plenary.reload").reload_module "mymodule"
Useful lua functions to have in your config
P = function(value)
print(vim.inspect(value))
return value
end
RELOAD = function(...)
return require("plenary.reload").reload_module(...)
end
R = function(name)
RELOAD(name)
return require(name)
end
More information
Here are some interesting resources to help you get started writing your own plugins for neovim:
- nvim lua guide
- Learn lua in Y minutes
- Articles
- Videos
You can find all information about lua plugins in neovim taking a look at the help:
:h lua
:h vim.api
Using Vim plugins with Neovim
Neovim still has full compatibility with Vim plugins. Take a look at the Vim plugins for more interesting plugins that are not written in lua but still supported in Neovim.
Resources

Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.