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 vimer, 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
tabstopandsofttabstop - tpope/vim-fugitive - amazing
gitintegration - christoomey/vim-tmux-navigator -
tmuxintegration - aspeddro/slides.nvim -
slidesintegration
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
goplsloaded 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.selectprompt 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.