January 2024 Editor Update

I decided to take some time over the holidays to redo my Neovim configuration, which I last spent any meaningful time on in 2019. I've been going back and forth on editors a lot for the last few years; last year, I spent the first six months of the year using Helix, which is a batteries-included editor based on the Kakoune editing model1. I gave up on Helix after one too many crashes, and went back to Neovim.

The main goals of my recent config rewrite were:

  1. Move from init.vim to init.lua
  2. Get rid of unused/underused plugins
  3. Move to LSP + Tree-sitter, for real

All of these things were successful! My new init.lua is about the same size as the old init.vim2, but has about 20% fewer plugins and a lot more comments. I also switched from vim-plug to lazy.vim, which slightly improved startup time3 and provided a slightly more ergonomic way to configure stuff.

Here's a quick screenshot showing my solution for Advent of Code 2023 Day 25, in Neovim in iTerm 2:

Neovim in iTerm 2

Read on for more fun and exciting4 details.


I'm now using 38 plugins, and I figured I'd do a quick overview of each of them!

WARNING: Vim plugins can execute arbitrary code as your user on your system. There is no sandboxing in any version of Vim that I'm aware of. Don't add a plugin if you aren't comfortable with the maintainer potentially having shell on your system. At the very least, look at the source and read the diffs before just blindly installing it. Eventually someone high-profile is going to be breached through a vim plugin and it's going to be sad for all of us.

Improves UI when making changes using Code Actions from a LSP. Unfortunately, none of the LSPs I use support code actions.

A cool new buffer switcher; usually can guess which buffers I want to toggle between. I have this bound to <space>b


Adds :BDelete ex commands for bulk-closing buffers. In particular, :BDelete other closes everything except the open buffer, which is something I want to do sometimes when changing contexts.


A new autoformatter that came out last year. Replaced all the other formatters with it (some of which were custom autocmds and some of which were neomake rules and some of which were plugins).


Adds new text object sf (for "surrounding function call") for changing the function call when inside an argument list. This can probably be done with treesitter-textobjects now...


Parses .editorconfig files to automatically apply things like indentation rules for a project.


A mildly-improved version of fzf.vim, a wrapper around the excellent fzf fuzzy finder. Has a strange bug where sometimes if you type a p too fast while it's loading, it searches for your unnamed register instead of searching for the letter "p".

Plugin manager

Statusline plugin (replacing the previous lightline). I keep it pretty minimal.


A colorscheme. I have a bunch of annoying highlight rules to override it because I'm too lazy to fork it and make my own changes. I'm still mixed on this, and go back and forth with just using default and manging colors in my terminal emulator.

UI component library. Only a dependency of other plugins.

Configuration rules for Language Server Protocol configs. This is a super-verbose plugin and I bet there's a better way to do it.

In terms of LSPs, I'm using

rust-analyzer is pretty good; the rest of them are... okay...


This plugin lets me use tree-sitter for highlighting and indenting, which is way more reliable than regular expression based syntax rules. Helix, for what it's worth, exclusively uses tree-sitter.


Adds some text objects for selecting functions and whatnot with treesitter.


Adds an interactive quick-picker for jumping between splits in the current tab.


Adds a new command :Other which lets you open a related file. I spent a lot of time in Rails applications now, and they tend to make you jump between a lot of related files (controller, view, model, specs for each, etc), and this plugin is super-useful for opening these files.


Adds gS and gJ commands to improve joining and splitting lines. Super-helpful in Ruby when you want to change

next if foo.has.some.long.condition


if foo.has.some.long.condition

Feels kind of gross to have to do that all the time, but, hey, that's Ruby for ya.

Visualize the undo tree. Can do other stuff, but I don't use anything else.
Adds some mappings and text objects for manipulating function arguments. Can probably be replaced by treesitter-textobjects. Maybe next year!
Show currently-open buffers in a similar UI to tabs.

Improves the ga command to handle UTF-8.


Adds gc command to toggle comments.


I hate code styles that require aligning around the middle (e.g., aligning a bunch of assignments around the = signs), but sometimes you have to interact with them, and :EasyAlign makes that much less annoying.

Does anyone not use this plugin

Adds text objects based on indentation levels (ai and ii). Mostly I only use this in Python.


Adds motions based on indentation levels ([-, ]-, [+, ]+, etc). Mostly I only use this in Python.


Adds support for Justfiles. This will probably be mainline soon and then I can drop it.

Absolutely essential plugin that previews the contents of registers. I don't know how I ever used them without it.
Marginally improves vim support for Puppet manifests.

Adds utilities for finding the root directory of the current project. I use the FindRootDirectory() function from this plugin in lots of autocmds and things. I prefer to have autochdir enabled, and then have various mappings that use FindRootDirectory to operate relative to the project root. I made a dumb little CLI tool called projroot which I use to do the same thing from my shell.

