How I Developed My First Neovim Plugin: A Step-by-Step Guide

iamgoncaloalves

Gonçalo Alves

Posted on September 19, 2024

How I Developed My First Neovim Plugin: A Step-by-Step Guide

Are you a Neovim enthusiast looking to extend its functionality? Ever wondered how to create your own plugin and publish it up on Github? Look no further! In this post, I'll walk you through my journey of developing a simple "Hello World" plugin for Neovim.

For those who want to see the finished product or follow along with the complete source code, you can check out the full repository here:

neovim-plugin-hello-world GitHub Repository

Feel free to star the repository if you find it helpful!

Btw, I'm building a real useful Neovim plugin and I will share how I built this plugin here, so make sure you follow me if you don't wanna miss that.

 

Introduction

Neovim, a hyper-extensible Vim-based text editor, allows users to create custom plugins to enhance their editing experience. In this tutorial, we'll create a simple plugin that adds a :HelloWorld command and a keymapping to Neovim, which prints "Hello from Neovim!" when executed.

 

Setting Up the Development Environment

Before we dive into coding, let's set up our development environment:

  1. Ensure you have Neovim installed (version 0.5 or later recommended).
  2. Choose a directory for your plugin development. I used ~/development/neovim/neovim-plugins/.

 

Creating the Plugin Structure

Before we dive into coding our specific plugin, it's important to understand the typical structure of a Neovim plugin. This structure follows conventions that determine how and when different parts of the plugin are loaded.

The Anatomy of a Neovim Plugin

A generic Neovim plugin usually has the following structure:

.
├── LICENSE
├── README.md
├── plugin/
│   └── plugin-file.lua
└── lua/
    └── plugin-name.lua
Enter fullscreen mode Exit fullscreen mode

Let's break down each component:

  1. Root Directory:

    • LICENSE: The license file for your plugin.
    • README.md: Documentation and usage instructions for your plugin.
  2. plugin/ Directory:

    • Files in this directory are automatically executed when Neovim starts.
    • This is useful for setting up global commands, autocommands, or keymaps that should be available immediately.
    • Example: plugin/plugin-file.lua
  3. lua/ Directory:

    • This is where the main plugin code resides.
    • Code here is only executed when explicitly required by the user or other parts of the plugin.
    • There are two common ways to structure the main file: a. Single file: lua/plugin-name.lua b. Directory with init file: lua/plugin-name/init.lua

Key Points About Plugin Structure

  1. The plugin/ directory is for code that needs to run immediately when Neovim starts, regardless of whether the user explicitly requires the plugin.

  2. The lua/ directory contains the main plugin code, which is only executed when required. This is where most of your plugin's functionality should be implemented.

  3. Naming is important. The main file in the lua/ directory should typically match your plugin's name (e.g., hello-world.lua for a plugin named "hello-world").

  4. For simple plugins, a single file in the lua/ directory is often sufficient. For more complex plugins, you might use a directory with an init.lua file.

  5. The main entry point of your plugin (in the lua/ directory) is what users will require to use your plugin. For example, users would use require('hello-world') to load a plugin with the file lua/hello-world.lua.

This structure allows for efficient loading of plugin code, with immediate execution of necessary setup code (in plugin/) and on-demand loading of the main functionality (in lua/).

Structure for Our Hello World Plugin

For our simple "Hello World" plugin, we'll use the following structure:

neovim-plugin-hello-world/
├── LICENSE
├── README.md
└── lua/
    └── neovim-plugin-hello-world.lua
Enter fullscreen mode Exit fullscreen mode

Since our plugin doesn't need any immediate setup, we'll omit the plugin/ directory and focus on the main functionality in the lua/ directory.

Let's create this structure in your chosen development directory:

  1. Create a new directory for your plugin:
   mkdir -p neovim-plugin-hello-world/lua
Enter fullscreen mode Exit fullscreen mode
  1. Navigate to the plugin directory:
   cd neovim-plugin-hello-world
Enter fullscreen mode Exit fullscreen mode
  1. Create the main Lua file:
   touch lua/neovim-plugin-hello-world.lua
Enter fullscreen mode Exit fullscreen mode
  1. Create a README.md file:
   touch README.md
Enter fullscreen mode Exit fullscreen mode
  1. Add a LICENSE file (choose an appropriate license for your project).

Now that we have our plugin structure set up, we're ready to start writing the actual plugin code.

 

Writing the Plugin Code

Let's create the main plugin file. Open lua/neovim-plugin-hello-world.lua in your favorite text editor and add the following code:

