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:
- Move from
init.vim
toinit.lua
- Get rid of unused/underused plugins
- 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.vim
2, 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:
Read on for more fun and exciting4 details.
Plugins
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.
- actions-preview.nvim
- Improves UI when making changes using Code Actions from a LSP. Unfortunately, none of the LSPs I use support code actions.
- arena.nvim
-
A cool new buffer switcher; usually can guess which buffers I want to toggle between. I have this bound to
<space>b
- close-buffers.nvim
-
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. - conform.nvim
-
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). - dsf.vim
-
Adds new text object
sf
(for "surrounding function call") for changing the function call when inside an argument list. This can probably be done withtreesitter-textobjects
now... - editorconfig-vim
-
Parses
.editorconfig
files to automatically apply things like indentation rules for a project. - fzf-lua
-
A mildly-improved version of
fzf.vim
, a wrapper around the excellent fzf fuzzy finder. Has a strange bug where sometimes if you type ap
too fast while it's loading, it searches for your unnamed register instead of searching for the letter "p". - lazy.nvim
- Plugin manager
- lualine
-
Statusline plugin (replacing the previous lightline). I keep it pretty minimal.
- night-owl
-
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 usingdefault
and manging colors in my terminal emulator. - nui.nvim
- UI component library. Only a dependency of other plugins.
- nvim-lspconfig
-
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
- ruby-lsp -- SO MUCH less crashy than solargraph
- python-lsp-server
- marksman
- lua-language-server
rust-analyzer
is pretty good; the rest of them are... okay... - nvim-treesitter
-
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.
- nvim-treesitter-textobjects
-
Adds some text objects for selecting functions and whatnot with treesitter.
- nvim-window
-
Adds an interactive quick-picker for jumping between splits in the current tab.
- other.nvim
-
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. - splitjoin
-
Adds
gS
andgJ
commands to improve joining and splitting lines. Super-helpful in Ruby when you want to changenext if foo.has.some.long.condition
into
if foo.has.some.long.condition next end
Feels kind of gross to have to do that all the time, but, hey, that's Ruby for ya.
- undotree
- Visualize the undo tree. Can do other stuff, but I don't use anything else.
- vim-argumentative
- Adds some mappings and text objects for manipulating function arguments. Can probably be replaced by treesitter-textobjects. Maybe next year!
- vim-buftabline
- Show currently-open buffers in a similar UI to tabs.
- vim-characterize
-
Improves the
ga
command to handle UTF-8. - vim-commentary
-
Adds
gc
command to toggle comments. - vim-easy-align
-
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. - vim-fugitive
- Does anyone not use this plugin
- vim-indent-object
-
Adds text objects based on indentation levels (
ai
andii
). Mostly I only use this in Python. - vim-indentwise
-
Adds motions based on indentation levels (
[-
,]-
,[+
,]+
, etc). Mostly I only use this in Python. - vim-just
-
Adds support for Justfiles. This will probably be mainline soon and then I can drop it.
- vim-peekaboo
- Absolutely essential plugin that previews the contents of registers. I don't know how I ever used them without it.
- vim-puppet
- Marginally improves vim support for Puppet manifests.
- vim-rooter
-
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 haveautochdir
enabled, and then have various mappings that useFindRootDirectory
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. - vim-ruby
- Improves vim support for Ruby.
- vim-sayonara
-
Adds the
:Sayonara
command, which is a better:bdelete
. I have it bound to<leader>bq
. - vim-sort-motion
-
Adds a mapping to sort a range. Is this better than
'<,'>r!sort
? Who knows. - vim-startuptime
- Profiling plugin. Added to write this blog post. Does it count?
- vim-surround
-
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. - vim-unimpaired
-
I probably added this 10 years ago. I don't know if I use any of the mappings it provides.
- vim-vinegar
- Some improvements to the netrw browser from Tim Pope.
- zellij.nvim
-
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.wildignore:append("*/tmp/*,*.so,*.swp,*.zip,*.pyc,*.o,*/target/*")
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"
end
-- select last visual selection
vim.keymap.set("n", "gy", "`[v`]")
-- highlight yanks
vim.api.nvim_create_autocmd("TextYankPost", {
callback = function()
vim.highlight.on_yank()
end,
})
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? Share on Mastodon