Skip to main content

NeoVim: reconfigured, reworked and re-headached

 Remember my Neovim quickstart guide? The one with 18 plugins, a Lua file called init.lua that wasn't actually the init file, and a <C-f> mapping to FZF that I, a professional, never installed? Yeah. Neovim 0.12 came out and made about two thirds of that config obsolete. So this post does four things: it revisits what changed, reconfigures everything from scratch, reworks half my plugins into built-in features, and, naturally, re-headaches me in the process. I kept count. You'll see.

Neovim 0.12 ships a plugin manager, as-you-type completion, a one-liner LSP setup, an undo tree, a diff tool and a decent statusline out of the box. My config went from 18 plugins to 3, and it does more than before. The whole thing is below, tested on Neovim 0.12, ready for you to steal. Every red box titled Re-headache is a wall I hit while testing it, so you don't have to.

Revisited: what on earth changed?

 Short answer: Neovim kept eating plugins. Long answer: over the last two releases the core team took the stuff everybody installs and baked it into the editor. Here's what happened to my old setup:

Every plugin on the left was in the 2024 config. Every thing on the right ships with Neovim 0.12.

 And the best thing is that the old config doesn't just look outdated, it straight up errors.

Remember the yamlls hack that called vim.diagnostic.disable() so Helm templates wouldn't scream at you? That function was deprecated in 0.10 and removed in 0.12. So if you copied my old config (you pesky thief), it's been quietly rotting on your disk. The old guide still works on 0.10, nags you on 0.11 and breaks on 0.12. You're welcome.

What you need

 First, make sure you're actually on 0.12. Your distro might be a bit behind, especially if you're on one of those "stable" distros that consider 2023 to be the cutting edge.

$ nvim --version | head -1
NVIM v0.12.2

 Then the usual suspects. On Arch it's mostly one command now, since half of the language servers made it into the official repos since last time:

sudo pacman -S neovim git gcc tree-sitter-cli ripgrep \
               gopls pyright clang rust-analyzer terraform yaml-language-server
yay -S terraform-ls helm-ls-bin jdtls
PackageWhy you want it
gitThe new built-in plugin manager uses it to download plugins
tree-sitter-cli + gccThe new nvim-treesitter compiles parsers locally. Install it from your package manager, not npm
ripgrepMakes the fuzzy file finder fast (optional, there's a fallback)
gopls, pyright, clangd…The language servers. Install only the ones you need, the config skips the rest

The rewritten nvim-treesitter doesn't download ready-made parsers anymore, it compiles them. Skip tree-sitter-cli and your first launch greets you with fifteen "ENOENT: tree-sitter" errors in a row, one per parser. Fifteen. I counted, because that's what this post is about now.

Back up your old config before trying this. Future you will thank present you.

mv ~/.config/nvim ~/.config/nvim.bak
mv ~/.local/share/nvim ~/.local/share/nvim.bak

Reconfigured: directory setup

 Last time we had an init.vim plus a plugged/ folder that grew like mold. This time everything is Lua, and the plugin folder isn't even in your config directory anymore: Neovim keeps it in ~/.local/share/nvim/site/pack/core/opt, where you'll never have to look at it.

~/.config/nvim
├── init.lua               ← the entry point (yes, the REAL one this time)
├── nvim-pack-lock.json    ← generated, pins plugin versions
├── after/
│   └── lsp/
│       ├── gopls.lua      ← per-server tweaks
│       ├── helm_ls.lua
│       └── yamlls.lua
└── lua/
    ├── options.lua
    ├── plugins.lua
    ├── lsp.lua
    ├── completion.lua
    ├── treesitter.lua
    └── keymaps.lua
mkdir -p ~/.config/nvim/lua ~/.config/nvim/after/lsp

 Notice something missing? There's no step to install a plugin manager. No curl-ing vim-plug into ~/.local/share/nvim/site/autoload/ like last time (and yes, vim-plug's file is called plug.vim even on Neovim, which confused me too while writing this). The plugin manager is already there. I'm almost sad, I had grown fond of that command.

