Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

22 minutes readneovim

Neovim Plugins - Enhancing Your Neovim Editor with Awesome Plugins

A sort of weird android enhanced with many plugins
Use these plugins to enhance your editing experience with neovim and make it behave like a modern IDE

Although Neovim supports most Vim plugins, it also comes with full support for Lua as a language for plugin authoring. This choice has led to a renaissance of sorts in the world of Neovim plugins. This article has a collection of plugins that I’ve found useful and interesting, and also some guidance about how to write your own plugins.

Table of Contents

Plugins

  • UI and User experience
    • noice.nvim is a highly experimental plugin that takes advantage of some of the latest neovim features to completely replace the UI for messages, cmdline and the popupmenu.
    • nvim-notify is a notification manager for neovim (noice uses it to show fancy notifications)
    • mini.nvim is a collection of modules that improve neovim’s user experience in small ways:
      • mini.pairs provides minimal and fast autopairs (for symbols like quotes or parenthesis)
    • bufferline.nvim provides a “bufferline”, which is a tab-like user interface where open buffers appear at the top of the screen as tabs.
    • vim-illuminate automatically highlights other uses of the word under the cursor using either the neovim LSP, Treesitter or reg-ex matching.
  • Fuzzy finders
  • Motions and moving around quickly
    • leap.nvim is a similar plugin to vim-sneak that adds labels to the targets and allows motions across files.
    • flit enhances the f/F and t/T motions in neovim by providing visual feedback and label across multiple lines.
  • Coding
    • LuaSnip is a powerful snippet engine written in lua.
  • Plugin managers
  • Language Server Protocol. LSP plugins bring IDE-like functionality into Neovim.
    • nvim-lspconfig provides a collection of LSP configurations for many programming languages.
    • mason is a package manager for neovim that lets you install and update LSP servers, DAP servers, linters and formatters.
    • nvim-cmp is a completion engine for Neovim written in lua. It’s the de facto standard completion plugin for the native LSP client.
    • trouble.nvim for a pretty list of diagnostics and other lists (LSP symbols, quickfix list, location list, etc).
    • coc.nvim provides an alternative experience to the native LSP client and family of plugins. With coc.nvim you get everything within the same plugin: LSP client configuration, LSP server installation, completion, diagnostics, formatters, etc.
  • Lua programming
    • 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
  • AI plugins
  • Distributions
  • More neovim plugins can be found in awesome-neovim.

Below there are some extended summaries about different plugins, useful commands and recommended mappings.

UI and User Experience

Noice

A noice screenshot showcasing the beautiful UI

noice.nvim is a highly experimental plugin that takes advantage of some of the latest neovim features to completely replace the UI for messages, cmdline and the popupmenu:

  • Instead of the normal vim/neovim cmdline, noice adds a VSCode-like command window to run your ex-commands. When you type a search or a filter the command window updates its icon to reflect a search or a filter. It also provides syntax highlighting.
  • Instead of the traditional :messages that brings a non-interactive window with a more prompt, noice puts :messages into a normal buffer
  • As a enhancement over :messages, noice has the :Noice command that provides a more aesthetically pleasing full messages history.
  • You can open the message history with telescope and fuzzy find stuff using :Noice telescope.

Some useful commands are:

:Noice           " shows the message history
:Noice history   " ditto
:Noice last      " shows the last message in a popup
:Noice errors    " shows the error messages in a split. Last errors on top
:Noice disable   " disables Noice
:Noice enable    " enables Noice
:Noice stats     " shows debugging stats
:Noice telescope " opens message history in Telescope

And there’s a lot more.

Bufferline

bufferline.nvim adds a buffer line to neovim, a tab-like user interface where every buffer looks like a tab with additional UI indicators like file types, lsp diagnostics, etc. Aside from the helpful indicators one can also:

  • Pin buffers
  • Group buffers
  • Re-order buffers
  • Move quickly between buffers using H and L
  • Interact with all buffers with a mouse

A demo of the bufferline user interface showing a series of tabs for each buffer that is currently open

vim-illuminate

vim-illuminate automatically highlights other uses of the word under the cursor using either the neovim LSP, Treesitter or reg-ex matching.

Neovim user shows the vim-illuminate plugin and how it highlights matches under the cursor

You can also use the following mappings to jump between matches:

  • <a-n> jump to next match
  • <a-p> jump to previous match
  • <a-i> as a text object that represents the match e.g. d<a-i> deletes the match

