Neovim lua transition: vim-plug to lazy.nvim

2023/08/23


Impetus

When I started using Neovim and plugins, I used vim-plug because, well, that's what other people said to use and there didn't seem to be much difference between all the different management systems so I just went with it.

The process of adding new plugins and managing them in vim-plug was pretty straight forward.

"" init.vim

call plug#begin(has('nvim') ? stdpath('data') . '/plugged' : '~/.vim/plugged')
Plug 'shaunsingh/nord.nvim'
"" other plugins...
call plug#end()

This setup worked great and I really like vim-plug, however, I recently decided to change from vim-plug to lazy.nvim.

My main reason for doing this was part a broader plan to more fully embrace neovim. I'd always felt a nagging sense of shame for essentially just using my vim config in nvim instead of a "proper" lua config, so I started converting my init.vim to init.lua and this seemed as good a time as any to ruthlessly audit my config with the fanatical zeal that every vim user periodically undertakes this task with.

It was at this point that I came back to plugins, and this time I made a conscious decision to use lazy.nvim because it made more sense to me in the context of neovim. It was designed to be used with neovim as well as being written in lua, which fitted in with the original purpose I set out with. From what I'd read anecdotally, It was supposed to be far faster, and it also had a pretty UI which is nice to have.

This post describes how I managed to implement lazy.nvim in my neovim config. I'm not an expert with any of this stuff but there was very little online on how to do what I was trying to do so I just blagged my way through it and managed to make it work the way I wanted in the end.

Conversion

I had no idea if this experiment would work and I sort of rely on my editor to, you know, do work, so I made sure my fully functioning setup was backed up to source control. As an extra somewhat paranoid step I also renamed my existing config so that it would be there if I needed it but it wouldn't actually be loaded by neovim.

$ mv init.vim old_init.vim

Next, I created a new init.lua file. I just used the lua template to do this. I also decided to organise my config a little rather than mindlessly dumping each new setting or mapping into one hard to read file (again). It makes sense to split everything into modules and the docs explain where nvim will look for these. My setup was not super complex, I just grouped settings by a common theme (all custom mappings together for example) and put all those individual files in ~/.config/nvim/lua folder, this means you can then require them very easily in the source file like so:

-- ~/.config/nvim/init.lua

vim.g.mapleader = " "
-- Plugins
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)
vim.opt.termguicolors = true
require('lazy').setup('plugins')

-- Other settings
require('mappings')
require('general')
require('lsp_config')

The overall file structure looks like this:

.
├── init.lua
├── lazy-lock.json
├── lua
│   ├── general.lua
│   ├── lsp_config.lua
│   ├── mappings.lua
│   └── plugins.lua
├── old_init.vim
└── spell
    ├── en.utf-8.add
    └── en.utf-8.add.spl

This directory structure allows me to maintain a clear separation of concerns and easily manage different aspects of my configuration. For example, the general.lua file just contains all my general settings, and so on. The plugins.lua file is how we will define our plugins using lazy.

The require('lazy').setup('plugins') line expects a table or string of plugins.

Plugins are defined in a slightly different way to vim-plug, previously I had something like this:

" init.vim

call plug#begin(has('nvim') ? stdpath('data') . '/plugged' : '~/.vim/plugged')
Plug 'shaunsingh/nord.nvim'
Plug 'kyazdani42/nvim-tree.lua' "file explorer
call plug#end()

" other settings...

lua << EOF
require'nvim-tree'.setup {
    update_focused_file = { enable = true },
    view = { width = 60, side = 'right' },
    actions = { open_file = { quit_on_open = true } },
    renderer = { indent_markers = { enable = true } }
  }
EOF

luafile ~/.config/nvim/lsp_config.lua

With lazy.nvim I that now becomes:

-- ~/.config/nvim/lua/plugins.lua

return {
  {'shaunsingh/nord.nvim', lazy = true, priority = 1000},
  {
    'kyazdani42/nvim-tree.lua',
    config = function()
      require('nvim-tree').setup {
        update_focused_file = { enable = true },
        view = { width = 60, side = 'right' },
        actions = { open_file = { quit_on_open = true } },
        renderer = { indent_markers = { enable = true } }
      }
    end,
  },
}

Lazy loading, as the name suggests, is one of the main benefits of lazy.nvim. It means you can load only the modules you need and when you need them so something like the colorscheme 'shaunsingh/nord.nvim' can be lazy = true. Priority forces certain plugins to load first.

Resolution

Rinse and repeat (or record a macro) for every other plugin in your old_init.vim.

One thing to note is that lazy.nvim will reload each time it detects changes in the config so be aware of that when you're writing those changes.

After all is said and done run :Lazy in nvim and follow the instructions to install/sync etc. plugins.

Overall I'm very happy with the results and feel I achieved what I set out to do, ending up with a pure lua config for neovim that runs very quickly and is easy to use/maintain. It also means now that neovim is setup, I need to find a new target to focus my imposter syndrome on.

This is the article I wish that I had when I set out to do this, I hope that you find it useful in some way.

I would love to hear your feedback/suggestions. Until then, happy coding!

< Back to all posts