Improves vim support for Ruby.

Adds the :Sayonara command, which is a better :bdelete. I have it bound to <leader>bq.


Adds a mapping to sort a range. Is this better than '<,'>r!sort ? Who knows.

Profiling plugin. Added to write this blog post. Does it count?

Another very old and quintessential vim plugin. Change surrounding quotes/brackets/braces. Built in to many editors. I use things like cs"' all the time. I usually forget about the more sophisticated features, though.


I probably added this 10 years ago. I don't know if I use any of the mappings it provides.

Some improvements to the netrw browser from Tim Pope.

Adds some fancy integration between neovim and zellij (which is a modern screen/tmux alternative).

Other Random Settings

Here are some of my favorite other settings, many of which now actually have comments explaining what they do.

-- show both numbers and relative numbers
vim.opt.number = true
vim.opt.relativenumber = true
-- highlight matching brackets when you type a closing one
vim.opt.showmatch = true
-- disable redrawing during macros
vim.opt.lazyredraw = true
-- tweak some completions
vim.opt.wildmode = "list:longest,full"

vim.keymap.set("i", "jk", "<ESC>")
vim.keymap.set("i", "kj", "<ESC>")
vim.keymap.set("n", "<space><space>", "<c-^>")
-- use arrow keys for manipulating buffers instead
vim.keymap.set("n", "<up>", ":buffers<cr>:buffer")
vim.keymap.set("n", "<down>", ":b#<cr>")
vim.keymap.set("n", "<left>", ":bp<cr>", { noremap = true, silent = true })
vim.keymap.set("n", "<right>", ":bn<cr>", { noremap = true, silent = true })
-- the diagnostic list is the "modern" replacement for the quickfix list, but has no default
-- bindings to navigate it???
vim.keymap.set("n", "[d", vim.diagnostic.goto_prev)
vim.keymap.set("n", "]d", vim.diagnostic.goto_next)

-- default indentation settings; overridden for many filetypes elsewhere in the config
vim.opt.shiftwidth = 4
vim.opt.softtabstop = 0
vim.opt.tabstop = 4
vim.opt.autoindent = true
vim.opt.expandtab = true

-- assume the /g flag on :s substitutions to replace all matches in a line:
vim.opt.gdefault = true

-- show hidden characters
vim.opt.list = true
vim.opt.listchars = "trail:~,tab:┊─,nbsp:␣,extends:◣,precedes:◢"
vim.opt.backspace = "indent,eol,start"

vim.opt.mouse = "a"

-- lualine handles this
vim.opt.showmode = false

-- default fold settings; overridden for many file types
vim.opt.foldcolumn = "1"
vim.opt.foldminlines = 10
vim.opt.foldenable = false

vim.opt.hidden = true

vim.g.netrw_liststyle = "3"
vim.g.netrw_list_hide = ".*\\.swp$,.*\\.DS_Store"
vim.cmd([[ autocmd FileType netrw setl bufhidden=wipe]])

-- change default for splits
vim.opt.splitright = true
vim.opt.diffopt = "filler,internal,algorithm:histogram,indent-heuristic"

if vim.fn.executable("rg") then
  vim.opt.grepprg = "rg --vimgrep --no-heading"

-- select last visual selection
vim.keymap.set("n", "gy", "`[v`]")

-- highlight yanks
vim.api.nvim_create_autocmd("TextYankPost", {
  callback = function()

vim.diagnostic.config({ virtual_text = true, signs = true })

What's Next?

A few remaining irritations:

  • I don't like the way diagnostics work; I really like the quickfix list. I was using diaglist to try to merge them for a while, but it gets stale and can only be fixed by entirely quitting the editor. I think trouble.nvim is the popular plugin for doing this now, so I should probably investigate it.
  • Need to spend more time looking at colorschemes...
  • It would be nice to have a GUI neovim client that integrates with macOS a little better. I go through phases of using VimR (which was stuck on an out-of-date neovim binary for a very long time); I also tried using Neovide for a bit (which has fun animations but very little system integration).

Put simply: in vim/neovim/etc, commands typically take the form verb count noun like d3w to delete the next 3 words; in helix, they take the form count noun verb (like 3wd to delete three words), and take advantage of this to show a preview of what changes will be made. vim also heavily relies on ex commands (like :substitute) to make bulk changes, whereas helix favors multiple cursors/selections.


685 lines, down from 746 in the old init.vim


From 389ms to 291ms


For very small values of "fun" and "exciting"

Want to comment on this? How about we talk on Mastodon instead? mastodon logo Share on Mastodon