# 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 `j`/`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 `?` keymap - `lua/absolute/core/colorscheme.lua` — swap `cs`, drop `th` - `lua/absolute/after/todo-comments.lua` — swap `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 (`k` lnext), 73 (`j` lprev), 120 (`k` resize), 124 (`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: ```lua opts.desc = "Jump to next location and center screen" vim.keymap.set("n", "k", "lnextzz", opts) opts.desc = "Jump to previous location and center screen" vim.keymap.set("n", "j", "lprevzz", opts) ``` with: ```lua opts.desc = "Next location-list entry" vim.keymap.set("n", "]l", "lnextzz", opts) opts.desc = "Previous location-list entry" vim.keymap.set("n", "[l", "lprevzz", 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: ```lua -- 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: ```lua -- 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 `require`s 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: ```lua require("../current-theme"); ``` with: ```lua 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`: ```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: ```lua local gitsigns = require("gitsigns") gitsigns.setup() ``` - [ ] **Step 2: Replace the file contents** Replace the entire contents of `lua/absolute/after/gitsigns.lua` with: ```lua 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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 `k` and `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 -- ` and address before continuing. - [ ] **Step 4: Stage and commit Phase 1** Run: ```bash 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: ```bash git commit -m "$(cat <<'EOF' feat(nvim): phase 1 — triage and abandoned-plugin cleanup Fix keymap collision on j/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) 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: ```lua 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: ```bash 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: ```bash rm lua/absolute/plugins/notify.lua \ lua/absolute/after/notify.lua \ lua/absolute/plugins/dressing.lua ``` - [ ] **Step 3: Verify deletion** Run: ```bash 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. `rn` on a symbol in a code file — centered floating prompt appears (proves `vim.ui.input` override). 5. `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: ```bash 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) 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`: ```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`: ```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: ```lua 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: ```lua local capabilities = cmp_nvim_lsp.default_capabilities() ``` with: ```lua 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: ```bash 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: ```bash 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: ```lua 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`: ```lua local map = vim.keymap.set local function with_desc(desc) return { noremap = true, silent = true, desc = desc } end map("n", "", function() Snacks.picker.files() end, with_desc("Find files")) map("n", "gf", function() Snacks.picker.git_files() end, with_desc("Find git files")) map("n", "pr", function() Snacks.picker.recent() end, with_desc("Recent files")) map("n", "ps", function() Snacks.picker.grep() end, with_desc("Live grep")) map("n", "sb", function() Snacks.picker.grep_word() end, with_desc("Grep word under cursor")) map("n", "pf", function() Snacks.picker.explorer() end, with_desc("File explorer (cwd)")) map("n", "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: ```lua 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 `?` 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: ```lua opts.desc = "Show Keymap" vim.keymap.set("n", "?", "Telescope keymaps", opts) -- toggle file explorer ``` - [ ] **Step 2: Edit the binding** Replace those two lines in `lua/absolute/remap.lua`: ```lua opts.desc = "Show Keymap" vim.keymap.set("n", "?", "Telescope keymaps", opts) -- toggle file explorer ``` with: ```lua opts.desc = "Show keymaps" vim.keymap.set("n", "?", 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: ```lua local opts = { noremap = true, silent = true } opts.desc = "Fuzzy find colorschemes" vim.api.nvim_set_keymap("n", "cs", "Telescope colorscheme", opts) opts.desc = "Fuzzy find themes" vim.api.nvim_set_keymap("n", "th", "Telescope themes", opts) ``` - [ ] **Step 2: Replace the block** Replace those eight lines (inclusive of the trailing blank if present) with: ```lua local opts = { noremap = true, silent = true } opts.desc = "Fuzzy find colorschemes" vim.keymap.set("n", "cs", function() Snacks.picker.colorschemes() end, opts) ``` (Drops `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 `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: ```lua opts.desc = "Open Comments in Telescope" vim.keymap.set("n", "pt", function() vim.cmd([[TodoTelescope]]) end, opts) ``` - [ ] **Step 2: Replace the block** Replace those four lines in `lua/absolute/after/todo-comments.lua`: ```lua opts.desc = "Open Comments in Telescope" vim.keymap.set("n", "pt", function() vim.cmd([[TodoTelescope]]) end, opts) ``` with: ```lua opts.desc = "Open todo comments in picker" vim.keymap.set("n", "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", "Telescope lsp_definitions", opts) 26: vim.keymap.set("n", "gi", "Telescope lsp_implementations", opts) 38: vim.keymap.set("n", "gt", "Telescope lsp_type_definitions", opts) 47: vim.keymap.set("n", "cr", "Telescope lsp_references", opts) ``` - [ ] **Step 2: Replace each binding** Make four individual edits in `lua/absolute/after/lsp-config.lua`: Replace line 20: ```lua vim.keymap.set("n", "gd", "Telescope lsp_definitions", opts) ``` with: ```lua vim.keymap.set("n", "gd", function() Snacks.picker.lsp_definitions() end, opts) ``` Replace line 26: ```lua vim.keymap.set("n", "gi", "Telescope lsp_implementations", opts) ``` with: ```lua vim.keymap.set("n", "gi", function() Snacks.picker.lsp_implementations() end, opts) ``` Replace line 38: ```lua vim.keymap.set("n", "gt", "Telescope lsp_type_definitions", opts) ``` with: ```lua vim.keymap.set("n", "gt", function() Snacks.picker.lsp_type_definitions() end, opts) ``` Replace line 47: ```lua vim.keymap.set("n", "cr", "Telescope lsp_references", opts) ``` with: ```lua vim.keymap.set("n", "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: ```bash 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: ```bash rm lua/absolute/plugins/telescope.lua \ lua/absolute/after/telescope.lua \ lua/absolute/utils/telescope_image_preview.lua ``` - [ ] **Step 3: Verify deletion** Run: ```bash 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: ``/`` navigate; `` accepts. 4. In Lua, type `for` — confirm the `friendly-snippets` `for` snippet expands. 5. Type `:Lazy ` 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 `cr` on a symbol — `Snacks.picker.lsp_references` opens. 11. Press `` — file picker; preview pane works. 12. Press `gf` — git files; preview works. 13. Press `pr` — recent files. 14. Press `ps` — live grep; type `function`; results stream in. 15. Press `sb` on a word — grep word under cursor; results show. 16. Press `pf` — file explorer at cwd. 17. Press `cf` — file explorer at current file's directory. 18. Press `?` — keymaps picker. 19. Press `cs` — colorscheme picker; pick a theme; confirm wezterm/ghostty sync still fires. 20. Press `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: ```bash 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) EOF )" ``` - [ ] **Step 5: Verify commit and overall branch state** Run: `git log --oneline -4` Expected (most recent first): ``` feat(nvim): phase 3 — replace nvim-cmp with blink.cmp and telescope with snacks.picker feat(nvim): phase 2 — replace nvim-notify and dressing with snacks 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: ```bash 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: ```bash 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.