Aug 12, 2024
In the eternal search of a better text editor, I’ve recently gone back to Neovim. I’ve taken the time to configure it myself, with as few plugins and other cruft as possible. My goal is a minimalist editing experience, tailored for exactly those tasks that I do regularly, and nothing more. In this post, I’ll give a brief tour of my setup and its motivations.
Over the years, I’ve been through a bunch of editors. Here are most of them, in roughly chronological order:
The majority were used specifically for the work I was doing at the time. VS Code for web development, IntelliJ IDEA for JVM languages, Emacs for Lisps. Vim and Emacs have been the most generally applicable, and the ones that I’ve enjoyed the most.
I’ve also evaluated Zed recently, but it hasn’t quite stuck. The
start-up time and responsiveness is impressive. However, it seems to
insist on being a global application. I want my editor to be
local to each project, with the correct PATH
from direnv,
and I want multiple isolated instances to run simultaneously. Maybe I’ll
revisit it later.
Yeah, so I’m back in Neovim. I actually started with the LazyVim distribution based on recommendation. On the positive side, it got me motivated to use Neovim again. But I had some frustrations with the distribution.
The start-up time wasn’t great. I guess it did some lazy loading of plugins to speed things up, but the experience was still that of an IDE taking its time to get ready. Not the Neovim experience I was hoping for.
More importantly, it was full of distractions; popups, status messages, news, and plugins I didn’t need. I guess it takes a batteries-included approach. That might make sense for beginners and those just getting into Neovim, but I realized quickly that I wanted something different.
Supposedly I could strip things out, but instead I decided to start from scratch and build the editor I wanted. One that I understand. Joran Dirk Greef talks about two different types of artists, sculptors and painters, and this is an exercise in painting.
More concretely, my main goals are:
I want to keep plugins to an absolute minimum. My editor is meant for coding and writing, and for what I work on right now. I might add or remove plugins and configuration over time, and that’s fine.
It should be as minimalist as I can make it. Visual distractions kept at a minimum. This includes busy colorschemes, which I find add little value. A basic set of typographical conventions in monochrome works well for me.
With the way I use Neovim, it needs to start fast. I quit and start it all the time, jumping between directories, working on different parts of the system or a project. Often I put it in the background with C-z, but not always. Making it faster seems to be mainly an exercise in minimizing plugins.
I manage dotfiles and other personal configuration using Nix and home-manager. The Neovim configuration is no exception, so I’ll include some of the Nix bits as well.
The vim.nix
module declares that I want Neovim installed
and exposed as vim
:
{
programs.neovim = enable = true;
vimAlias = true;
...};
In that attribute set, there are two other important parts; the
plugins
list and the extraConfig
.
Let’s start with the plugins:
with pkgs.vimPlugins; [
plugins =
nvim-lspconfig(nvim-treesitter.withPlugins(p: [
p.bash
p.json
p.lua
p.markdown
p.nix
p.python
p.rust
p.zig
p.vimdoc]))
conform-nvim
neogit
fzf-vim];
Basically it’s five plugins, not counting the various treesitter parsers:
lspconfig
plugins helps with
configuring Neovim for use with various servers.
:%!whatever-formatter
, but I’d rather have the
editor do it for me.
:Files
and :GFiles
, as quick jump-to-file
commands (think C-p
in VS Code or Zed). They are bound to
<Leader>ff
and <Leader>gf
,
respectively. This might be another plugin I could do without, writing a
small helper around fzf
, or just making do with
:find
and **/
wildcards. On the other hand,
I’m trying out :Buffers
instead of stumbling around with
:bnext
and :bprev
.
One great thing with the Nix setup is I don’t need a package manager in Neovim itself.
Many things I don’t need plugins for. For instance, there are a ton of plugins for auto-completion, but Neovim has most of that built in, and I prefer triggering it manually:
C-x C-f
C-Space
in my case)C-x C-n
C-x C-s
or
z=
I’ve tried various snippet engines many times, but not found them very useful. Most of my time is spent reading or modifying existing code, not churning out new boilerplate. Instead they tend to clutter the auto-completion list. Snippets might make more sense for things like HTML, but I don’t write HTML often, and in that case I’d prefer some emmet/zen-coding plugin.
You can get great mileage from learning how to use the Quickfix list. I’m no expert, but I prefer investing in composable primitives that I can reuse in different ways. Project-wide search-and-replace is such an example:
:grep whatever
:cfdo s/whatever/something else/g | update
:cfdo :bd
Here we search (:grep
, which I’ve configured to use
rg
), substitute and save each file, and delete those
buffers afterwards.
I also use :make
and :compiler
a lot.
Neovim is cool.
Maybe I’m just growing old, but I prefer a monochrome colorscheme.
Right now I’m using the built-in quiet
scheme with a few
extra tweaks:
set termguicolors
set bg=dark
colorscheme quiet
highlight Keyword gui=bold
highlight Comment gui=italic
highlight Constant guifg=#999999
highlight NormalFloat guibg=#333333
It’s black-and-white, but keywords are bold, comments are darker and italic, and literals are light gray. Here’s how it looks with some Zig code:
Maybe I’m coming off as nostalgic or conservative, but I do find it more readable this way.
Another thing I’m going to try soon is writing on the Daylight Computer, hopefully in Neovim. Being comfortable with a monochrome colorscheme should come in handy.
My config uses a Vimscript entrypoint (extraConfig
in
the Nix code). This part is based on my near-immortal config from the
good old Vim days. Early on, it calls vim.loader.enable()
to improve startup time. I use Lua scripts for configuring the plugins
and related keymap bindings. Maybe everything could be Lua, but I
haven’t gotten that far yet. However, it’s nice to have the base config
somewhat portable; I can just copy-paste it onto a server or some other
temporary environment and have a decent /usr/bin/vim
experience.
You’ll find the full configuration in nix.vim and the Lua bits inside the vim/ directory.
That’s about it! I’m really happy with how fast and minimalistic it is. It starts in just above 100ms. And I can understand all of my configuration (even if I don’t understand all of Neovim.) Perhaps I’ve spent more time on it than I’ve saved, but at least I’m happy so far.
I’m writing and publishing this on my birthday. What a treat to find time to blog on such an occasion!