NVim must-have plugins
I’ve recently seen quite a few of people on Mastodon asking for “recommended plugins” as they (re)discover nvim
.
I don’t actually know why now. Maybe it’s like the new year’s resolution hitting late for many developers? Something along with getting back to work, full of vacations renewed hope?
Anyway, in this post I’ll try to sum up what I personally would want to find on every configuration. I know there are so-called nvim
distribution like astro, lazy to name a few. But I feel like they’re too complex, complete and opinionated. I’d rather hand-pick from a list than have too much for my use cases.
Here we go!
A plugin manager
For years, I managed my plugins via the (n)vim
native mechanisms and a maintained curated list of git submodules
. Not gonna lie, it does the job. But it’s a lot of manual work outside the editor. So I started using a plugin manager and can’t recommend the experience enough.
I’ve used multiple plugin managers over time. And I settled for lazy
. Mainly because it was the latest cool kid in the block at some point. But it does the job, does it well and does it fast. What else would I want?
The lazy
repository showcases everything you need from installation to customization. I’m not going to paste code here as I consider the lazy
repository the unique and best source of truth. The rest of the list might on the other hand come with lazy
examples.
Treesitter
Treesitter
is not only here to render colorful code.
Granted it does that. But it also acts as a foundation layer for other plugins. For example the excellent neorg
makes extensive use of treesitter
in the internal of its notes taking engine. Some others will add new text object based on treesitter
’s output.
Installation
{
"nvim-treesitter/nvim-treesitter",
name = "treesitter",
config = function() require("plugins.treesitter") end
}
What do we have here? It’s some lua
code as shown in the lazy
repository. All it does is:
- Install the
nvim-treesitter
repository from the eponymousgithub
user, - Name it
treesitter
instead ofnvim-treesitter
. This is completely optional. I just don’t like having all thosenvim-*
scattered across my configuration. - Load it’s configuration from another file. Again, this is optional. I like to keep the plugins list separated from each plugin’s configuration.
Configuration
local parsers = {
"lua",
"vim",
}
require("nvim-treesitter.configs").setup({
auto_install = true,
ensure_installed = parsers,
highlight = { enable = true },
indent = { enable = true },
})
This is pretty neat!
The treesitter
plugin takes a list of syntaxes and ensure those parsers are always installed. I find lua
and vim
to be the very minimal set as they’re the languages nvim
uses itself.
On top of handling all the highlight and indentation as any text editor should, there is the last magic line: auto_install = true
. Anytime you open a buffer of a new filetype, treesitter
will fetch its parser and give you all the aforementioned niceties in a blink!
A color scheme
Native color schemes are somewhat ugly IMHO. I used the desert
theme in nvim
for so many years I did not even realize how hard-on-the-eyes it was. I guess you can get accustomed to anything after all.
Anyway, there are plenty of awesome color schemes out there. Some are inspired by other editors, some come from the good old vim
days and some are full-featured lua
plugins for nvim
. You should have a look at night fox
, ever forest
, tokio night
or even catppuccin
. They all are well balanced dark themes (yeah, sorry, dark theme lover here, I don’t know much about the light ones).
I personally fell in love with kanagawa
. I don’t know, I find it zen and sweet. And I don’t want my terminal to yell at me so it’s a good pick. I like it so much I also use it outside of nvim
in my alacritty
configuration.
Installation
{
"rebelot/kanagawa.nvim",
name = "kanagawa",
config = function() require("plugins.kanagawa") end
},
No surprise here. Just like for treesitter
:
- Fetch the plugin,
- Give it a sensible name,
- Load its configuration.
Configuration
require("kanagawa").setup({
dimInactive = true,
transparent = true,
})
vim.cmd("colorscheme kanagawa")
Few lines here.
I like a bit of transparency in my terminal and my editor. I find quite handy to increase it a lot during meetings so that I can have nvim
on top of the visio-conference and take notes while also looking at the speaker. The magic of touch-typing in action.
I also set dimInactive
to true
. It makes it easier to spot which buffer I work on when in split view.
And the last line simply enables the kanagawa
color scheme. Simple and effective as all things should be.
Telescope
Telescope
is a super-charged, highly-modular, fuzzy finder.
It allows you to search over lists of pretty much anything. From an in-nvim
ripgrep
to search for a word’s occurences to a documentation crawler to a git
integration, it does it all.
What makes it so great is its extensibility. Other plugins can easily hook into telescope
API and extend its possibilities far beyond vanilla telescope
. It came in handy for hyperlinks insertion while in insert-node during neorg
notes taking for example.
Installation
{
"nvim-telescope/telescope.nvim",
name = "telescope",
config = function() require("plugins.telescope") end,
keys = {
"<Leader>|",
"<Leader><Leader>",
"<Leader>tg",
"<Leader>th",
"<Leader>tk",
}
}
All right, something new here: keys
.
I don’t always need telescope
. And when I do, it’s usually for a couple of features I use all the time. So instead of going :Telescope command args<CR>
I prefer to set some shortcuts in normal mode. Here are what they’re used for:
<Leader><Leader>
: Opens an interactive list of loaded buffers. Here,<Leader>
is the default\
key.<Leader>|
: Opens an interactive list ofgit
-tracked files. On aqwerty
keyboard,|
is\
but shifted. So,\\
and\|
are pretty close together and both spawn file/buffer related features.<Leader>tg
: Stands fortelescope
+grep
in my mind. Lets meripgrep
over the codebase I’m in.<Leader>th
: Stands fortelescope
+help
in my mind. Lets me fuzzy find acrossnvim
’s wonderful documentation. Because, yes, RTFM!<Leader>tk
istelescope
+key
. It goes through my user-defined key bindings. Simply because I’ve quite a few and I don’t always remember all.
Configuration
require("telescope").setup({
pickers = {
buffers = {
show_all_buffers = true,
sort_lastused = true,
mappings = {
i = {
["<c-d>"] = "delete_buffer",
}
}
}
}
})
local builtin = require("telescope.builtin")
vim.keymap.set("n", "<Leader><Leader>", builtin.buffers, { desc = "Telescope buffers" })
vim.keymap.set("n", "<Leader>|", builtin.git_files, { desc = "Telescope git files" })
vim.keymap.set("n", "<Leader>tg", builtin.live_grep, { desc = "Telescope live grep" })
vim.keymap.set("n", "<Leader>th", builtin.help_tags, { desc = "Telescope help tags" })
vim.keymap.set("n", "<Leader>tk", builtin.keymaps, { desc = "Telescope keymaps" })
Here, the last part corresponds to the previously detailed shortcuts. The only thing of interest is the first block and it’s picker
custom configuration. What it does is simple: if I’m in the buffer
picker (remember, <Leader><Leader>
or \\
here) then ^D
closes the highlighted buffer. It allows me to easily clean after some codebase exploration.
A completion engine
I like writing code. I mean, I’m a dev. That would not make sense not to. But if the code writes itself, I won’t complain. This is pretty much what a completion engine does: write code faster than me while reducing typos and suggesting what may come next.
nvim-cmp
is the engine. By itself it’s pretty useless. But when given some other plugins it becomes a completion beast.
Installation
{
"hrsh7th/nvim-cmp",
name = "cmp",
event = "InsertEnter",
dependencies = {
{
"L3MON4D3/LuaSnip",
config = function()
require("plugins.luasnip")
end
},
{ "hrsh7th/cmp-buffer", },
{ "hrsh7th/cmp-cmdline", },
{ "hrsh7th/cmp-nvim-lua", },
{ "hrsh7th/cmp-path", },
{ "onsails/lspkind.nvim", name = "lspkind" },
{ "saadparwaiz1/cmp_luasnip" },
},
config = function() require("plugins.cmp") end
}
Wow, look at that dependencies list!
LuaSnip
for code snippets. I won’t go into details here but suffice it to say good bye boiler plate 👋 It goes withcmp_luasnip
later in the list.cmp-buffer
,cmp-cmdline
,cmp-nvim-lua
andcmp-path
tellcmp
to complete based on buffer content, command line possible commands at arguments, file paths etc.lspkind
, well, see the last plugin.
This one’s configuration is too long to be copy-pasted here. But here is what I use. It’s a mouthful and is heavily inspired by nvim-cmp
documentation so fear no magic, it all makes sense in the end.
Language Server Protocol
Last but not least, something to interact with nvim
’s LSP features.
For those who may not know what LSP brings into the game, let me tell you a story:
Once upon a time, multiple text-editor fought during what was later called the Holly War. They all pretended to be better than the others.
At the time, there were many programming languages. And every text-editor tried their best to give users a smooth experience in every single language. This required quite a lot of ad-hoc code and a great deal of energy got wasted in those battle.
One day, M$, got a brilliant idea: why should so many efforts be sent down the drain in vain when every language could be in charge of it own tooling? Even better, why should every text-editor write so many lines of code when those tools can expose their features through a unified API?
A lot of people hate M$ and there is no lack of good reasons to do so. But M$ still gave the world the LSP it needed. And instead of having
M
editors writingN
tool sets resulting inM * N
implementations, the Holly War fighters only have to write1
LS client while the languages makers write the LS server.They lived happily ever after and
nvim
won the Holly War.
Ok, sorry for this digression. It’s late and I can’t sleep so I guess it shows. Let’s head back to the plugins thing.
Nvim-lspconfig
is the GOAT here. Nvim
has native LSP support. But it does not embed opinionated LSP configuration. This, is nvim-lspconfig
’s job. And let me assure you, it rocks at it.
Once, I told myself it’d be great to dive into this and learn the internal of LSP configuration, have a closer-to-my-needs setup and so on. Well, all I did was writing pretty much what was provided by nvim-lspconfig
. So I installed it back on my machine and forgot about it.
Installation
{
"neovim/nvim-lspconfig",
name = "lspconfig",
dependencies = {
{ "hrsh7th/cmp-nvim-lsp" },
{ "simrat39/rust-tools.nvim", name = "rust-tools" },
{ "williamboman/mason-lspconfig.nvim", name = "mason-lspconfig" },
{ "williamboman/mason.nvim", name = "mason" },
},
config = function() require("plugins.lsp-config") end
}
There is a couple of things to note here.
First, I have the cmp-nvim-lsp
plugin for nvim-cmp
integration. I guess it makes sense after what I told about how nvim-cmp
works.
Also, I use mason
and mason-lspconfig
. If you use VSCode
and deserve a front-end developer position in Hell and use its market place, you know what mason
brings to the game: single tap installation and configuration for a great list of LSP, DAP and the like. Check it out, it also acts as an interactive configuration explorer, it’s awesome!
Again, my configuration is too long to be presented here. And it would not make sense to elaborate on my settings in the matter.
Conclusion
I think that’s all.
It’s only a plugin manager and five plugins (and their dependencies of course). But I think it’s the real bare minimum to bootstrap a great coding experience.
For the record, here is the complete list of plugins I use. You’ll find many more plugins than the five presented here and their configurations. But it’d not make any sense to force those onto a nvim
enthusiast according to me.
Mastering a text editor is a journey that never ends. I’ll probably add, remove, tweak many plugins in the coming years. And the best part is I’ll enjoy doing so. First because I’m kind of a vim
nerd but also because it’ll make my life better on a daily basis. And this is also true for emacs
users: we all have custom needs depending of our jobs/hobbies/toy-projects and configure our tools accordingly. This is why I don’t fancy vim
/nvim
/emacs
distributions: I feel like they’re too opinionated and don’t give enough freedom to the end-user.
If you agree with me, don’t hesitate to tell your friends about this blog-post. You can also let me know what you think on mastodon. And if you think I’m wrong and should be silenced, well, please, do share why on here because I’d love to improve and be worthy of you my dear reader!