The entry point: init.lua

 This file does nothing but load the others, in order. Keep it boring.

-- ~/.config/nvim/init.lua
-- Leader goes first, before anything maps with it
vim.g.mapleader = ' '

require('options')
require('plugins')
require('lsp')
require('completion')
require('treesitter')
require('keymaps')

 Each require('something') looks for lua/something.lua. No extension, no path. If you get a module not found error, it's a typo in the file name. It's always a typo in the file name.

Options

-- General settings
local o = vim.opt

o.number = true
o.relativenumber = true       -- jump with 5j / 12k like a pro
o.cursorline = true
o.signcolumn = 'yes'          -- no more text jumping when diagnostics appear
o.scrolloff = 8               -- keep 8 lines visible above/below the cursor
o.mouse = 'a'
o.clipboard = 'unnamedplus'   -- share the system clipboard
o.ignorecase = true
o.smartcase = true            -- ...unless you type a capital letter
o.tabstop = 2
o.softtabstop = 2
o.shiftwidth = 2
o.expandtab = true
o.undofile = true             -- undo history survives closing the file
o.splitright = true
o.splitbelow = true
o.updatetime = 250
o.winborder = 'rounded'       -- rounded borders on every floating window
o.pumborder = 'rounded'       -- ...and on the completion menu (new in 0.12)
o.pummaxwidth = 50            -- keep the menu narrow so docs fit beside it (new in 0.12)
o.foldlevelstart = 99         -- open files unfolded

vim.diagnostic.config({
  virtual_text = true,
  severity_sort = true,
  float = { source = true },
})

 If you compare it with the old one, you'll notice a bunch of lines went missing: nocompatible, ttyfast, syntax on, filetype plugin indent on, hlsearch, incsearch… That's because they've been the default in Neovim for years. I was basically telling my editor to do things it was already doing. Very DevOps of me.

 The new ones worth pointing out:

  • winborder gives every floating window (hover docs, diagnostics, signature help) a rounded border. One option, no plugin.
  • pumborder and pummaxwidth are new in 0.12. Borders on the completion menu, and a width cap so long function signatures don't push the documentation popup off the screen.
  • undofile keeps your undo history after you close a file. You'll see why that's great in a minute.

Without pummaxwidth, Go's long function signatures make the completion menu as wide as the screen, and the documentation popup gets crammed into a 10-character strip on the left, right on top of your line numbers. It looked like a ransom note. One option, problem gone.

Plugins, the built-in way: vim.pack

 This is the headline feature. Neovim 0.12 ships vim.pack, a plugin manager that lives inside the editor. You give it a list of Git URLs, it clones them, loads them, and writes a lockfile. That's it. That's the whole API you need on day one.

-- Plugins, managed by the built-in vim.pack (no vim-plug needed)
local gh = function(repo) return 'https://github.com/' .. repo end

-- Keep treesitter parsers in sync whenever the plugin is installed or updated
vim.api.nvim_create_autocmd('PackChanged', {
  callback = function(ev)
    local name, kind = ev.data.spec.name, ev.data.kind
    if name == 'nvim-treesitter' and (kind == 'install' or kind == 'update') then
      if not ev.data.active then vim.cmd.packadd('nvim-treesitter') end
      vim.cmd('TSUpdate')
    end
  end,
})

vim.pack.add({
  gh('AlexvZyl/nordic.nvim'),                                -- colorscheme
  gh('neovim/nvim-lspconfig'),                               -- server configs
  { src = gh('nvim-treesitter/nvim-treesitter'), version = 'main' },
})

-- Opt-in plugins that ship with Neovim itself
vim.cmd.packadd('nvim.undotree')   -- :Undotree
vim.cmd.packadd('nvim.difftool')   -- :DiffTool

