as nerdy as needed.

Neovim lessons learned after some weeks with it

This is a summary of my experience using Neovim for some time. It's not a guide, there are plenty of resources online for that. I've linked the most helpful ones at the end of this blog post.

What is my motivation?

That's really easy to explain and at the same time, debunk some of the most common misconception people have: I want to feel home in my editor/IDE whatever you call the tool you start up to type text or code. I want it to not only look, but behave the way it makes most sense to me. This covers things like theme, font, plugins, word wrapping, indentation, scrolling behaviour and much more.

I didn't start using Neovim for efficiency reasons, maybe I'm even slower in the end. It was for the same reason you might tune your car, experiment with your hifi setup, build a custom mechanical keyboard or paint your wall in Viva Magenta - I picked it up because I wanted that feeling of being in control, knowing something from the inside out, to some extent.

Less is more

The subjective part

Some parts of Neovim can be overwhelming, especially if it's your first time with keyboard-navigation based editors. You will try to memorize quite a bit of keyboard combinations, and your custom remaps for those. Keeping the basics for movement, modes and editing in your head is a good thing. Just don't try to actively remember any keymaps you encounter. The stuff relevant for you will stick automatically when using Neovim enough.

Sometimes it's hard to resist the urge to install and configure it to be able to support any so obscure and rare workflow you can imagine. This is the point I'm asking you to pause and think about why you'd want to try Neovim in the first place. For me, it was:

  • It needs to look good (theme)
  • Detailed syntax highlighting
  • Code formatting
  • Support for LSP
  • Fuzzy file/content search instead of a file explorer

Of course I didn't know that it will boil down to this list right from the start. It became apparent over time, after testing, failing and clearing out plugins and workflows that don't suit me.

Neovim does not need to resemble the editor you've came from. It's perfectly fine to take a different route here. Actually right now you are already doing that by reading a blog post about Neovim.

So much for the personal part of less is more.

The technical part

As Neovim and its plugins moves much faster than Vim, things can potentially break. Hence it's better to start with a small set of plugins, then expand when you understand the big picture and actually feel like you miss something.

The package manager we'll cover later makes it relatively user-friendly to rollback plugin versions to a "known good" state.

Start with a Neovim distribution

Some people advise to do this, others don't. I simply stumbled over kickstart.nvim and it was useful to see what a fully-configured Neovim configuration is capable of. But as soon as the urge to configure™ ignited, I knew it was time to roll my own configuration in order to understand and be able to maintain it over a long time.

Roll your own with lazy.nvim

When writing this blog post, I wanted to introduce the lazy.nvim package manager as an alternative to packer.nvim. In the meantime, Packer is no longer maintained, so adoption of lazy.nvim will probably even improve in the near future.

You don't generally base your whole configuration on a package manager, but it's good to align to standards/best practices a software expects. For one thing, to be able to benefit from other's people's solutions and secondly, to elegantly sidestep the not-invented-here syndrome.

My directory structure is simple, but allows to differentiate between package manager, plugins, key mappings and settings.

Structure of my configuration:

$ tree --filesfirst  ~/.config/nvim
├── init.lua
├── lazy-lock.json
└── lua
    └── weeheavy
        ├── init.lua
        ├── lazy.lua
        ├── prefs.lua
        ├── remap.lua
        └── plugins
            ├── devicons.lua
            ├── gitsigns.lua
            ├── indent-blankline.lua
            ├── lualine.lua
            ├── nightfox.lua
            ├── oil.lua
            ├── telescope-fzf-native.lua
            ├── telescope.lua
            ├── tokyonight.lua
            ├── treesitter-context.lua
            ├── treesitter.lua
            ├── vim-illuminate.lua
            ├── which-key.lua
            ├── zenmode.lua
            └── lsp
                ├── conform.lua
                └── lsp-zero.lua

lazy.nvim will manage a lockfile called lazy-lock.json by storing installed plugin versions in there. Imagine you have a plugin that is not pinned to a certain version (e.g. a "stable" tag).

Example situation: All is fine, until some day, someone introduces a breaking change in a plugin you use. Nobody got time to read all the changelogs, so you hit U to update all plugins. Now everything fails at the worst possible time. Instead of fixing things right now, you can recover from a failed plugin update by running :Lazy restore <plugin> which downgrades to the last good state.

This works when you either store your lockfile in version control or do backups of it before upgrading.

You could also use the restore functionality to sync multiple Neovim installations, as :Lazy restore also works for plugin upgrades as well.

Lazy loading

lazy.nvim supports loading plugins only when certain Neovim events, commands or keypresses occur. This is not that well documented in lazy.nvim. It can also lead to wrong behaviour if configured incorrectly.

Some people invest in tuning their config to lazy-load all the plugins while others ignore it. You can profile your startup time by opening a file with Neovim, then typing :Lazy profile.

A few things I learned on the way:

BufNewFile/BufReadPre events are fired when opening a new resp. existing file. This can be used for plugins that are only relevant when displaying a file. E.g. a statusline plugin, or a line indentation marker plugin.

I use the event trigger like this on lualine.nvim:

return {
    lazy = true,
    event = { "BufReadPre", "BufNewFile" },
    opts = {
        options = {

The cmd variant is useful for plugins that are launched by entering a Vim command. This also applies for commands that are mapped to a key combination. I use it for oil.nvim and telescope.nvim.

<leader>+e executes :Oil so the configuration for oil.nvim looks like this:

return {
    lazy = true,
    cmd = "Oil",
    dependencies = {
    config = function()


The LSP concept, setup and configuration is very confusing on first contact. I learned:

  • You can either manually install language servers (LSP), DAP, linters and formatters, or use the mason.nvim package manager
  • LSP depends on various plugins for which the relation is too confusing to even explain for me. That's why I use lsp-zero.nvim to abstract this. The biggest help was josean's blog post
  • It's not fully transparent what part does what. Example with a Markdown file:
  • The marksman language server supports with autocompletion of links
  • The prettier formatter neatly aligns table borders depending on content width

Neovim is surprisingly easy to install on Windows

I only tested Windows 10, but Nik F P's guide and installing dependencies documented in init.lua with Chocolatey was all I needed to make it work basically.

Telescope key bindings is another topic (e.g. Ctrl+v for vertical splits).

IMHO essential extensions

I consider a package manager, LSP, Treesitter absolutely vital, so I only list other plugins I use day to day. Reference: the full list of plugins I use (see the

My Neovim configuration

Most of the points above are implicitly reflected in my config. There's a README with further details in the link.

Key bindings

There's no need for yet another generic Neovim key bind cheat sheet. Still, I of course maintain my own cheat sheet, matching my personal key binds/remaps.

Useful resources

Similar posts