Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

10 minutes readneovim

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

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:

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:

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