vim.cmd.colorscheme('nordic')

 Three plugins. Three. Let's go through them:

  • nordic.nvim: the colorscheme. Purely cosmetic, swap it for whatever you like.
  • nvim-lspconfig: notice we never call require('lspconfig') anymore (that's deprecated now). The plugin just ships ready-made server configs in an lsp/ folder, and Neovim picks them up on its own. More on that below.
  • nvim-treesitter, pinned to the main branch: this is a complete rewrite of the plugin and it is not compatible with the old setup. The old require'nvim-treesitter.configs'.setup{} is gone.

 The PackChanged autocommand is a hook: whenever nvim-treesitter is installed or updated, it rebuilds the parsers so they match the new version. It's the vim.pack equivalent of vim-plug's {'do': ':TSUpdate'}.

Neovim 0.12 ships :Undotree and :DiffTool, but they're opt-in, and nothing tells you. Type :Undotree on a fresh install and you get "E492: Not an editor command", which is a fun way to find out the feature you read about in the release notes is, technically, there. That's what the two packadd lines in plugins.lua are for.

Updating and removing plugins

 To update everything, run this from inside Neovim:

:lua vim.pack.update()

 It opens a tab listing every pending change, commit by commit. Read it (or pretend to), then :w to apply or :q to cancel. To remove a plugin, delete it from the list, :restart, and run :lua vim.pack.del({ 'plugin-name' }).

nvim-pack-lock.json records the exact commit of every plugin. If you keep your dotfiles in Git, commit it, and your config will install the exact same versions on every machine. No more "works on my laptop".

LSP: the part that got ridiculously simple

 Remember the giant lsp.lua with lspconfig.gopls.setup{}, lspconfig.pyright.setup{} and so on, one block per language? Here's the 2026 version:

-- Language servers: config name -> executable it needs
local servers = {
  gopls         = 'gopls',
  pyright       = 'pyright-langserver',
  clangd        = 'clangd',
  rust_analyzer = 'rust-analyzer',
  jdtls         = 'jdtls',
  terraformls   = 'terraform-ls',
  helm_ls       = 'helm_ls',
  yamlls        = 'yaml-language-server',
}

-- Only enable the ones actually installed, so missing servers don't nag you
for name, bin in pairs(servers) do
  if vim.fn.executable(bin) == 1 then
    vim.lsp.enable(name)
  end
end

-- Helm detection (this replaces the whole vim-helm plugin)
local function in_chart(path)
  return vim.fs.root(path, 'Chart.yaml') ~= nil
end

vim.filetype.add({
  pattern = {
    ['.*/templates/.*%.ya?ml'] = function(path)
      if in_chart(path) then return 'helm' end
    end,
    ['.*/templates/.*%.tpl'] = 'helm',
    ['.*/values.*%.ya?ml'] = function(path)
      if in_chart(path) then return 'yaml.helm-values' end
    end,
  },
})

 vim.lsp.enable('gopls') is all it takes to turn on a language server. Neovim looks for a file called lsp/gopls.lua somewhere in its runtime path (nvim-lspconfig ships hundreds of them), reads the command, file types and root markers from there, and starts the server whenever you open a matching file.

 The loop around it is my own little addition: it only enables servers that are actually installed. In the old config, if you didn't have terraform-ls, Neovim would nag you about it every time you opened a .tf file. Now it just quietly skips it. Install the server later, and it starts working. No config change needed.

Goodbye vim-helm, hello vim.filetype.add()

 Neovim knows Terraform files out of the box now, so vim-terraform is gone. Helm templates it doesn't know, because to Neovim a Helm template is just a YAML file with weird curly braces in it. That's where those few vim.filetype.add() lines come in: any .yaml inside a templates/ folder that belongs to a chart (there's a Chart.yaml somewhere above it) becomes a helm file. And that replaces an entire plugin.

 Here's the kicker: since your templates are now helm files and not yaml files, yamlls never attaches to them in the first place. Remember that on_attach hack that disabled diagnostics on Helm templates, the one I dared you to remove? Not needed anymore. The problem doesn't exist.

Per-server settings go in after/lsp/

 Some servers need extra settings. Each one gets its own small file that just returns a table. They go in after/lsp/ rather than lsp/ so they're loaded after the defaults from nvim-lspconfig, so your settings always win.

-- ~/.config/nvim/after/lsp/gopls.lua
return {
  settings = {
    gopls = {
      analyses = { unusedparams = true },
      staticcheck = true,
      gofumpt = true,
    },
  },
}
-- ~/.config/nvim/after/lsp/yamlls.lua
return {
  settings = {
    yaml = {
      validate = true,
      completion = true,
      -- Auto-detects schemas for GitHub Actions, docker-compose, etc.
      schemaStore = { enable = true, url = 'https://www.schemastore.org/api/json/catalog.json' },
      -- Kubernetes manifests, matched by file name
      schemas = {
        kubernetes = {
          '*deployment*.{yml,yaml}', '*service*.{yml,yaml}', '*ingress*.{yml,yaml}',
          '*configmap*.{yml,yaml}', '*secret*.{yml,yaml}', '*statefulset*.{yml,yaml}',
          '*pod*.{yml,yaml}', 'k8s/**/*.{yml,yaml}',
        },
      },
    },
  },
}
-- ~/.config/nvim/after/lsp/helm_ls.lua
return {
  settings = {
    ['helm-ls'] = {
      yamlls = { path = 'yaml-language-server' },
    },
  },
}

 The YAML one also got smarter. Instead of the seven hard-coded schema URLs I had before, yaml-language-server understands the magic word kubernetes for K8s manifests, and schemaStore automatically picks up schemas for GitHub Actions, docker-compose, GitLab CI and a few hundred other formats.

The keymaps you get for free

 You don't have to map any of this. As soon as a server attaches, these just work:

KeysWhat it does
KHover documentation
grnRename symbol everywhere
graCode actions (quick fixes, auto-imports…)
grrFind references
griGo to implementation
grtGo to type definition 0.12
gODocument outline (all functions, types, etc.)
Ctrl+]Go to definition
Ctrl+S (insert mode)Signature help, while you're typing function arguments
]d / [dJump to next / previous diagnostic
Ctrl+W dShow the diagnostic under the cursor in a float

 And if something isn't working, run :checkhealth vim.lsp. It tells you which servers are enabled, which ones attached to which buffer, and why the others didn't. It's the first place to look before you start blaming your config. Or me.

Reworked: completion without nvim-cmp

 This is the one I was most skeptical about. The old setup used nvim-cmp plus five companion plugins and a 60-line config I had mostly copy-pasted from a README. In 0.12, the editor can show a completion menu as you type, all by itself, and it can pull suggestions from your language server. Here's the whole thing:

-- Completion without nvim-cmp (or its five friends)
vim.o.autocomplete = true                       -- pop the menu as you type
vim.o.complete = 'o^10,.^5,w^5,b^5'             -- LSP first, then open buffers
vim.o.completeopt = 'menuone,noselect,popup,fuzzy'
vim.o.pumheight = 12

-- Let the LSP apply snippets, auto-imports & docs when you accept an item
vim.api.nvim_create_autocmd('LspAttach', {
  group = vim.api.nvim_create_augroup('rs.completion', {}),
  callback = function(ev)
    local client = assert(vim.lsp.get_client_by_id(ev.data.client_id))
    if client:supports_method('textDocument/completion') then
      vim.lsp.completion.enable(true, client.id, ev.buf, { autotrigger = false })
    end
  end,
})

-- <Tab>/<S-Tab>: move through the menu, or jump through snippet fields
local function tab(dir, key, fallback)
  return function()
    if vim.fn.pumvisible() == 1 then return key end
    if vim.snippet.active({ direction = dir }) then
      return ('<Cmd>lua vim.snippet.jump(%d)<CR>'):format(dir)
    end
    return fallback
  end
end
vim.keymap.set({ 'i', 's' }, '<Tab>', tab(1, '<C-n>', '<Tab>'), { expr = true })
vim.keymap.set({ 'i', 's' }, '<S-Tab>', tab(-1, '<C-p>', '<S-Tab>'), { expr = true })

-- <CR> accepts only an item you picked with <Tab>; otherwise it's a normal Enter
vim.keymap.set('i', '<CR>', function()
  if vim.fn.pumvisible() == 1 and vim.fn.complete_info({ 'selected' }).selected ~= -1 then
    return '<C-y>'
  end
  return vim.fn.pumvisible() == 1 and '<C-e><CR>' or '<CR>'
end, { expr = true })

-- <C-Space>: open the menu on demand (LSP if attached, otherwise buffer words)
vim.keymap.set('i', '<C-Space>', function()
  return vim.bo.omnifunc ~= '' and '<C-x><C-o>' or '<C-n>'
end, { expr = true })

-- Command-line autocompletion for :, / and ?
vim.o.wildmode = 'noselect:lastused,full'
vim.o.wildoptions = 'pum'
vim.api.nvim_create_autocmd('CmdlineChanged', {
  pattern = { ':', '/', '?' },
  callback = function() vim.fn.wildtrigger() end,
})

 The interesting bits:

  • vim.o.autocomplete = true is new in 0.12. It pops the completion menu up as you type, no key needed.
  • complete = 'o^10,.^5,w^5,b^5' tells it where to look, in order: the LSP first (o, up to 10 items), then the current buffer, other windows and other open buffers (5 each). One menu, every source, ranked.
  • vim.lsp.completion.enable() doesn't open the menu here (autocomplete already does that), but it makes accepting an item apply the extra goodies: snippet expansion, auto-imports and the documentation popup.
  • Tab / Shift+Tab move through the menu, and jump between snippet fields when there's no menu. Enter only accepts an item you actually picked with Tab. If you didn't pick anything, it's a normal Enter and you get your new line. (My first version accepted the top suggestion on every Enter. Typing a new line and getting ListenAndServeTLS instead gets old fast.)
  • Ctrl+Space opens the menu on demand, for when you haven't typed anything yet. That one's my mapping, not a default: it asks the LSP if there is one, and falls back to words from your open buffers.
>
Typing http.Lis in Go: gopls suggestions, function signatures and the docs popup, without a single completion plugin.

 And the bottom part gives the command line the same treatment. Type :, / or ? and suggestions appear while you type. That's what cmp-cmdline used to do, and fun fact: the old config configured cmp-cmdline but never installed it. I was on fire back then.

Treesitter

 Highlighting is now handled by Neovim itself. The nvim-treesitter plugin's only job is to download and compile parsers (that's why you need tree-sitter-cli). You turn highlighting on yourself, which sounds like more work but is actually just one autocommand:

-- Treesitter: parsers from nvim-treesitter, highlighting from Neovim itself
require('nvim-treesitter').install({
  'go', 'python', 'java', 'c', 'cpp', 'rust', 'lua', 'bash',
  'hcl', 'terraform', 'yaml', 'helm', 'json', 'markdown',
})

vim.api.nvim_create_autocmd('FileType', {
  group = vim.api.nvim_create_augroup('rs.treesitter', {}),
  callback = function(ev)
    -- pcall: quietly skip filetypes that have no parser installed
    if not pcall(vim.treesitter.start, ev.buf) then return end
    vim.wo[0][0].foldmethod = 'expr'
    vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()'
    vim.bo[ev.buf].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
  end,
})

 The first time you start Neovim it'll compile all those parsers in the background. Give it a minute. Make a coffee. The pcall is there so file types without a parser just fall back to normal highlighting instead of throwing errors at you. As a bonus, you also get treesitter-based folding (za to toggle a fold) and smarter indentation.

A Helm template, recognized as Helm and highlighted as Helm, with the file tree open on the left. Zero Helm plugins involved.

In visual mode, press an to expand the selection to the surrounding syntax node (the expression, then the statement, then the function…) and in to shrink it again. New in 0.12. Try v then an an an inside a function. Once you get used to it, there's no going back.

