
Why do we need to keep revisiting this?
The short answer is we don’t.
The long answer is a bit more personal. While I don’t work as a developer anymore, I do still have to work with code occasionally, and I still, for whatever reason, enjoy tinkering with my tools. I find that talking about and tinkering with CLI tools is a lot of fun, and one of my favorite tools is Neovim. Every now and then I’ll re-evaluate my setup and try to improve on my previous config by setting up a new one from scratch, keeping what I felt worked well in the previous setup, and adding new things that I think will either improve on tools I already use or which will streamline a workflow that I didn’t previously have. And yes, believe it or not, as your career advances your workflow will also continue to change. Yet another reason occasional re-evaluation is a good thing.
To potentially save people some time, I want to mention that this article is not intended for people new to Vim or NeoVim. I assume that you have some previous knowledge of vim/vim and either have experience with Lua or, at the least, no aversion to it.
Neovim vs Vim
I wanted to get this out of the way real quick because there will probably be some confusion if you’re not familiar with the history. Neovim is a fork of Vim that has several notable improvements. The core functionality is compatible with Vim, e.g., if you aliased a bare version of vim
with a bare version nvim
, a user opening the editor would never realize they were using NeoVim.
The notable improvements mentioned above are based on aggressive refactoring giving the following benefits:
Overall performance improvements
Using the XDG Base Directory standard for configuration instead of hardcoded directories
A more sophisticated plugin API that allows the use of RPC calls and Lua for plugin creation and scripting (Vimscript v1 is also supported)
Native support of LSP
Support for asynchronous I/O
I switched between the two editors without much thought for years but decided to finally commit to Nvim in June of 2022 when the decision was made to essentially re-implement vimscript from scratch instead of simply embracing an already existing language like Lua or Python. Are you going to re-invent a wheel that is already notoriously quirky and only usable in a single tool? That was when I decided just to embrace NeoVim.
On with the Configuration
Before we start a quick configuration note; nvim supports init.vim
or init.lua
as the default configuration file, but not both. If you’re going to commit to Nvim, you probably want to use init.lua
, and since I do, I will.
I’ve always been kind of scattered when it comes to plugin management but I’d like to find a single solution and stick to it. Since I’m all in for learning new things, I’ll be starting off with a clean slate and using Lazy.nvim as my plugin manager. It’s still relatively new (and I’m new to it) but I’ve read good things about it, it embraces lazy loading of plugins which is nice, although load time has never been a real issue for me to be honest, and the default plugins it installs make for a fascinating user experience. There was a lot of exploration there.
One of the things I truly appreciate is that the instructions for installation start off by telling you to make a backup of your existing configuration and then show you how to do it. Thanks, buddy!
After following the install instructions and opening nvim, you’ll be visually assaulted by a bunch of scrolling text as lazy.nvim downloads all of the default plugins and installs them. When that finally ends you’ll see something like this that shows you the total number of plugins, whats’ installed and what’s currently loaded:
I’ll admit, I was a bit startled at first. It clearly just left me in a buffer displaying the lazy.nvim install plugin management interface as indicated by the menu at the top. While it was not what I was expecting, it was a simple and effective menu. The ?
action gives some useful context on what was going on.
When I hit :q
I’d intuitively half expected to quit nvim, but instead, it dumped me to the default lazy.nvim popup interface.
This is what you would see if you exit nvim and start it again. Clearly, the intent is to have a single plugin manager that, by default, turns nvim into an IDE any user of VS Code or IntelliJ would feel comfortable with. I would also add that I’d prefer it didn't have the giant modal proclaiming that it was Lazyvim on startup but I can’t really blame it, the resulting product is pretty nice.
The documentation for lazy.nvim is pretty good and after some reading and research, I found a more simplistic approach to using lazy.nvim that felt more like what I was after; using the plugin manager as a plugin manager and ignoring its opinion of what my IDE should be.
Simplistic Approach
I deleted the nvim directory and everything in it (remember not to delete your backup of the original configuration) starting over from scratch. Overkill perhaps but I like a clean slate approach, it helps make things more reproducible.
As I said earlier, my usual approach tends to look like a Frankenstein hodgepodge of configuration files, so I’m going to try and be less haphazard about my config and try to impose some structure on it from the start. I eventually decided on a pretty standard-looking folder structure as shown below.
I think it’s reasonably clear. The user
directory is what I’ve chosen to name the directory where I’ll store my user-specific Lua scripts, and the user/plugins
directory is where the plugins will go. I could have named the directory after my OS username, which is what most people do. I chose this just to be different. I suppose for a multi-user scenario, it would make sense to have the individual user directories in there, e.g., user/user.name
but I don’t think most people use that kind of setup and if I’m honest, I was just lazy and wanted to be different.
Below is my base levelinit.lua
in the root lua
directory. The lazy.lua
script is in the root plugin directory. It puts the fzf
executable into the runtime path, loads my preferences for options (things like line numbers, tab stops, etc.), loads the lazy.vim settings (more on that later), and finally create an auto-command that lazy loads keymaps and user-defined auto-commands:
-- Set runtime path to include fzf
vim.opt.rtp:prepend("/opt/homebrew/opt/fzf")
-- Load options file
require("user.options")
require("user.lazy")
vim.api.nvim_create_autocmd("User", {
pattern = "VeryLazy",
callback = function()
require "user.autocmds"
require "user.keymaps"
end,
})
A couple of notes here — I load the options ahead of everything else because I keep my leader key in there and I want that mapped so that any subsequent plugins, autocommands, etc., get mapped correctly; I also don’t know that the lazy loading of auto-commands or keymaps is all that useful, but it was keeping with the theme so I went with it.
The current lazy.lua
file contains code that pulls the latest stable copy of lazy.nvim when needed, adds lazy.nvim to the runtime path, and lastly tells lazy where the import directories are located (it doesn’t import nested directories under lua
before setting a couple of options on what to do regarding updates.
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({ {import = "user.plugins"}, {import = "user.plugins.lsp"} }, {
checker = {
enabled = true,
notify = false,
},
change_detection = {
notify = false,
},
})
Now when I start nvim it will start as it normally would I can use the command :Lazy
to bring up the new plugin manager interface when needed.
The Plugins
The plugins listed in the screenshot are the ones that I’m currently using and have configured. A quick note on lazy; I was pleasantly surprised that when I created a new plugin file and saved it, lazy picked up the change and loaded the configuration.
Now that I’m embracing a Lua-only setup with nvim, I needed to transfer all of my vimScript stuff to Lua. I’m lazy and don’t like typing so I shamelessly stole my coworkers options.lua
and keymaps.lua
as a starting point to minimize effort. I also added a autocmds.lua
file for any autocommands I’ll want to set up.
I set up nvim-tree as my file explorer, which is new to me. I traditionally haven’t used a file explorer. I also configured a nerd font to provide widgets.
I’ve got an init.lua
for the plugins directory to load plugins that I don’t need to provide any specific configuration for and I’ve moved the color scheme into its own file. The lazy.lua
file handles the configuration of the plugin manager specifically, as shown above. In addition to the init.lua
file for simple plugins, there are individual files for plugins that require configuration to meet your needs.
There was more work involved than I’d hoped in migrating to a pure Lua configuration, but there are a ton of examples out there and the Neovim documentation is pretty good.
You can also see that I moved the lsp configuration into its own directory. The lspconfig.lua
belongs to the nvim-lspconfig
plugin which contains configurations for the nvim lsp client. It can get quite large so I’m thinking about moving out the configuration for individual languages into their own files. The other plugin there is mason
which is used to configure external tools like linters, formatters, etc., through a single interface.
Of the less specialized plugins themselves, there are only a few interesting items. I’ve never used dressing, tree-sitter, telescope, or which-key. I saw dressing looking around at nvim plugins and liked the way it looked so I thought I’d try it, nothing really functional was added there.
I’ve heard good things about tree-sitter and the plugins that wrap it, and other nvim users that I respect use it, so I thought I’d try that and I like the idea of some additional text objects.
People have been raving about telescope for years. I’ve used fzf for years and have been completely satisfied with it as a tool for finding files, but I’ve been working almost entirely with familiar code bases. It was postulated to me that for getting around large, unfamiliar code bases, fzf is as much hindrance as help. This seems reasonable so I thought I’d give it a shot and give me an excuse to start looking at new codebases. I still have fzf.vim configured though because I love using it to find buffers. I may change my mind later and go all in on telescope. We’ll see.
I’ve never used which-key either but I figured with all of the new plugins, something that gives me a menu for keystrokes will be helpful until I have them in muscle memory.
These were just my ramblings on re-configuring my nvim setup. Let me know if you have any questions, recommendations, or comments.