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:

  1. Install the nvim-treesitter repository from the eponymous github user,
  2. Name it treesitter instead of nvim-treesitter. This is completely optional. I just don’t like having all those nvim-* scattered across my configuration.
  3. 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:

  1. Fetch the plugin,
  2. Give it a sensible name,
  3. 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 of git-tracked files. On a qwerty keyboard, | is \ but shifted. So, \\ and \| are pretty close together and both spawn file/buffer related features.
  • <Leader>tg: Stands for telescope + grep in my mind. Lets me ripgrep over the codebase I’m in.
  • <Leader>th: Stands for telescope + help in my mind. Lets me fuzzy find across nvim’s wonderful documentation. Because, yes, RTFM!
  • <Leader>tk is telescope + 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 with cmp_luasnip later in the list.
  • cmp-buffer, cmp-cmdline, cmp-nvim-lua and cmp-path tell cmp 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 writing N tool sets resulting in M * N implementations, the Holly War fighters only have to write 1 LS client while the languages makers write the LS server.

They lived happily ever after and nvim won the Holly War.

Unknown, circa who knows

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!