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