Three-phase, single-branch plan derived from the modernization spec. Each phase is one commit with restart-and-validate gates. Phase 1 fixes critical bugs and removes abandoned plugins; phase 2 swaps notify/dressing for snacks UI; phase 3 swaps nvim-cmp for blink.cmp and telescope for snacks.picker.
36 KiB
Neovim Modernization Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Modernize a Neovim 0.12 config by fixing four silent breakages, removing abandoned plugins, and swapping nvim-cmp + telescope + nvim-notify + dressing for blink.cmp + snacks.nvim.
Architecture: Three sequential phases on a single branch. Each phase ends in one commit. Tasks within a phase stage changes only — they do not commit individually. Restart Neovim and run the validation checklist at the end of each phase before moving on.
Tech Stack: Neovim 0.12, Lua, lazy.nvim, blink.cmp v1.x, snacks.nvim, gitsigns.nvim.
Spec: docs/superpowers/specs/2026-05-02-nvim-modernization-design.md.
Working directory for all commands: /Users/davidibia/dotfiles/.config/nvim.
File map
Phase 1 — Triage and deletions
Modify:
lua/absolute/remap.lua— drop dead<leader>j/<leader>klnext/lprev binding; rebind to[l/]llua/absolute/after/lsp-config.lua— replacevim.fn.sign_defineblock withvim.diagnostic.configlua/absolute/lazy.lua— fixrequire("../current-theme")→require("current-theme")lua/absolute/after/gitsigns.lua— enable current-line blame
Create:
lua/absolute/plugins/snacks.lua— top-level snacks.nvim spec (foundation only)
Delete:
lua/absolute/after/nvim-tree.lua(orphan)lua/absolute/plugins/nvim-treesitter-playground.lualua/absolute/plugins/vim-maximizer.lualua/absolute/plugins/poet-v.lualua/absolute/after/poet-v.lualua/absolute/plugins/render-markdown.lualua/absolute/plugins/git-blame.lualua/absolute/plugins/codecompanion.lualua/absolute/after/codecompanion.lualua/absolute/plugins/copilot.lualua/absolute/plugins/gen.lualua/absolute/after/gen.lualua/absolute/plugins/llm.lualua/absolute/after/llm.lualua/absolute/plugins/opencode.lualua/absolute/after/opencode.lua
Phase 2 — UI swap
Modify:
lua/absolute/plugins/snacks.lua— enableinputandnotifiermodules
Delete:
lua/absolute/plugins/notify.lualua/absolute/after/notify.lualua/absolute/plugins/dressing.lua
Phase 3 — Engine swaps
Modify:
lua/absolute/plugins/snacks.lua— enablepickermodulelua/absolute/after/lsp-config.lua— swap capabilities provider; swap LSP picker keymapslua/absolute/remap.lua— swap<leader>?keymaplua/absolute/core/colorscheme.lua— swap<leader>cs, drop<leader>thlua/absolute/after/todo-comments.lua— swap<leader>ptfrom:TodoTelescopetoSnacks.picker.todo_comments
Create:
lua/absolute/plugins/blink-cmp.lualua/absolute/after/blink-cmp.lualua/absolute/after/snacks.lua(replacesafter/telescope.lua)
Delete:
lua/absolute/plugins/nvim-cmp.lualua/absolute/after/nvim-cmp.lualua/absolute/plugins/telescope.lualua/absolute/after/telescope.lualua/absolute/utils/telescope_image_preview.lua
Phase 1 — Triage and deletions
Task 1.1: Fix keymap collision (C1)
Files:
-
Modify:
lua/absolute/remap.lua -
Step 1: Open the file and confirm current state
Run: grep -n "leader>k\|leader>j\|lnext\|lprev" lua/absolute/remap.lua
Expected output includes lines 70 (<leader>k lnext), 73 (<leader>j lprev), 120 (<leader>k resize), 124 (<leader>j resize).
- Step 2: Replace the dead lnext/lprev lines with idiomatic location-list bracket pair
Edit lua/absolute/remap.lua. Replace lines 69–73:
opts.desc = "Jump to next location and center screen"
vim.keymap.set("n", "<leader>k", "<cmd>lnext<CR>zz", opts)
opts.desc = "Jump to previous location and center screen"
vim.keymap.set("n", "<leader>j", "<cmd>lprev<CR>zz", opts)
with:
opts.desc = "Next location-list entry"
vim.keymap.set("n", "]l", "<cmd>lnext<CR>zz", opts)
opts.desc = "Previous location-list entry"
vim.keymap.set("n", "[l", "<cmd>lprev<CR>zz", opts)
- Step 3: Verify the file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/remap.lua" -c "qa" 2>&1 | head -5
Expected: no output (file loads clean) or only a "did not load mapleader" type warning. Any E5113/E: line is a failure — re-edit.
Task 1.2: Migrate diagnostic-signs API (C2)
Files:
-
Modify:
lua/absolute/after/lsp-config.lua:73-83 -
Step 1: Open the file and confirm current sign_define block
Run: sed -n '72,84p' lua/absolute/after/lsp-config.lua
Expected: lines 73–83 contain the signs table and the for type, icon in pairs(signs) loop calling vim.fn.sign_define.
- Step 2: Replace the block
In lua/absolute/after/lsp-config.lua, replace lines 73–83:
-- Change the Diagnostic symbols in the sign column (gutter)
local signs = {
Error = " ",
Warn = " ",
Hint = " ",
Info = " ",
}
for type, icon in pairs(signs) do
local hl = "DiagnosticSign" .. type
vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end
with:
-- Configure diagnostic signs (Neovim 0.10+ API; required in 0.12)
vim.diagnostic.config({
signs = {
text = {
[vim.diagnostic.severity.ERROR] = " ",
[vim.diagnostic.severity.WARN] = " ",
[vim.diagnostic.severity.HINT] = " ",
[vim.diagnostic.severity.INFO] = " ",
},
},
})
- Step 3: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/after/lsp-config.lua" -c "qa" 2>&1 | head -5
Note: This will fail because the file requires conform and cmp_nvim_lsp, which aren't on runtimepath under -u NONE. That's expected. What you're checking for is parse errors (E5113: Lua: ... unexpected symbol near ...). If you see only module not found errors, the file parses.
Task 1.3: Delete orphan after-file (C3)
Files:
-
Delete:
lua/absolute/after/nvim-tree.lua -
Step 1: Confirm there is no plugin spec
Run: ls lua/absolute/plugins/ | grep -i nvim-tree
Expected: empty output (no nvim-tree plugin spec exists).
- Step 2: Confirm nothing else
requires the orphan
Run: grep -rn "absolute.after.nvim-tree\|after.nvim-tree" lua/ init.lua 2>/dev/null
Expected: empty output.
- Step 3: Delete the file
Run: rm lua/absolute/after/nvim-tree.lua
- Step 4: Verify deletion
Run: ls lua/absolute/after/nvim-tree.lua 2>&1
Expected: ls: ...: No such file or directory.
Task 1.4: Fix require path in lazy.lua (C4)
Files:
-
Modify:
lua/absolute/lazy.lua:36 -
Step 1: Confirm current state
Run: sed -n '36p' lua/absolute/lazy.lua
Expected: require("../current-theme");
- Step 2: Edit the line
In lua/absolute/lazy.lua, replace line 36:
require("../current-theme");
with:
require("current-theme")
(Drops the bogus ../ prefix and the unnecessary semicolon.)
- Step 3: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/lazy.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean (module-not-found errors are fine; syntax errors are not).
Task 1.5: Add foundation snacks.nvim spec
Files:
-
Create:
lua/absolute/plugins/snacks.lua -
Step 1: Create the file
Create lua/absolute/plugins/snacks.lua:
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
},
}
- Step 2: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/plugins/snacks.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 1.6: Enable gitsigns current-line blame
Files:
-
Modify:
lua/absolute/after/gitsigns.lua -
Step 1: Confirm current contents
Run: cat lua/absolute/after/gitsigns.lua
Expected:
local gitsigns = require("gitsigns")
gitsigns.setup()
- Step 2: Replace the file contents
Replace the entire contents of lua/absolute/after/gitsigns.lua with:
local gitsigns = require("gitsigns")
gitsigns.setup({
current_line_blame = true,
current_line_blame_opts = {
virt_text_pos = "eol",
delay = 500,
ignore_whitespace = false,
},
})
- Step 3: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/after/gitsigns.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean (a "module gitsigns not found" error is fine).
Task 1.7: Delete abandoned plugin specs
Files:
-
Delete:
lua/absolute/plugins/nvim-treesitter-playground.lua -
Delete:
lua/absolute/plugins/vim-maximizer.lua -
Delete:
lua/absolute/plugins/poet-v.lua -
Delete:
lua/absolute/after/poet-v.lua -
Delete:
lua/absolute/plugins/render-markdown.lua -
Delete:
lua/absolute/plugins/git-blame.lua -
Step 1: Verify no references in remaining code
Run:
grep -rn "playground\|vim-maximizer\|poet-v\|render-markdown\|git-blame\|gitblame" lua/ init.lua 2>/dev/null | grep -v "lua/absolute/plugins/nvim-treesitter-playground.lua\|lua/absolute/plugins/vim-maximizer.lua\|lua/absolute/plugins/poet-v.lua\|lua/absolute/after/poet-v.lua\|lua/absolute/plugins/render-markdown.lua\|lua/absolute/plugins/git-blame.lua"
Expected output may include:
lua/absolute/plugins/codecompanion.luamentioningrender-markdownin itsft = { "markdown", "codecompanion" }(therender-markdown.luaspec) — that's fine, codecompanion is being deleted in Task 1.8.- Comments mentioning old gitsigns/git-blame — fine.
If anything else lights up (e.g. an active require("git-blame")), stop and address it before deleting.
- Step 2: Delete the six files
Run:
rm lua/absolute/plugins/nvim-treesitter-playground.lua \
lua/absolute/plugins/vim-maximizer.lua \
lua/absolute/plugins/poet-v.lua \
lua/absolute/after/poet-v.lua \
lua/absolute/plugins/render-markdown.lua \
lua/absolute/plugins/git-blame.lua
- Step 3: Verify all six are gone
Run:
ls lua/absolute/plugins/nvim-treesitter-playground.lua \
lua/absolute/plugins/vim-maximizer.lua \
lua/absolute/plugins/poet-v.lua \
lua/absolute/after/poet-v.lua \
lua/absolute/plugins/render-markdown.lua \
lua/absolute/plugins/git-blame.lua 2>&1
Expected: each line reports No such file or directory.
Task 1.8: Delete AI plugin specs
Files:
-
Delete:
lua/absolute/plugins/codecompanion.lua -
Delete:
lua/absolute/after/codecompanion.lua -
Delete:
lua/absolute/plugins/copilot.lua -
Delete:
lua/absolute/plugins/gen.lua -
Delete:
lua/absolute/after/gen.lua -
Delete:
lua/absolute/plugins/llm.lua -
Delete:
lua/absolute/after/llm.lua -
Delete:
lua/absolute/plugins/opencode.lua -
Delete:
lua/absolute/after/opencode.lua -
Step 1: Verify no references elsewhere
Run:
grep -rn "codecompanion\|copilot\|gen.nvim\|huggingface/llm\|opencode" lua/ init.lua 2>/dev/null \
| grep -v "lua/absolute/plugins/codecompanion.lua\|lua/absolute/after/codecompanion.lua\|lua/absolute/plugins/copilot.lua\|lua/absolute/plugins/gen.lua\|lua/absolute/after/gen.lua\|lua/absolute/plugins/llm.lua\|lua/absolute/after/llm.lua\|lua/absolute/plugins/opencode.lua\|lua/absolute/after/opencode.lua"
Expected: empty output (or only comments).
If you see live references in the toggleterm config (_OLLAMA_TOGGLE is fine — that's an Ollama terminal, not gen.nvim), in markview, or anywhere else, stop and remove those references first.
- Step 2: Delete the nine files
Run:
rm lua/absolute/plugins/codecompanion.lua \
lua/absolute/after/codecompanion.lua \
lua/absolute/plugins/copilot.lua \
lua/absolute/plugins/gen.lua \
lua/absolute/after/gen.lua \
lua/absolute/plugins/llm.lua \
lua/absolute/after/llm.lua \
lua/absolute/plugins/opencode.lua \
lua/absolute/after/opencode.lua
- Step 3: Verify deletion
Run:
ls lua/absolute/plugins/codecompanion.lua lua/absolute/after/codecompanion.lua \
lua/absolute/plugins/copilot.lua \
lua/absolute/plugins/gen.lua lua/absolute/after/gen.lua \
lua/absolute/plugins/llm.lua lua/absolute/after/llm.lua \
lua/absolute/plugins/opencode.lua lua/absolute/after/opencode.lua 2>&1
Expected: every line reports No such file or directory.
Task 1.9: Validate and commit Phase 1
- Step 1: Show pending changes
Run: git status
Expected: modifications to remap.lua, after/lsp-config.lua, lazy.lua, after/gitsigns.lua, plus the previously-modified core/colorscheme.lua, core/init.lua, lazy-lock.json, plugins/codecompanion.lua (now deleted), plugins/copilot.lua (now deleted), plugins/venv-selector.lua, lua/current-theme.lua. New files: plugins/snacks.lua, core/theme-registry.lua, plugins/flash.lua. Many D (deleted) entries.
- Step 2: Run Neovim once to bootstrap snacks and clean orphans
Run: nvim --headless "+Lazy! sync" "+Lazy! clean" "+qa" 2>&1 | tail -20
Expected: lazy installs snacks.nvim, removes playground, vim-maximizer, poet-v, git-blame.nvim, render-markdown.nvim, codecompanion.nvim, copilot.vim, gen.nvim, llm.nvim, opencode.nvim. No errors.
- Step 3: Open Neovim interactively and run validation checklist
Run: nvim
In Neovim:
:messages— should be empty ofE:lines.:Lazy— should showsnacks.nviminstalled; should NOT listnvim-notify-replacement plugins yet (those come in Phase 2).:checkhealth lspconfig— diagnostic signs section should not warn aboutsign_define.- Open a Lua file with a syntax error (e.g.
:e /tmp/bad.lua, typelocal x =); confirm error gutter sign renders. :Inspect,:InspectTree,:EditQuery— all three open without errors (proves treesitter playground replacement).- Open a git-tracked file, position cursor on a line with prior commit history; after ~500 ms confirm virtual blame text appears at end of line.
- Press
<leader>kand<leader>jin normal mode — current window should resize vertically (noE:errors). - Press
]land[l— should move through the location list (or report "location list empty"; either is fine). :q
If anything fails, roll back individually-suspect files with git checkout -- <file> and address before continuing.
- Step 4: Stage and commit Phase 1
Run:
git add lua/absolute/remap.lua \
lua/absolute/after/lsp-config.lua \
lua/absolute/lazy.lua \
lua/absolute/after/gitsigns.lua \
lua/absolute/plugins/snacks.lua \
lua/absolute/core/theme-registry.lua \
lua/absolute/plugins/flash.lua \
lua/absolute/core/colorscheme.lua \
lua/absolute/core/init.lua \
lua/absolute/plugins/venv-selector.lua \
lua/current-theme.lua \
lazy-lock.json
git add -u # picks up the deletions
- Step 5: Verify the staged set
Run: git status
Expected: green entries match the file map for Phase 1; nothing in red.
- Step 6: Commit
Run:
git commit -m "$(cat <<'EOF'
feat(nvim): phase 1 — triage and abandoned-plugin cleanup
Fix keymap collision on <leader>j/<leader>k (now resize-only;
location-list moved to ]l/[l). Migrate diagnostic signs from
deprecated vim.fn.sign_define to vim.diagnostic.config. Drop
orphan after/nvim-tree.lua and the bogus require("../current-theme").
Add foundation snacks.nvim spec. Remove playground, vim-maximizer,
poet-v, git-blame, render-markdown, and all five AI plugin specs.
Enable gitsigns current_line_blame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
- Step 7: Verify commit
Run: git log -1 --stat
Expected: shows the Phase 1 commit with file list matching the file map above.
Phase 2 — UI swap
Task 2.1: Enable snacks input and notifier
Files:
-
Modify:
lua/absolute/plugins/snacks.lua -
Step 1: Replace the file contents
Replace the entire contents of lua/absolute/plugins/snacks.lua with:
return {
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
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 = "",
},
},
-- picker added in Phase 3
},
}
- Step 2: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/plugins/snacks.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 2.2: Delete nvim-notify and dressing
Files:
-
Delete:
lua/absolute/plugins/notify.lua -
Delete:
lua/absolute/after/notify.lua -
Delete:
lua/absolute/plugins/dressing.lua -
Step 1: Verify no live references
Run:
grep -rn "rcarriga/nvim-notify\|require(\"notify\")\|stevearc/dressing\|require(\"dressing\")" lua/ init.lua 2>/dev/null \
| grep -v "lua/absolute/plugins/notify.lua\|lua/absolute/after/notify.lua\|lua/absolute/plugins/dressing.lua"
Expected: empty.
- Step 2: Delete the three files
Run:
rm lua/absolute/plugins/notify.lua \
lua/absolute/after/notify.lua \
lua/absolute/plugins/dressing.lua
- Step 3: Verify deletion
Run:
ls lua/absolute/plugins/notify.lua \
lua/absolute/after/notify.lua \
lua/absolute/plugins/dressing.lua 2>&1
Expected: each line reports No such file or directory.
Task 2.3: Validate and commit Phase 2
- Step 1: Sync lazy and prune
Run: nvim --headless "+Lazy! sync" "+Lazy! clean" "+qa" 2>&1 | tail -20
Expected: lazy removes nvim-notify and dressing.nvim. No errors.
- Step 2: Validate interactively
Run: nvim
In Neovim:
:messages— clean.:lua vim.notify("Phase 2 OK", vim.log.levels.INFO)— compact toast appears top-right.:lua vim.notify("Test warn", vim.log.levels.WARN)— warning-styled toast.<leader>rnon a symbol in a code file — centered floating prompt appears (provesvim.ui.inputoverride).<leader>caon a code-action target — selection menu uses snacks picker UI (provesvim.ui.selectoverride; picker not yet enabled, so this routes through snacks.input).:q
- Step 3: Stage and commit Phase 2
Run:
git add lua/absolute/plugins/snacks.lua lazy-lock.json
git add -u
git commit -m "$(cat <<'EOF'
feat(nvim): phase 2 — replace nvim-notify and dressing with snacks
Enable snacks.input and snacks.notifier modules; remove the
now-redundant nvim-notify and archived dressing.nvim specs.
Notifier preserves the prior icons and 3000 ms timeout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
- Step 4: Verify commit
Run: git log -1 --stat
Expected: shows the Phase 2 commit.
Phase 3 — Engine swaps
Task 3.1: Add blink.cmp plugin spec
Files:
-
Create:
lua/absolute/plugins/blink-cmp.lua -
Step 1: Create the file
Create lua/absolute/plugins/blink-cmp.lua:
return {
"saghen/blink.cmp",
version = "1.*",
event = "InsertEnter",
dependencies = { "rafamadriz/friendly-snippets" },
config = function()
require("absolute.after.blink-cmp")
end,
}
- Step 2: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/plugins/blink-cmp.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 3.2: Add blink.cmp configuration
Files:
-
Create:
lua/absolute/after/blink-cmp.lua -
Step 1: Create the file
Create lua/absolute/after/blink-cmp.lua:
require("blink.cmp").setup({
keymap = { preset = "default" },
appearance = {
nerd_font_variant = "mono",
},
sources = {
default = { "lsp", "path", "snippets", "buffer" },
},
cmdline = {
keymap = { preset = "cmdline" },
completion = { menu = { auto_show = true } },
},
signature = { enabled = true },
})
- Step 2: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/after/blink-cmp.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean (module blink.cmp not found is fine; syntax errors are not).
Task 3.3: Migrate LSP capabilities provider to blink
Files:
-
Modify:
lua/absolute/after/lsp-config.lua:3,70 -
Step 1: Confirm current state
Run: sed -n '1,5p;68,72p' lua/absolute/after/lsp-config.lua
Expected: line 3 is local cmp_nvim_lsp = require("cmp_nvim_lsp"); line 70 is local capabilities = cmp_nvim_lsp.default_capabilities().
- Step 2: Edit line 3
In lua/absolute/after/lsp-config.lua, replace line 3:
local cmp_nvim_lsp = require("cmp_nvim_lsp")
with: (delete the line entirely). Make sure no blank line is left between local conform = require("conform") and the next non-blank line.
- Step 3: Edit the capabilities line
In the same file, replace the line:
local capabilities = cmp_nvim_lsp.default_capabilities()
with:
local capabilities = require("blink.cmp").get_lsp_capabilities()
- Step 4: Verify the edit
Run: grep -n "capabilities\|cmp_nvim_lsp\|blink" lua/absolute/after/lsp-config.lua | head -10
Expected: no cmp_nvim_lsp references; one blink.cmp reference; many capabilities = capabilities reuses.
Task 3.4: Delete nvim-cmp specs
Files:
-
Delete:
lua/absolute/plugins/nvim-cmp.lua -
Delete:
lua/absolute/after/nvim-cmp.lua -
Step 1: Verify no live references outside the doomed files
Run:
grep -rn "hrsh7th/nvim-cmp\|require(\"cmp\")\|require(\"cmp_nvim_lsp\")\|require(\"luasnip\")\|require(\"lspkind\")" lua/ init.lua 2>/dev/null \
| grep -v "lua/absolute/plugins/nvim-cmp.lua\|lua/absolute/after/nvim-cmp.lua"
Expected: empty (the LSP capabilities reference was removed in Task 3.3).
If anything else lights up — e.g. another plugin that called cmp.register_source — pause and migrate that reference before continuing.
- Step 2: Delete the two files
Run:
rm lua/absolute/plugins/nvim-cmp.lua \
lua/absolute/after/nvim-cmp.lua
- Step 3: Verify deletion
Run: ls lua/absolute/plugins/nvim-cmp.lua lua/absolute/after/nvim-cmp.lua 2>&1
Expected: each line reports No such file or directory.
Task 3.5: Enable snacks picker module
Files:
-
Modify:
lua/absolute/plugins/snacks.lua -
Step 1: Replace the file contents
Replace the entire contents of lua/absolute/plugins/snacks.lua with:
return {
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
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 = "",
},
},
picker = {
enabled = true,
ui_select = true,
},
},
}
- Step 2: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/plugins/snacks.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 3.6: Add new after/snacks.lua with picker keymaps
Files:
-
Create:
lua/absolute/after/snacks.lua -
Step 1: Create the file
Create lua/absolute/after/snacks.lua:
local map = vim.keymap.set
local function with_desc(desc)
return { noremap = true, silent = true, desc = desc }
end
map("n", "<C-p>", function()
Snacks.picker.files()
end, with_desc("Find files"))
map("n", "<leader>gf", function()
Snacks.picker.git_files()
end, with_desc("Find git files"))
map("n", "<leader>pr", function()
Snacks.picker.recent()
end, with_desc("Recent files"))
map("n", "<leader>ps", function()
Snacks.picker.grep()
end, with_desc("Live grep"))
map("n", "<leader>sb", function()
Snacks.picker.grep_word()
end, with_desc("Grep word under cursor"))
map("n", "<leader>pf", function()
Snacks.picker.explorer()
end, with_desc("File explorer (cwd)"))
map("n", "<leader>cf", function()
Snacks.picker.explorer({ cwd = vim.fn.expand("%:p:h") })
end, with_desc("File explorer (current file dir)"))
Note: snacks auto-loads its after/plugin/snacks.vim shim that creates the Snacks global, so the function() wrappers (rather than direct references) are required to defer global lookup until the keymap fires.
- Step 2: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/after/snacks.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean. (The Snacks global won't exist under -u NONE, but that's a runtime concern, not a parse error.)
- Step 3: Wire the after-file into the plugin spec
Edit lua/absolute/plugins/snacks.lua to call the new after-file. Replace the entire contents with:
return {
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
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 = "",
},
},
picker = {
enabled = true,
ui_select = true,
},
},
config = function(_, opts)
require("snacks").setup(opts)
require("absolute.after.snacks")
end,
}
- Step 4: Verify the spec parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/plugins/snacks.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 3.7: Migrate <leader>? keymap in remap.lua
Files:
-
Modify:
lua/absolute/remap.lua:7 -
Step 1: Confirm current state
Run: sed -n '6,8p' lua/absolute/remap.lua
Expected:
opts.desc = "Show Keymap"
vim.keymap.set("n", "<leader>?", "<cmd>Telescope keymaps<CR>", opts) -- toggle file explorer
- Step 2: Edit the binding
Replace those two lines in lua/absolute/remap.lua:
opts.desc = "Show Keymap"
vim.keymap.set("n", "<leader>?", "<cmd>Telescope keymaps<CR>", opts) -- toggle file explorer
with:
opts.desc = "Show keymaps"
vim.keymap.set("n", "<leader>?", function()
Snacks.picker.keymaps()
end, opts)
- Step 3: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/remap.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 3.8: Migrate keymaps in colorscheme.lua
Files:
-
Modify:
lua/absolute/core/colorscheme.lua:147-153 -
Step 1: Confirm current state
Run: sed -n '147,154p' lua/absolute/core/colorscheme.lua
Expected:
local opts = { noremap = true, silent = true }
opts.desc = "Fuzzy find colorschemes"
vim.api.nvim_set_keymap("n", "<leader>cs", "<cmd>Telescope colorscheme<CR>", opts)
opts.desc = "Fuzzy find themes"
vim.api.nvim_set_keymap("n", "<leader>th", "<cmd>Telescope themes<CR>", opts)
- Step 2: Replace the block
Replace those eight lines (inclusive of the trailing blank if present) with:
local opts = { noremap = true, silent = true }
opts.desc = "Fuzzy find colorschemes"
vim.keymap.set("n", "<leader>cs", function()
Snacks.picker.colorschemes()
end, opts)
(Drops <leader>th per the spec — the registry-aware behavior fires on ColorScheme autocmds regardless of how the colorscheme is selected.)
- Step 3: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/core/colorscheme.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean (a "module absolute.core.theme-registry not found" error is fine).
Task 3.9: Migrate <leader>pt in todo-comments.lua
Files:
-
Modify:
lua/absolute/after/todo-comments.lua:17-20 -
Step 1: Confirm current state
Run: sed -n '17,21p' lua/absolute/after/todo-comments.lua
Expected:
opts.desc = "Open Comments in Telescope"
vim.keymap.set("n", "<leader>pt", function()
vim.cmd([[TodoTelescope]])
end, opts)
- Step 2: Replace the block
Replace those four lines in lua/absolute/after/todo-comments.lua:
opts.desc = "Open Comments in Telescope"
vim.keymap.set("n", "<leader>pt", function()
vim.cmd([[TodoTelescope]])
end, opts)
with:
opts.desc = "Open todo comments in picker"
vim.keymap.set("n", "<leader>pt", function()
Snacks.picker.todo_comments()
end, opts)
- Step 3: Verify file parses
Run: nvim --headless -u NONE -c "luafile lua/absolute/after/todo-comments.lua" -c "qa" 2>&1 | head -5
Expected: parse-clean.
Task 3.10: Migrate LSP picker keymaps in lsp-config.lua
Files:
-
Modify:
lua/absolute/after/lsp-config.lua:20,26,38,47 -
Step 1: Confirm current state
Run: grep -n "Telescope" lua/absolute/after/lsp-config.lua
Expected:
20: vim.keymap.set("n", "gd", "<cmd>Telescope lsp_definitions<CR>", opts)
26: vim.keymap.set("n", "gi", "<cmd>Telescope lsp_implementations<CR>", opts)
38: vim.keymap.set("n", "gt", "<cmd>Telescope lsp_type_definitions<CR>", opts)
47: vim.keymap.set("n", "<leader>cr", "<cmd>Telescope lsp_references<CR>", opts)
- Step 2: Replace each binding
Make four individual edits in lua/absolute/after/lsp-config.lua:
Replace line 20:
vim.keymap.set("n", "gd", "<cmd>Telescope lsp_definitions<CR>", opts)
with:
vim.keymap.set("n", "gd", function() Snacks.picker.lsp_definitions() end, opts)
Replace line 26:
vim.keymap.set("n", "gi", "<cmd>Telescope lsp_implementations<CR>", opts)
with:
vim.keymap.set("n", "gi", function() Snacks.picker.lsp_implementations() end, opts)
Replace line 38:
vim.keymap.set("n", "gt", "<cmd>Telescope lsp_type_definitions<CR>", opts)
with:
vim.keymap.set("n", "gt", function() Snacks.picker.lsp_type_definitions() end, opts)
Replace line 47:
vim.keymap.set("n", "<leader>cr", "<cmd>Telescope lsp_references<CR>", opts)
with:
vim.keymap.set("n", "<leader>cr", function() Snacks.picker.lsp_references() end, opts)
- Step 3: Verify
Run: grep -n "Telescope\|Snacks.picker" lua/absolute/after/lsp-config.lua
Expected: zero Telescope matches; four Snacks.picker.lsp_* matches.
Task 3.11: Delete telescope ecosystem
Files:
-
Delete:
lua/absolute/plugins/telescope.lua -
Delete:
lua/absolute/after/telescope.lua -
Delete:
lua/absolute/utils/telescope_image_preview.lua -
Step 1: Verify no live references remain
Run:
grep -rn "Telescope\|require(\"telescope\|telescope.builtin\|telescope_image_preview\|nvim-telescope" lua/ init.lua 2>/dev/null \
| grep -v "lua/absolute/plugins/telescope.lua\|lua/absolute/after/telescope.lua\|lua/absolute/utils/telescope_image_preview.lua"
Expected: empty.
If anything else lights up — e.g. a custom command, a comment that's actually executable — pause and migrate before deleting.
- Step 2: Delete the three files
Run:
rm lua/absolute/plugins/telescope.lua \
lua/absolute/after/telescope.lua \
lua/absolute/utils/telescope_image_preview.lua
- Step 3: Verify deletion
Run:
ls lua/absolute/plugins/telescope.lua \
lua/absolute/after/telescope.lua \
lua/absolute/utils/telescope_image_preview.lua 2>&1
Expected: each line reports No such file or directory.
Task 3.12: Validate and commit Phase 3
- Step 1: Sync lazy and prune
Run: nvim --headless "+Lazy! sync" "+Lazy! clean" "+qa" 2>&1 | tail -30
Expected:
-
lazy installs
blink.cmp(and downloads its Rust matcher binary; first run takes longer). -
lazy removes
telescope.nvim,telescope-file-browser.nvim,telescope-live-grep-args.nvim,telescope-media-files.nvim,telescope-themes,telescope-dap.nvim,popup.nvim,nvim-cmp,cmp-nvim-lsp,cmp-buffer,cmp-path,cmp-cmdline,cmp_luasnip,LuaSnip,lspkind.nvim. -
No errors.
-
Step 2: Run
:checkhealth blink.cmp
Run: nvim --headless "+checkhealth blink.cmp" "+qa" 2>&1 | tail -40
Expected: green checks for vim.snippet, fuzzy matcher binary, and at least one source.
- Step 3: Validate interactively
Run: nvim
In Neovim:
:messages— clean.- Open a Lua file. Type
vim.land confirm a blink completion menu appears with LSP entries. - With the menu open:
<C-n>/<C-p>navigate;<C-y>accepts. - In Lua, type
for<Tab>— confirm thefriendly-snippetsforsnippet expands. - Type
:Lazy <Tab>in cmdline — confirm cmdline completion shows. - Press
Kon a symbol — LSP hover floats appear. - Press
gdon a symbol —Snacks.picker.lsp_definitionsopens. - Press
gion a symbol —Snacks.picker.lsp_implementationsopens. - Press
gton a symbol —Snacks.picker.lsp_type_definitionsopens. - Press
<leader>cron a symbol —Snacks.picker.lsp_referencesopens. - Press
<C-p>— file picker; preview pane works. - Press
<leader>gf— git files; preview works. - Press
<leader>pr— recent files. - Press
<leader>ps— live grep; typefunction; results stream in. - Press
<leader>sbon a word — grep word under cursor; results show. - Press
<leader>pf— file explorer at cwd. - Press
<leader>cf— file explorer at current file's directory. - Press
<leader>?— keymaps picker. - Press
<leader>cs— colorscheme picker; pick a theme; confirm wezterm/ghostty sync still fires. - Press
<leader>pt— todo-comments picker. :q
If any binding fails, run :lua print(vim.inspect(vim.api.nvim_get_keymap("n"))) to inspect the actual binding state and re-edit the source file.
- Step 4: Stage and commit Phase 3
Run:
git add lua/absolute/plugins/blink-cmp.lua \
lua/absolute/after/blink-cmp.lua \
lua/absolute/after/snacks.lua \
lua/absolute/plugins/snacks.lua \
lua/absolute/after/lsp-config.lua \
lua/absolute/remap.lua \
lua/absolute/core/colorscheme.lua \
lua/absolute/after/todo-comments.lua \
lazy-lock.json
git add -u
git commit -m "$(cat <<'EOF'
feat(nvim): phase 3 — replace nvim-cmp with blink.cmp and telescope with snacks.picker
Swap the completion engine to blink.cmp v1.x, dropping nvim-cmp,
its eight cmp-* sources, lspkind, and LuaSnip. Swap the picker
to snacks.picker, removing telescope.nvim and six telescope
extensions. LSP capabilities now sourced from blink.cmp; LSP
navigation keymaps and the colorscheme/keymaps/todo pickers move
to Snacks.picker. friendly-snippets is preserved as blink's
snippet source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
- Step 5: Verify commit and overall branch state
Run: git log --oneline -4
Expected (most recent first):
<sha> feat(nvim): phase 3 — replace nvim-cmp with blink.cmp and telescope with snacks.picker
<sha> feat(nvim): phase 2 — replace nvim-notify and dressing with snacks
<sha> feat(nvim): phase 1 — triage and abandoned-plugin cleanup
fd3878b docs(spec): add nvim modernization design
Run: git diff --stat HEAD~3 HEAD | tail -3
Expected: large net negative line count (~hundreds of lines deleted, fewer added).
Done. Acceptance verification
- Final acceptance check
Run:
grep -E '"(nvim-cmp|cmp-buffer|cmp-cmdline|cmp-path|cmp_luasnip|cmp-nvim-lsp|lspkind\.nvim|LuaSnip|telescope\.nvim|telescope-file-browser\.nvim|telescope-live-grep-args\.nvim|telescope-media-files\.nvim|telescope-themes|telescope-dap\.nvim|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.json
Expected: empty output (every removed plugin is gone from the lockfile).
Run:
grep -E '"(blink\.cmp|snacks\.nvim)"' lazy-lock.json
Expected: both lines present.
Run: grep -rn "vim.fn.sign_define\|require(\"cmp_nvim_lsp\")\|require(\"telescope\)" lua/ init.lua 2>/dev/null
Expected: empty.
If all three checks pass, the implementation matches the spec.