Keymaps, and the stuff I didn't know Neovim could do

 Here's where it gets fun. This is the part where I found out Neovim could do way more than I thought.

local map = vim.keymap.set

-- File tree: netrw as a sidebar (goodbye NERDTree)
vim.g.netrw_banner = 0
vim.g.netrw_liststyle = 3
vim.g.netrw_winsize = 25
map('n', '<C-t>', '<Cmd>Lexplore<CR>', { desc = 'Toggle file tree' })

-- Tabs (same as the old config)
map('n', '<C-n><C-n>', '<Cmd>tabnew<CR>')
map('n', '<C-n><C-p>', '<Cmd>tabprevious<CR>')
map('n', '<C-n><C-x>', '<Cmd>tabnext<CR>')
map('n', '<C-n><C-c>', '<Cmd>tabclose<CR>')

-- Fuzzy file finder built on :find (no fzf required)
local files
function _G.RsFind(arg, _)
  if not files then
    local list = vim.fn.executable('rg') == 1
        and vim.fn.systemlist({ 'rg', '--files', '--hidden', '--glob', '!.git' })
        or vim.fn.globpath('.', '**', true, true)
    files = vim.tbl_map(function(f) return vim.fn.fnamemodify(f, ':.') end,
      vim.tbl_filter(function(f) return vim.fn.isdirectory(f) == 0 end, list))
  end
  return arg == '' and files or vim.fn.matchfuzzy(files, arg)