For more information, take a look at the vim-illuminate documentation.

Fuzzy finders

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:

Some recommended mappings:

-- From https://github.com/nvim-lua/kickstart.nvim
-- See `:help telescope.builtin`
vim.keymap.set('n', '<leader>?', require('telescope.builtin').oldfiles, { desc = '[?] Find recently opened files' })
vim.keymap.set('n', '<leader><space>', require('telescope.builtin').buffers, { desc = '[ ] Find existing buffers' })
vim.keymap.set('n', '<leader>/', function()
  -- You can pass additional configuration to telescope to change theme, layout, etc.
  require('telescope.builtin').current_buffer_fuzzy_find(require('telescope.themes').get_dropdown {
    winblend = 10,
    previewer = false,
  })
end, { desc = '[/] Fuzzily search in current buffer]' })

vim.keymap.set('n', '<leader>sf', require('telescope.builtin').find_files, { desc = '[S]earch [F]iles' })
vim.keymap.set('n', '<leader>sh', require('telescope.builtin').help_tags, { desc = '[S]earch [H]elp' })
vim.keymap.set('n', '<leader>sw', require('telescope.builtin').grep_string, { desc = '[S]earch current [W]ord' })
vim.keymap.set('n', '<leader>sg', require('telescope.builtin').live_grep, { desc = '[S]earch by [G]rep' })
vim.keymap.set('n', '<leader>sd', require('telescope.builtin').diagnostics, { desc = '[S]earch [D]iagnostics' })

For additional extensions take a look at the telescope wiki.

Motions and Moving Around Quickly

Leap

leap.nvim is a plugin similar to vim-sneak that adds labels to the targets and allows motions across files. To use it:

  1. Type s to leap forward, S to leap backwards or gs to leap into another buffer window
  2. Type two characters that mark your motion target. E.g. if you want to jump to banana you could type sba
  3. If there’s only a match you jump immediately there, otherwise you jump to the first match and see a number of labels that you can jump directly to by typing the label (e.g. s). Alternatively use ; to jump to the next match , to jump backwards. (Like with f and t)

For more info take a look at the leap.nvim documentation.

Flit

flit enhances the f/F and t/T motions in neovim by providing visual feedback and label across multiple lines. Essentially it’s like leap.nvim but instead of using a special command s it extends the behavior of the built-in f and t motions.

Coding

LuaSnip

LuaSnip is a powerful snippet engine written in lua.

An example snippet

How Do Snippets Work in LuaSnip?

Creating a Snippet

If you want to learn more about LuaSnip, the official docs have lots of useful information and interactive gifs that showcase LuaSnip in action. Alternatively, take a look at these resources for new LuaSnip users.

Language Server Protocol

The Minimal LSP Setup

For a minimal LSP setup for neovim I recommend the following plugins:

  • nvim-lspconfig provides a collection of LSP configurations for many programming languages.
  • mason is a package manager for neovim that lets you install and update LSP servers, DAP servers, linters and formatters.
  • mason-lspconfig integrates mason with nvim-lspconfig.
  • nvim-cmp is a completion engine for Neovim written in lua. It’s the de facto standard completion plugin for the native LSP client. It provides completions by using different sources like the LSP, buffers, path, emojis, etc. Sources are typically provided by additional plugins.
  • trouble.nvim for a pretty list of diagnostics and other lists (LSP symbols, quickfix list, location list, etc).

This means that nvim-lspconfig will manage all your LSP configurations, mason will let you install and update LSP servers, nvim-cmp will provide you with completions using the LSP and other sources, and trouble.nvim will give you beautiful lists of diagnostics.

Alternatively, you can use the lsp-zero which bundles all of these plugins (and some other ones) with a minimal configuration.

All-in-one alternative

coc.nvim brings an IDE like experience to neovim with one single plugin

On the other side of the spectrum you have another alternative: coc.nvim. One plugin with a simple configuration that brings everything you need:

  • LSP client configuration and server installation in the form of coc extensions
  • completion
  • code actions
  • diagnostics
  • formatters
  • and more…

Plugin Managers

Lazy.nvim

For more information about lazy.nvim take a look at my notes on LazyVim.

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.

AI plugins

Copilot

Two useful plugins to integrate copilot in neovim are:

For additional configurations for the copilot panel, suggestions, filetypes supported, etc, take a look at the zbirenbaum/copilot.lua docs.

If you are using LazyVim you can add copilot as an extra with one single step.

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:

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


Jaime González García

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.Jaime González García