Lazy Neovim Configuration
I was a vim
user for a while. The :wq
, "+P
, y
, g
, gg
, =g
and all
the cryptic shortcuts invented ten years before I was born… They are just
hard wired into my brain.
A while ago I decided to use neovim
. I was most impressed the
fact that neovim
turned out to be a healthy, good community project. So I did
my little bit at opencollective and
I contribute a few bucks monthly to the awesome neovim
community back.
Lazy neovim configuration
hyperextensible Vim-based text editor
As a long time vim
er, I am amazed and frightened by the hyperextensible
nature of a neovim. On the one hand neovim
can do much more than vim
, due
all the new features it has. The lua for plugins, async, builtin LSP,
treesitter integration and so. On the other hand, all this super power comes at
a cost. I went from a few plugins I used with vim
to 24 ones.
That is a lot of a configuration and integration.
Especially if you are new to neovim
and its plugins and don’t have a no idea where to start.
Kickstart
I can’t recommend the
kickstart.nvim enough. Unlike a
full featured neovim
distribution this is designed to (kick)start your neovim
journey and you’re expected to maintain your own configuration.
There is a video by TJ de Vries about it. In which he explains the whole
configuration, for example the reason why C-y
is used to accept the
completion instead of the more traditional Enter
. There are a few other
interesting bits, like letting neovim
communicate more capabilities to LSP.
-- LSP servers and clients are able to communicate to each
-- other what features they support. By default, Neovim
-- doesn't support everything that is in the LSP Specification.
-- When you add nvim-cmp, luasnip, etc. Neovim now has *more*
-- capabilities. So, we create new capabilities with nvim cmp, and
-- then broadcast that to the servers.
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = vim.tbl_deep_extend(
'force',
capabilities,
require('cmp_nvim_lsp').default_capabilities())
It uses folke/lazy.nvim, which is where the title of this post comes from.
Distributions
Another approach is to go and use a neovim distro. Personally, I found such
enhanced editor too intimidating for vim
veterans like me. Also,
there are too many distributions out there. So many there is a tool that allows you to
switch between them
https://lazyman.dev.
I counted 14 of them on a page.
Give the kickstart.nvim
a try, really.
My plugins
I was a stubborn guy who was proud to call a git clone to install his plugins. This does not work well for advanced neovim and I found folke/lazy.nvim to be good and well known option. It supports the lockfile, so you never have need to upgrade any plugin.
Theme
ellisonleao/gruvbox.nvim is my favourite theme. Great dark one and a great light one. Colors in this blog’s CSS are from gruvbox itself.
nvim-tree/nvim-web-devicons supports all the tiny icons that make the editor more aesthetically pleasing.
Misc
Not everything falls into a category 5 plugins you need for your $DAYJOB. Here
are not non-essential but useful plugins that integrates neovim
with tmux
or git
.
Did you know that you can create and present slides from neovim
?
- tpope/vim-sleuth - do the right thing with
tabstop
andsofttabstop
- tpope/vim-fugitive - amazing
git
integration - christoomey/vim-tmux-navigator -
tmux
integration - aspeddro/slides.nvim -
slides
integration
LSP
I used the dense-analysis/ale plugin
with vim
. And it is an excellent plugin that I miss a bit. It is a single
solution for a LSP, linting and a formatting. And it required almost no
configuration. If the required tool was installed, ale
was ready to use it.
And unlike coc.vim
it don’t need a node.js installed.
The neovim
story is a bit more complicated. This is where the
hyperextensibility comes into the play. Or a Cambrian explosion of a neovim
plugins.
Let us start with VonHeikemen/lsp-zero.nvim
Out of the box it will help you integrate nvim-cmp (an autocompletion plugin) and nvim-lspconfig (a collection of configurations for various language servers)
This is not really needed and kickstart does not use it. However since
ThePrimeagen’s video “Neovim: 0 to LSP” recommends it, I use it too. It
introduces a few more plugins and plugin dependencies and dependencies of a
plugin dependencies. This is where lazy.nvim
enters the chat.
- neovim/nvim-lspconfig provides a configuration for LSP servers. Each must be enabled in a config
require('lspconfig').gopls.setup({})
- hrsh7th/nvim-cmp is a completion plugin which that displays completion hints from all various sources. LSP beeing one of them.
- j-hui/fidget.nvim provides a nice progress notifications. Like when the
gopls
loaded a workspace.
{
'neovim/nvim-lspconfig',
dependencies = {
{'hrsh7th/nvim-cmp'},
{ 'j-hui/fidget.nvim', opts = {} },
}
},
Kickstart integrates with
williamboman/mason.nvim. Since I
don’t want my editor to install programs behind my back I do not use it. Which is
fine and in a line with the neovim
philosophy.
Lint
The native solution for linting is mfussenegger/nvim-lint, which is used by the lazyvim distribution. It took a bit of fiddling to figure out the setup. But I found a configuration I am happy with.
-- linting - nvim native, complementary to LSP
{
"mfussenegger/nvim-lint",
event = { "BufWritePost", "BufReadPost", "InsertLeave" },
config = function()
local lint = require("lint")
lint.linters_by_ft = {
go = {"golangcilint"},
sh = {"shellcheck"},
proto = {"buf_lint"},
}
local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true })
vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave" }, {
group = lint_augroup,
callback = function()
lint.try_lint()
end,
})
vim.keymap.set("n", "<leader>l", function()
lint.try_lint()
end, { desc = "Trigger linting for current file" })
end,
},
Treesitter
Treesitter is one of the features
of a neovim
that I was skeptical about. I thought the vim
’s regexp based
parsing was good enough. I could not have been more wrong. Colors provided via
treesitter gives me more information about a semantics helping the readability.
It also does not break in the middle of a big file. And it has powerful
queries that can give you a context of source file like what is the function name
you are currently editing.
- nvim-treesitter/nvim-treesitter the tree-sitter itself.
- nvim-treesitter/nvim-treesitter-context which provides a nice context of a source code on top of the editor.
It is definitely something to explore more.
Fuzzy search
I have been using vim-fzf
for an eternity and can’t work without a fuzzy
search.
nvim-telescope/telescope.nvim
is a native plugin for neovim
. You can fuzzy search - files, open buffers,
LSP output and even a list of vim config files. Kickstart does an amazing job
of showing the possibilities and how to integrate the LSP and telescope.
map('gd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition')
- nvim-telescope/telescope-fzf-native.nvim is an optional dependency and gives you an access to fzf as Lua.
- nvim-telescope/telescope-ui-select.nvim
replaces the default
vim.ui.select
prompt with telescope. This is what allows an integration of LSP and Telescope.
Autoformatting
stevearc/conform.nvim fixes some missbehaving LSPs by
Conform calculates minimal diffs and applies them using the built-in LSP format utilities.
So the marks and others won’t get removed by LSP reformat.
Snippets
L3MON4D3/LuaSnip is something I would love to explore more. I have a plan to watch TJ de Vries’s video about snippets for Go.
{
'L3MON4D3/LuaSnip',
dependencies = {
{'rafamadriz/friendly-snippets'},
},
run = "make install_jsregexp",
},
Completion
The mighty hrsh7th/nvim-cmp has taken
the code completion to whole new level. This is a typical plugin for neovim
.
Provides a core functionality itself and requires dependent plugins to
integrate with other components.
-- Autocompletion
{
'hrsh7th/nvim-cmp',
dependencies = {
{'L3MON4D3/LuaSnip'},
{'hrsh7th/cmp-buffer'},
{'hrsh7th/cmp-nvim-lsp'},
{'saadparwaiz1/cmp_luasnip'},
},
},
Which are called sources
and must be enabled as well.
sources = {
{ name = "nvim_lsp" },
{ name = "path" },
{ name = "luasnip" },
{
name = 'buffer',
option = {
get_bufnrs = function()
return vim.api.nvim_list_bufs()
end
}
}
},
Conclusion
Configuring the neovim
can be a lot of a work. Starting with a kickstart
makes it much easier. You can get quite advanced configuration that is meant
to be adapted. There are popular plugins like which-key
used in most of
distributions and I never learned to like it.
At the moment I have mixed feelings about this. Configuring neovim
requires a
lot of Lua code a lot of different plugins to be installed. On the one hand
this gives you the flexibility to configure everything exactly the way you want
to. On the other hand I hope that the existing state is just a Cambrian
explosion will ends eventually and some of the functionality will finds its way
into the core neovim
.