-- Main module for the Hello World plugin
local M = {}

-- Function to print the hello message
function M.say_hello()
  print("Hello from Neovim!")
end

-- Function to set up the plugin (Most package managers expect the plugin to have a setup function)
function M.setup(opts)
  -- Merge user options with defaults
  opts = opts or {}

  -- Create the user command
  vim.api.nvim_create_user_command("HelloWorld", M.say_hello, {})

  -- Set up a key mapping
  -- Use opts.keymap if provided, otherwise default to '<leader>hw'
  local keymap = opts.keymap or '<leader>hw'

  -- Create the keymap
  vim.keymap.set('n', keymap, M.say_hello, { 
    desc = "Say hello from our plugin",
    silent = true  -- Prevents the command from being echoed in the command line
  })
end

-- Return the module
return M
Enter fullscreen mode Exit fullscreen mode

Let's break down this code:

  1. We define a module M that will contain all our plugin's functionality.
  2. The say_hello() function is the core of our plugin. It simply prints a greeting message.
  3. The setup() function is used to initialize our plugin with user-provided options:
    • It creates a user command :HelloWorld that calls our say_hello() function.
    • It sets up a keymapping (default <leader>hw) that also calls say_hello().
  4. We return the module at the end, making its functions available to users of our plugin.

 

Debugging Your Plugin

To debug your plugin locally using lazy.nvim with the dir option:

  1. Ensure you have lazy.nvim installed in your Neovim configuration.

  2. Modify your Neovim configuration to load your plugin locally. Add the following to your init.lua or wherever you configure your plugins:

require("lazy").setup({
  {
    "neovim-plugin-hello-world",
    dir = "~/development/neovim/neovim-plugins/neovim-plugin-hello-world",
    config = function()
      require("neovim-plugin-hello-world").setup({
        keymap = "<leader>hello"  -- optional: override the default keymap
      })
    end,
  },
  -- ... your other plugins ...
})
Enter fullscreen mode Exit fullscreen mode
  1. Restart Neovim or run :Lazy sync to load your plugin.

  2. Test your plugin:

    • Try running the command :HelloWorld
    • Use the keymapping (default <leader>hw or your custom mapping) in normal mode

You should see "Hello from Neovim!" printed in the command line for both methods.

Iterative Development

With this setup, you can easily iterate on your plugin:

  1. Make changes to your plugin code.
  2. Save the files.
  3. In Neovim, run :Lazy reload neovim-plugin-hello-world to reload your plugin.
  4. Test the changes by running :HelloWorld or using the keymapping.

This workflow allows for rapid development and testing without constantly restarting Neovim or manually sourcing files.

 

Installing the Plugin Remotely

When you're ready to share your plugin with the world, you'll need to push it to a GitHub repository. Here's how to make it installable via lazy.nvim:

  1. Create a GitHub repository for your plugin.
  2. Push your plugin code to the repository.
  3. Update your README.md file with installation and usage instructions.

Here's a sample README.md:

# neovim-plugin-hello-world

A simple Neovim plugin that adds a `:HelloWorld` command and keymapping.

&nbsp;
## Installation

Using [lazy.nvim](https://github.com/folke/lazy.nvim):

{
  "yourusername/neovim-plugin-hello-world",
  opts = {
      keymap = "<leader>hello"  -- optional: override the default keymap
    }
}

&nbsp;
## Usage

After installation:
1. Run `:HelloWorld` command in Neovim
2. Or use the keymapping (default `<leader>hw` or your custom mapping) in normal mode

Both will print "Hello from Neovim!" in the command line.
Enter fullscreen mode Exit fullscreen mode

(Don't forget to replace yourusername with your actual GitHub username.)

 

Conclusion

Congratulations! You've just created your first Neovim plugin. This simple example demonstrates the basics of plugin development, including structuring your code, creating user commands, setting up keymappings, and making your plugin installable.

As you become more comfortable with plugin development, you can explore more advanced features like autocommands, integrating with Neovim's API, and creating more complex functionality. The possibilities are endless!

Remember, the key to successful plugin development is iterative improvement and testing. With the setup we've created, you can quickly make changes, reload your plugin, and see the results in real-time.

Happy coding, and may your Neovim experience be ever more customized and efficient!

 

Connect with me

If you reached this far and liked this article be sure to leave a comment. That will make my day!

If you want to connect with me you can send me a message on Twitter/X.

If you want to read other stuff by me you can check out my personal blog.

You can also check other stuff that I have going on here

💖 💪 🙅 🚩
iamgoncaloalves
Gonçalo Alves

Posted on September 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related