end
vim.o.findfunc = 'v:lua.RsFind'
vim.api.nvim_create_autocmd('CmdlineEnter', {
  pattern = ':',
  callback = function() files = nil end,   -- refresh the list each time
})
map('n', '<C-f>', ':find ', { desc = 'Fuzzy find file' })

-- The fun stuff
map('n', '<leader>u', '<Cmd>Undotree<CR>', { desc = 'Undo tree' })
map('n', '<leader>f', function() vim.lsp.buf.format() end, { desc = 'Format buffer' })
-- Toggle diagnostic virtual lines with <leader>d
map('n', '<leader>d', function()
  local new_config = not vim.diagnostic.config().virtual_lines
  vim.diagnostic.config({ virtual_lines = new_config })
end, { desc = 'Toggle diagnostic virtual_lines' })

A file tree without NERDTree

 Neovim has always shipped with a file browser called netrw. It's just very ugly by default. Those three netrw_* lines hide the banner, switch to a tree view and make it 25% wide, and the <C-t> mapping in keymaps.lua toggles it as a sidebar with Ctrl+T, the same key I had for NERDTree. That one's my mapping, not a Neovim default, so pick whatever key you like. Press Enter to open a file, % to create one, d to create a folder, D to delete and R to rename.

A fuzzy finder without FZF

 This one blew my mind a bit. Neovim has a :find command, and it lets you plug your own function into it (the findfunc option). Mine lists every file in the project with ripgrep and fuzzy-matches whatever you type. Combined with the command-line autocompletion from before, you get a live fuzzy file picker. With my <C-f> mapping from keymaps.lua: press Ctrl+F, type a few letters, Tab, Enter.

:find yaml fuzzy-matching every YAML file in the project. It's under 20 lines of Lua.

Undo history as a tree

 Here's something most people don't know: Vim's undo history isn't a list, it's a tree. If you undo three times and then type something new, the "redo" branch isn't gone, it's still there, just hidden. Run :Undotree (remember the packadd from Re-headache #4) and it shows you every branch. I mapped it to Space u in keymaps.lua (that's <leader>u, my mapping, not a default). Move the cursor to any state and your file goes back to exactly how it was. Combined with undofile, that works even after you closed the file yesterday. It's basically Git for people who forgot to commit.

Wrote "step one", "step two", undid "step two", wrote "step three". Both branches are still there in the tree on the right.

Diagnostics you can actually read

 By default, errors show up as text at the end of the line. That's fine until a message is longer than your screen. Since 0.11, Neovim can also show diagnostics as virtual lines: the full message on its own line underneath the problem, with a little arrow pointing at the exact spot. It's off by default, though. You switch it on through vim.diagnostic.config():

