Files
absolute-vim/docs/superpowers/plans/2026-05-02-nvim-modernization.md
David Ibia b4bb915a3f docs(plan): add nvim modernization implementation plan
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.
2026-05-02 06:04:43 +01:00

36 KiB
Raw Permalink Blame History

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>k lnext/lprev binding; rebind to [l/]l
  • lua/absolute/after/lsp-config.lua — replace vim.fn.sign_define block with vim.diagnostic.config
  • lua/absolute/lazy.lua — fix require("../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.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
  • 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

Phase 2 — UI swap

Modify:

  • lua/absolute/plugins/snacks.lua — enable input and notifier modules

Delete:

  • lua/absolute/plugins/notify.lua
  • lua/absolute/after/notify.lua
  • lua/absolute/plugins/dressing.lua

Phase 3 — Engine swaps

Modify:

  • lua/absolute/plugins/snacks.lua — enable picker module
  • lua/absolute/after/lsp-config.lua — swap capabilities provider; swap LSP picker keymaps
  • lua/absolute/remap.lua — swap <leader>? keymap
  • lua/absolute/core/colorscheme.lua — swap <leader>cs, drop <leader>th
  • lua/absolute/after/todo-comments.lua — swap <leader>pt from :TodoTelescope to Snacks.picker.todo_comments

Create:

  • lua/absolute/plugins/blink-cmp.lua
  • lua/absolute/after/blink-cmp.lua
  • lua/absolute/after/snacks.lua (replaces after/telescope.lua)

Delete:

  • lua/absolute/plugins/nvim-cmp.lua
  • lua/absolute/after/nvim-cmp.lua
  • lua/absolute/plugins/telescope.lua
  • lua/absolute/after/telescope.lua
  • lua/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.lua mentioning render-markdown in its ft = { "markdown", "codecompanion" } (the render-markdown.lua spec) — 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:

  1. :messages — should be empty of E: lines.
  2. :Lazy — should show snacks.nvim installed; should NOT list nvim-notify-replacement plugins yet (those come in Phase 2).
  3. :checkhealth lspconfig — diagnostic signs section should not warn about sign_define.
  4. Open a Lua file with a syntax error (e.g. :e /tmp/bad.lua, type local x =); confirm error gutter sign renders.
  5. :Inspect, :InspectTree, :EditQuery — all three open without errors (proves treesitter playground replacement).
  6. 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.
  7. Press <leader>k and <leader>j in normal mode — current window should resize vertically (no E: errors).
  8. Press ]l and [l — should move through the location list (or report "location list empty"; either is fine).
  9. :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:

  1. :messages — clean.
  2. :lua vim.notify("Phase 2 OK", vim.log.levels.INFO) — compact toast appears top-right.
  3. :lua vim.notify("Test warn", vim.log.levels.WARN) — warning-styled toast.
  4. <leader>rn on a symbol in a code file — centered floating prompt appears (proves vim.ui.input override).
  5. <leader>ca on a code-action target — selection menu uses snacks picker UI (proves vim.ui.select override; picker not yet enabled, so this routes through snacks.input).
  6. :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).


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:

  1. :messages — clean.
  2. Open a Lua file. Type vim.l and confirm a blink completion menu appears with LSP entries.
  3. With the menu open: <C-n>/<C-p> navigate; <C-y> accepts.
  4. In Lua, type for<Tab> — confirm the friendly-snippets for snippet expands.
  5. Type :Lazy <Tab> in cmdline — confirm cmdline completion shows.
  6. Press K on a symbol — LSP hover floats appear.
  7. Press gd on a symbol — Snacks.picker.lsp_definitions opens.
  8. Press gi on a symbol — Snacks.picker.lsp_implementations opens.
  9. Press gt on a symbol — Snacks.picker.lsp_type_definitions opens.
  10. Press <leader>cr on a symbol — Snacks.picker.lsp_references opens.
  11. Press <C-p> — file picker; preview pane works.
  12. Press <leader>gf — git files; preview works.
  13. Press <leader>pr — recent files.
  14. Press <leader>ps — live grep; type function; results stream in.
  15. Press <leader>sb on a word — grep word under cursor; results show.
  16. Press <leader>pf — file explorer at cwd.
  17. Press <leader>cf — file explorer at current file's directory.
  18. Press <leader>? — keymaps picker.
  19. Press <leader>cs — colorscheme picker; pick a theme; confirm wezterm/ghostty sync still fires.
  20. Press <leader>pt — todo-comments picker.
  21. :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.