Plan a three-phase modernization: triage critical bugs and remove abandoned plugins, swap notify+dressing for snacks UI, and replace nvim-cmp with blink.cmp + telescope with snacks.picker.
17 KiB
Neovim Config Modernization — Design Spec
Date: 2026-05-02 Target Neovim version: 0.12.x Branch strategy: Single branch, three sequential commits with restart-and-validate between each.
Goals
- Fix four silent breakages currently in the running config.
- Remove abandoned, archived, or superseded plugins.
- Swap two plugin engines that have been overtaken by snacks.nvim and blink.cmp in the 2026 ecosystem.
- End up with a smaller, less-redundant
lua/absolute/tree that is consistent with Neovim 0.12 idioms.
Non-goals
- Migrating
nvim-treesitterfrom frozenmasterto the 0.12-onlymainrewrite. Deferred to a separate, future spec. - Restructuring the
absolute/module layout. The currentcore / plugins / after / utils / helperssplit is preserved. - Adding new AI plugins. All AI integrations (
opencode,codecompanion,copilot,gen,llm) are removed in this spec. - Changing colorscheme behavior or the
theme-registrysystem, which is recent and intentional.
Scope summary
| Phase | Theme | Risk | Files touched |
|---|---|---|---|
| 1 | Triage and deletions | Low | ~20 files (mostly deletes) |
| 2 | UI swap (notify + dressing → snacks) | Low | 4 files |
| 3 | Engine swaps (nvim-cmp → blink, telescope → snacks.picker) | Medium | ~15 files |
Each phase is one commit on the same branch. Restart Neovim and validate after each commit before starting the next.
Phase 1 — Triage and deletions
Critical fixes
C1 — lua/absolute/remap.lua: keymap collision on <leader>j / <leader>k.
Lines 70–73 bind <leader>k/<leader>j to lnext/lprev. Lines 120–124 rebind the same keys to window resize, silently overriding them. The window-resize 4-way pad (<leader>h/j/k/l) is the intentional binding.
Action: Delete lines 70–73. If location-list nav is wanted, rebind to the idiomatic ]l / [l pair (next / previous location-list entry). Decision in this spec: rebind to ]l / [l.
C2 — lua/absolute/after/lsp-config.lua: deprecated diagnostic-sign API.
Lines 73–83 use vim.fn.sign_define to set diagnostic gutter icons. This API is deprecated in 0.10 and is removed in 0.12.
Action: Replace with the modern vim.diagnostic.config table:
vim.diagnostic.config({
signs = {
text = {
[vim.diagnostic.severity.ERROR] = " ",
[vim.diagnostic.severity.WARN] = " ",
[vim.diagnostic.severity.HINT] = " ",
[vim.diagnostic.severity.INFO] = " ",
},
},
})
C3 — Orphan after-file lua/absolute/after/nvim-tree.lua.
There is no matching plugin spec in lua/absolute/plugins/. The file is dead but will crash on require if anything ever loads it.
Action: Delete the file.
C4 — Non-idiomatic require in lua/absolute/lazy.lua:36.
require("../current-theme") uses a literal-slash module name. The file is at lua/current-theme.lua, which is on the runtime path; the canonical form is require("current-theme").
Action: Change to require("current-theme").
Plugin removals
The following plugin specs and their after-files are deleted. lazy.nvim removes them from lazy-lock.json automatically on the next :Lazy sync.
| Plugin | Files deleted | Reason |
|---|---|---|
playground |
plugins/nvim-treesitter-playground.lua |
Repo archived 2024; replaced by built-in :Inspect / :InspectTree / :EditQuery |
vim-maximizer |
plugins/vim-maximizer.lua |
Last commit 2013; native <C-w>_ and <C-w>| cover the use case |
poet-v |
plugins/poet-v.lua, after/poet-v.lua |
Abandoned; venv-selector.nvim covers Python envs |
git-blame.nvim |
plugins/git-blame.lua |
Replaced by gitsigns current_line_blame |
render-markdown.nvim |
plugins/render-markdown.lua |
Picked markview.nvim as single markdown renderer |
codecompanion.nvim |
plugins/codecompanion.lua, after/codecompanion.lua |
AI cleanup |
copilot.vim |
plugins/copilot.lua |
AI cleanup |
gen.nvim |
plugins/gen.lua, after/gen.lua |
AI cleanup |
llm.nvim |
plugins/llm.lua, after/llm.lua |
AI cleanup |
opencode.nvim |
plugins/opencode.lua, after/opencode.lua |
AI cleanup |
dressing.nvim and nvim-notify removal is deferred to Phase 2 because both are swapped, not deleted.
New file: lua/absolute/plugins/snacks.lua
Removing opencode.nvim removes the dependency that pulled snacks.nvim into the tree. snacks is required for Phase 2 (input, notifier) and Phase 3 (picker), so a top-level spec is added now.
return {
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
opts = {
bigfile = { enabled = true },
quickfile = { enabled = true },
-- input, notifier added in Phase 2
-- picker added in Phase 3
},
}
Config tweak: gitsigns line-blame
lua/absolute/after/gitsigns.lua is updated to enable virtual-text blame on the current line, replacing the functionality lost when git-blame.nvim is removed:
current_line_blame = true,
current_line_blame_opts = {
virt_text_pos = "eol",
delay = 500,
ignore_whitespace = false,
},
Untracked work
lua/absolute/core/theme-registry.luais committed as-is (already complete).lua/absolute/plugins/flash.luais committed as-is, retainingenabled = false.lua/absolute/after/opencode.luais deleted in this phase along with the rest of opencode.
Disabled-spec policy
Seven plugin specs currently have enabled = false and look intentional:
apple-pkl, flash, flutter-tools, mini, nvim-surround, smear-cursor, tailwind-sorter.
These are left untouched — the user toggles them on for specific projects. They are not part of this modernization.
Phase 1 net change
- 16 plugin spec or after-files deleted
- 1 file added (
plugins/snacks.lua) - 4 files edited (
remap.lua,after/lsp-config.lua,after/gitsigns.lua,lazy.lua) - ~10 plugins removed from lockfile
Phase 1 validation
After committing Phase 1, restart Neovim and verify:
- No errors on startup (
:messagesempty ofE:lines). - Diagnostic signs render (open a Lua file with an error).
:Lazy cleanremoves the orphaned plugins from disk.<leader>jand<leader>kresize the current window.]l/[ljump in location list.- Gitsigns shows current-line blame after a 500 ms idle.
:Inspect,:InspectTree,:EditQueryall work (proves treesitter playground replacement).
Phase 2 — UI swap (notify + dressing → snacks)
Both replaced plugins are tiny. Migration is mostly deletes plus a snacks config bump.
Deletions
| File | Notes |
|---|---|
lua/absolute/plugins/notify.lua |
Lazy spec |
lua/absolute/after/notify.lua |
Configures vim.notify = notify |
lua/absolute/plugins/dressing.lua |
Trivial VeryLazy spec, no after-file |
nvim-notify and dressing.nvim drop from the lockfile.
Update lua/absolute/plugins/snacks.lua
Add input and notifier modules. Notifier config preserves the icons and timeout from the existing nvim-notify setup:
opts = {
bigfile = { enabled = true },
quickfile = { enabled = true },
input = { enabled = true },
notifier = {
enabled = true,
timeout = 3000,
style = "compact",
top_down = true,
icons = {
debug = "",
error = "",
info = "",
trace = "✎",
warn = "",
},
},
}
snacks installs vim.notify = Snacks.notifier.notify and overrides vim.ui.input automatically when the modules are enabled. No manual hookup is needed.
Settings dropped (no equivalent needed)
background_colour, fps, render, stages, minimum_width, time_formats from the old nvim-notify config — all subsumed by snacks defaults or replaced by the style = "compact" choice.
Phase 2 net change
- 3 files deleted
- 1 file edited (
plugins/snacks.lua) - 0 keymaps changed
- 2 plugins removed from lockfile
Phase 2 validation
- Restart Neovim. No errors on startup.
- Trigger a notification (e.g.
:lua vim.notify("test", vim.log.levels.INFO)) — toast appears in the top-right. - Trigger a
vim.ui.input(e.g.<leader>rnto LSP-rename, or<leader>grto enter a git-root command) — centered floating prompt appears. - Trigger a
vim.ui.select(e.g.<leader>cafor code actions) — the snacks select UI appears.
Phase 3 — Engine swaps (blink.cmp + snacks.picker)
The largest phase. Two engine swaps in one commit because they share the snacks ecosystem and both touch the LSP attach path.
3a — nvim-cmp → blink.cmp
Plugins removed from lockfile when nvim-cmp is deleted (they were nvim-cmp dependencies):
nvim-cmp, cmp-nvim-lsp, cmp-buffer, cmp-path, cmp-cmdline, cmp_luasnip, LuaSnip, lspkind.nvim. friendly-snippets is kept and consumed directly by blink.
Files deleted: lua/absolute/plugins/nvim-cmp.lua, lua/absolute/after/nvim-cmp.lua.
New file lua/absolute/plugins/blink-cmp.lua:
return {
"saghen/blink.cmp",
version = "1.*", -- pin to v1 stable
event = "InsertEnter",
dependencies = { "rafamadriz/friendly-snippets" },
config = function()
require("absolute.after.blink-cmp")
end,
}
New file lua/absolute/after/blink-cmp.lua:
require("blink.cmp").setup({
keymap = { preset = "default" }, -- C-y accept, C-n/p navigate, C-Space trigger
appearance = { nerd_font_variant = "mono" },
sources = {
default = { "lsp", "path", "snippets", "buffer" },
},
cmdline = {
keymap = { preset = "cmdline" },
completion = { menu = { auto_show = true } },
},
signature = { enabled = true },
})
Edit lua/absolute/after/lsp-config.lua:
Replace the cmp-nvim-lsp capabilities provider:
-- old:
local cmp_nvim_lsp = require("cmp_nvim_lsp")
local capabilities = cmp_nvim_lsp.default_capabilities()
-- new:
local capabilities = require("blink.cmp").get_lsp_capabilities()
3b — telescope.nvim → snacks.picker
Plugins removed from lockfile when telescope.nvim is deleted:
telescope.nvim, telescope-file-browser.nvim, telescope-live-grep-args.nvim, telescope-media-files.nvim, telescope-themes, telescope-dap.nvim, popup.nvim.
junegunn/fzf stays — it is also a dependency of nvim-bqf (lua/absolute/plugins/nvim-bqf.lua:6), which is unrelated to this swap. The lua/absolute/plugins/fzf.lua spec is kept as-is.
Files deleted:
lua/absolute/plugins/telescope.lualua/absolute/after/telescope.lualua/absolute/utils/telescope_image_preview.lua(replaced by snacks built-in image preview via image.nvim)
Update lua/absolute/plugins/snacks.lua — add the picker module:
opts = {
bigfile = { enabled = true },
quickfile = { enabled = true },
input = { enabled = true },
notifier = { enabled = true, ... }, -- from Phase 2
picker = {
enabled = true,
ui_select = true, -- override vim.ui.select with picker
},
}
New file lua/absolute/after/snacks.lua — replaces the old after/telescope.lua and houses the picker keymaps:
local map = vim.keymap.set
local opts = { noremap = true, silent = true }
map("n", "<C-p>", function() Snacks.picker.files() end, vim.tbl_extend("force", opts, { desc = "Find files" }))
map("n", "<leader>gf", function() Snacks.picker.git_files() end, vim.tbl_extend("force", opts, { desc = "Find git files" }))
map("n", "<leader>pr", function() Snacks.picker.recent() end, vim.tbl_extend("force", opts, { desc = "Recent files" }))
map("n", "<leader>ps", function() Snacks.picker.grep() end, vim.tbl_extend("force", opts, { desc = "Live grep" }))
map("n", "<leader>sb", function() Snacks.picker.grep_word() end, vim.tbl_extend("force", opts, { desc = "Grep word under cursor" }))
map("n", "<leader>pf", function() Snacks.picker.explorer() end, vim.tbl_extend("force", opts, { desc = "File explorer" }))
map("n", "<leader>cf", function() Snacks.picker.explorer({ cwd = vim.fn.expand("%:p:h") }) end, vim.tbl_extend("force", opts, { desc = "File explorer at file dir" }))
Edits to existing files, swapping <cmd>Telescope ...<CR> for Snacks.picker.* calls:
| File | Old binding | New binding |
|---|---|---|
remap.lua:7 |
<cmd>Telescope keymaps<CR> |
function() Snacks.picker.keymaps() end |
core/colorscheme.lua:150 |
<cmd>Telescope colorscheme<CR> |
function() Snacks.picker.colorschemes() end |
core/colorscheme.lua:153 |
<cmd>Telescope themes<CR> |
delete the <leader>th binding entirely (merged into <leader>cs) |
after/lsp-config.lua:20 |
<cmd>Telescope lsp_definitions<CR> |
function() Snacks.picker.lsp_definitions() end |
after/lsp-config.lua:26 |
<cmd>Telescope lsp_implementations<CR> |
function() Snacks.picker.lsp_implementations() end |
after/lsp-config.lua:38 |
<cmd>Telescope lsp_type_definitions<CR> |
function() Snacks.picker.lsp_type_definitions() end |
after/lsp-config.lua:47 |
<cmd>Telescope lsp_references<CR> |
function() Snacks.picker.lsp_references() end |
after/todo-comments.lua:18-20 |
vim.cmd([[TodoTelescope]]) |
Snacks.picker.todo_comments() |
Things lost in Phase 3
- DAP picker (
telescope-dap) — DAP itself works; the picker UI for breakpoints/variables is gone.nvim-dap-uialready covers most of this. live-grep-argsargument syntax — snacks accepts--type luastyle args via a slightly different prompt prefix; muscle-memory shifts.- Custom
telescope_image_preview.lua— dropped. snacks displays images natively viaimage.nvimintegration in its file picker. telescope-themesregistry helpers —<leader>this dropped.<leader>cs(colorschemes picker) replaces both. Thetheme-registry.luasystem continues to work because it hooksColorSchemeautocmds, which fire regardless of how the colorscheme is selected.
Phase 3 net change
- ~14 plugins removed from lockfile (8 from
nvim-cmpchain, 7 fromtelescopechain —friendly-snippetsstays) - 5 files deleted (
plugins/nvim-cmp.lua,after/nvim-cmp.lua,plugins/telescope.lua,after/telescope.lua,utils/telescope_image_preview.lua) - 3 files added (
plugins/blink-cmp.lua,after/blink-cmp.lua,after/snacks.lua) - 5 files edited (
plugins/snacks.lua,after/lsp-config.lua,core/colorscheme.lua,remap.lua,after/todo-comments.lua)
Phase 3 validation
- Restart Neovim. No errors on startup.
- Open an insert-mode completion (
<C-Space>in a code file) — blink menu appears with LSP, snippets, path, buffer sources. - Trigger snippet expansion (e.g.
for<Tab>in Lua) — friendly-snippets fire via blink's snippet engine. <C-p>opens snacks file picker and previews work.<leader>psopens live grep;<leader>sbgreps the word under cursor.- LSP
gd/gi/gt/<leader>cropen snacks LSP pickers. <leader>csopens colorscheme picker; selecting a theme triggers the wezterm/ghostty sync.<leader>?opens keymaps picker.:checkhealth blink.cmpis clean.
Risk and rollback
Each phase is one commit. To roll a phase back, git revert <sha> and :Lazy sync to restore the prior plugin set. Because each phase ends in a working state, partial rollback (e.g. revert Phase 3 but keep Phases 1 and 2) is safe.
The highest-risk single change is the LSP-capabilities provider swap in after/lsp-config.lua (Phase 3a). If LSP completion misbehaves after Phase 3, isolate by temporarily reverting just that one line to the cmp-nvim-lsp provider and bisecting the blink.cmp config.
Open items deferred
- Treesitter
master→mainrewrite (Neovim 0.12-only). Worth its own spec when ready. - Replacing
nvim-tmux-navigationconfiguration file naming (the spec file isvim-tmux-navigator.luabut the plugin isalexghergh/nvim-tmux-navigation). Cosmetic; not part of this work. - Auditing the seven intentionally-disabled plugin specs. Out of scope.
Acceptance criteria
After all three phases land:
:checkhealthreports no deprecated API usage related to this work.lazy-lock.jsonno longer contains:nvim-cmp,cmp-*,lspkind.nvim,LuaSnip,telescope.nvim,telescope-*,popup.nvim,nvim-notify,dressing.nvim,playground,vim-maximizer,poet-v,git-blame.nvim,render-markdown.nvim,codecompanion.nvim,copilot.vim,gen.nvim,llm.nvim,opencode.nvim.lazy-lock.jsonnewly contains:blink.cmp,snacks.nvim.- Every keymap in the migration tables resolves to a working snacks.picker or blink.cmp action.
- Diagnostic gutter signs render through
vim.diagnostic.config. <leader>jand<leader>kresize the current window with no collision.- Startup time as reported by
:Lazy profileis no worse than before.