vim.diagnostic.config({ virtual_lines = true })

 I didn't want it on all the time, so the <leader>d mapping at the bottom of keymaps.lua turns virtual lines on and off. The usual end-of-line message stays put, so with it on you get both: the short version at the end of the line and the full story underneath. With the leader set to space, that's Space d. Again: my shortcut, not a built-in one. If you skip that mapping, you won't have it.

gopls complaining about an unused variable. Also note the E:1 in the statusline: that's the new default statusline counting errors. Bye, lualine.

A few more toys

  • :restart (or ZR) restarts Neovim right there in the same terminal. Perfect after changing your config, no more quitting and reopening like a caveman. It doesn't reopen your files by itself, though: if you want them back, use :mksession! Session.vim | restart source Session.vim.
  • :DiffTool dir1 dir2 compares two files or two whole directories side by side. Great for "what's the difference between these two Helm charts".
  • :lsp restart brings a stuck language server back to life (gopls, I'm looking at you). There's also :lsp stop, :lsp enable and :lsp disable.
  • :checkhealth now shows a summary at the top. Run it once after setup; it tells you about anything missing (a parser, a server, the clipboard tool…).
Experimental: the new message UI (ui2)

Neovim 0.12 also includes a redesigned command line and message area that gets rid of the dreaded "Press ENTER or type command to continue" prompts and highlights your command line as you type. It's still experimental, so it's not in my config, but if you're feeling brave, add this to init.lua:

require('vim._core.ui2').enable()

If it breaks, delete the line. That's the beauty of experiments.

The first launch

 Open Neovim. It'll ask you to confirm installing the three plugins: say yes. Then, once it's done:

  1. Run :restart so everything loads cleanly.
  2. Wait for the treesitter parsers to finish compiling (progress messages at the bottom).
  3. Open a file in a language you installed a server for, and run :checkhealth vim.lsp to make sure it attached.

 That's it. No :PlugInstall, no :TSInstall helm, no running things from the terminal because they didn't work from inside the editor. It just installs itself.

Start Neovim with nvim --clean to get a completely default editor, then fix your config from there. And if it's truly cursed, delete ~/.local/share/nvim/site/pack/core and let vim.pack reinstall everything from scratch.

Re-cap

 Let's take stock. The old config had 18 plugins, a plugin manager installed with curl, about 220 lines of which a good chunk was copy-pasted from READMEs, and two bugs I didn't know about. Now we only need 3 plugins, a plugin manager that came with the editor, completion with no completion plugin, an undo tree, a fuzzy finder, a diff tool, and roughly the same number of lines, except this time I actually understand every one of them.

 Final score: revisited one release cycle's worth of changes, reconfigured ten files from scratch, reworked 15 plugins out of existence, and re-headached exactly four times. Four headaches, all documented, none of them yours anymore. That's a better ratio than the first time around, believe me.

 The funniest part is that most of this was hiding in :help the whole time. I spent hours installing plugins for things Neovim was already doing, just because the internet told me to. So I guess every once in a while, reading the release notes comes in handy, doesn't it?. Run :help news right now. You might find your next favorite feature. Or a function you're using that just got removed.

 And because copy-pasting ten files out of a blog post is just a hassle, I made stealing my config very hard for you and it now lives in a public repo, RattleNvim. It even has a ./install.sh script to make things even more inconvenient for you. Basically it checks you're on Neovim 0.12, backs up whatever config you had into ~/.config/nvim-old-config.tar.gz, and moves in. Bear in mind that that's my daily driver, not the lab version from this post, so it basically means it's very opinionated. That and also that a couple of old friends sneaked back in (Telescope for its popup, nvim-tree for its icons and kanso dark theme because I have feelings), so it runs 8 plugins instead of 3. Still a long way from 18. My secrets are yours too, but keep them between you an I, alright?

 As always, take the config, break it, make it yours. And if you find another hidden gem, you know where to find me. Keep coding!

Comments