# Navigation Enhancement Plans **Status**: Two complementary approaches being considered (not mutually exclusive) Both approaches aim to reduce repetitive typing when navigating with Neovim's bracket-based pairs (`[c`, `]d`, etc.). --- ## Option 1: Repeat/Reverse Last Bracket Navigation ### Concept Two simple commands that remember and replay the last `[x` or `]x` navigation: - **Repeat**: Execute the same navigation again (e.g., `]d` → `.` → `.` → `.`) - **Reverse**: Execute the opposite direction (e.g., after `]d`, press `,` → `[d`) Similar to `;` and `,` for repeating/reversing `f/F/t/T` motions. ### User Experience ```vim " Example 1: Scanning diagnostics ]d " Next diagnostic . " Next diagnostic (repeat) . " Next diagnostic (repeat) . " Next diagnostic (repeat) , " Previous diagnostic (reverse, oops went too far) " Example 2: Checking spelling ]s " Next misspelling . " Next misspelling . " Next misspelling " Example 3: Quickfix workflow ]q " Next quickfix item . " Next quickfix . " Next quickfix ``` ### When This Works Best - Repeatedly navigating **same type**: diagnostics, spelling, quickfix, location list - Linear scanning through items of one kind - Quick corrections when you overshoot (reverse) ### Implementation **Module**: `lua/bracket-repeat.lua` ```lua local M = {} -- Track last bracket navigation local last_nav = { prefix = nil, -- '[' or ']' key = nil, -- 'c', 'd', 'm', 's', 'q', etc. } --- Setup wrapper mappings to track bracket navigation function M.setup() -- Common bracket pairs to track local pairs = { 'c', -- Git hunks (gitsigns) 'd', -- Diagnostics 's', -- Spelling 'q', -- Quickfix 'l', -- Location list 't', -- Tags 'm', -- Methods (treesitter) 'f', -- Functions (treesitter) 'p', -- Parameters (treesitter) } for _, key in ipairs(pairs) do -- Wrap [x to track vim.keymap.set('n', '[' .. key, function() last_nav.prefix = '[' last_nav.key = key return '[' .. key end, { expr = true, silent = true }) -- Wrap ]x to track vim.keymap.set('n', ']' .. key, function() last_nav.prefix = ']' last_nav.key = key return ']' .. key end, { expr = true, silent = true }) end end --- Repeat last bracket navigation function M.repeat_last() if not last_nav.prefix or not last_nav.key then vim.notify('No bracket navigation to repeat', vim.log.levels.WARN) return end vim.cmd('normal! ' .. last_nav.prefix .. last_nav.key) end --- Reverse last bracket navigation (flip direction) function M.reverse_last() if not last_nav.prefix or not last_nav.key then vim.notify('No bracket navigation to reverse', vim.log.levels.WARN) return end local opposite = last_nav.prefix == '[' and ']' or '[' vim.cmd('normal! ' .. opposite .. last_nav.key) -- Update tracking to reflect the reversal last_nav.prefix = opposite end return M ``` **Integration**: `init.lua` or `lua/keymaps.lua` ```lua -- Setup tracking require('bracket-repeat').setup() -- Keybindings (choose one option) -- Option A: Override ; and , (loses f/F/t/T repeat, but very ergonomic) vim.keymap.set('n', ';', function() require('bracket-repeat').repeat_last() end, { desc = 'Repeat bracket navigation' }) vim.keymap.set('n', ',', function() require('bracket-repeat').reverse_last() end, { desc = 'Reverse bracket navigation' }) -- Option B: Use z prefix (keeps ; and , for f/t motions) vim.keymap.set('n', 'z.', function() require('bracket-repeat').repeat_last() end, { desc = 'Repeat bracket navigation' }) vim.keymap.set('n', 'z,', function() require('bracket-repeat').reverse_last() end, { desc = 'Reverse bracket navigation' }) -- Option C: Use leader vim.keymap.set('n', '.', function() require('bracket-repeat').repeat_last() end, { desc = 'Repeat bracket navigation' }) vim.keymap.set('n', ',', function() require('bracket-repeat').reverse_last() end, { desc = 'Reverse bracket navigation' }) ``` ### Pros & Cons **Pros:** - ✅ Very simple (~40 lines) - ✅ No mode switching, stays in normal mode - ✅ Familiar pattern (like `;`/`,` for `f`/`t`) - ✅ Works with natural workflow - ✅ Easy to add more tracked pairs **Cons:** - ⚠️ Only handles one "type" at a time (can't easily switch between `]d` and `]c`) - ⚠️ Requires calling `.setup()` to track pairs - ⚠️ Might want `;`/`,` for `f`/`t` repeat (depends on keybinding choice) ### Estimated Complexity - **Total**: ~40 lines - **Time**: 15-30 minutes --- ## Option 2: Navigation Mode (getchar Loop) ## User Experience ### Entry - `z[` - Enter navigation mode with `[` prefix active (backward) - `z]` - Enter navigation mode with `]` prefix active (forward) ### In Mode - All letter keys (`a-z`, `A-Z`) get prefixed with current bracket - Examples: - Press `c` → executes `[c` or `]c` (git hunks) - Press `d` → executes `[d` or `]d` (diagnostics) - Press `m` → executes `[m` or `]m` (methods) - Press `f` → executes `[f` or `]f` (functions) - Press `b` → executes `[b` or `]b` (buffers) - Press `q` → executes `[q` or `]q` (quickfix) ### Toggle Prefix - `[` - Switch to `[` prefix (backward navigation) - `]` - Switch to `]` prefix (forward navigation) ### Exit - `` - Exit navigation mode and return to normal editing ### When This Works Best - Switching between **different types** of navigation (`]d` → `]c` → `]m`) - Want visual feedback showing current direction - Prefer a dedicated "navigation state" - Mixed workflow: `]q` to quickfix, then multiple `]c` for hunks (stay in mode, switch keys) ### Example Workflow ```vim " Mixed navigation scenario z] " Enter forward navigation mode (shows: NAV ] →:) q " Execute ]q (next quickfix) c " Execute ]c (next hunk) c " Execute ]c (next hunk) [ " Toggle to backward (shows: NAV [ ←:) c " Execute [c (previous hunk) d " Execute [d (previous diagnostic) " Exit mode ``` ## Technical Implementation ### Approach: getchar() Loop (No Remapping) Instead of remapping keys, use a `while` loop with `vim.fn.getchar()` to read keypresses and manually execute the prefixed commands. **Why this approach:** - ✅ Zero mapping conflicts (never touches existing keymaps) - ✅ Simpler implementation (~50-80 lines vs ~150-250) - ✅ Built-in visual feedback via `vim.api.nvim_echo()` - ✅ Impossible to leak state (no cleanup needed if crashed) - ✅ Predictable behavior (each keypress isolated) - ✅ Naturally handles special keys **The "blocking" behavior is actually desired** - you're in a focused navigation mode, press `` to exit anytime. ### Common Bracket Navigation Pairs - `[c`/`]c` - Previous/next git hunk (gitsigns) - `[d`/`]d` - Previous/next diagnostic - `[b`/`]b` - Previous/next buffer (custom mapping) - `[q`/`]q` - Previous/next quickfix item - `[l`/`]l` - Previous/next location list item - `[m`/`]m` - Previous/next method (treesitter textobjects) - `[f`/`]f` - Previous/next function (treesitter textobjects) - `[p`/`]p` - Previous/next parameter (treesitter textobjects) - `[t`/`]t` - Previous/next tag ## Implementation Details ### Module Structure Create `lua/navigation-mode.lua`: ```lua local M = {} --- Enter navigation mode with getchar() loop --- @param prefix string Either '[' or ']' function M.enter(prefix) prefix = prefix or '[' while true do -- Display prompt local hl = prefix == '[' and 'DiagnosticInfo' or 'DiagnosticHint' local arrow = prefix == '[' and '←' or '→' vim.api.nvim_echo({{string.format('NAV %s %s: ', prefix, arrow), hl}}, false, {}) -- Get next keypress local ok, char = pcall(vim.fn.getchar) if not ok then break end -- Handle gracefully -- Convert to string local key = type(char) == 'number' and vim.fn.nr2char(char) or char -- Handle special keys if key == '\27' then -- ESC break elseif key == '[' then prefix = '[' elseif key == ']' then prefix = ']' else -- Execute prefix + key as normal mode command local cmd = prefix .. key vim.cmd('normal! ' .. vim.api.nvim_replace_termcodes(cmd, true, false, true)) end end -- Clear prompt vim.api.nvim_echo({{'', 'Normal'}}, false, {}) end return M ``` **Key Points:** - No state management needed (self-contained loop) - `pcall(vim.fn.getchar)` handles `` interrupts gracefully - `vim.api.nvim_replace_termcodes()` ensures special key sequences work - Visual feedback built into the loop (shows `NAV [ ←:` or `NAV ] →:`) - Press `[` or `]` to toggle prefix without exiting - Press `` (or ``) to exit ### Integration in keymaps.lua ```lua -- Navigation mode vim.keymap.set('n', 'z[', function() require('navigation-mode').enter('[') end, { desc = 'Enter navigation mode (backward)' }) vim.keymap.set('n', 'z]', function() require('navigation-mode').enter(']') end, { desc = 'Enter navigation mode (forward)' }) ``` ## Visual Feedback Built into the getchar() loop: - Shows `NAV [ ←:` (in blue) when in backward mode - Shows `NAV ] →:` (in teal) when in forward mode - Prompt updates immediately when toggling with `[` or `]` - Clears when exiting with `` No additional statusline integration needed - the command line prompt is clear and non-intrusive. ## Design Decisions ### Mapping Conflicts: Solved No mapping conflicts possible - getchar() reads raw input without touching keymaps. ### Buffer-local Mappings: Not Applicable Loop executes `normal! [key` which uses whatever mappings exist naturally. ### Visual Feedback: Built-in Command line prompt is sufficient and non-intrusive. ### Number Keys Currently not prefixed - this allows using counts if a prefixed command accepts them. Example: `3c` → executes `[3c` or `]3c` (may not be useful, but won't break anything) Could add special handling if needed: ```lua if key:match('^%d$') then -- Handle numbers specially end ``` ### Operators (d, c, y, etc.) In navigation mode, `d` executes `[d` (diagnostic navigation), not delete operator. This is desired behavior - use `` to exit and edit normally. ### Error Handling If a command doesn't exist (e.g., `[z`), Vim will show an error but mode continues. User can press `` to exit or try another key. ### Pros & Cons **Pros:** - ✅ Switch between different navigation types easily (`c` → `d` → `m`) - ✅ Visual feedback (command line prompt) - ✅ No mapping conflicts - ✅ Clean state (nothing to cleanup if interrupted) - ✅ Natural for rapid mixed navigation **Cons:** - ⚠️ Requires mode switching (mental overhead) - ⚠️ Blocking loop (though this is by design) - ⚠️ Need to remember entry/exit keys ### Estimated Complexity - **Total**: ~50-80 lines - **Time**: 30-60 minutes --- ## Comparison & Recommendation | Aspect | Repeat/Reverse | Navigation Mode | |--------|---------------|----------------| | **Best for** | Same-type scanning | Mixed navigation | | **Complexity** | ~40 lines | ~50-80 lines | | **Mental model** | Like `;`/`,` | New mode | | **Typing (4× same nav)** | `]d ...` (4 keys) | `z] dddd ` (9 keys) | | **Typing (mixed nav)** | `]d ]d ]c ]c` (8 keys) | `z] ddcc ` (9 keys) | | **Mode switching** | No | Yes | | **Visual feedback** | No (unless added) | Yes (built-in) | ### Use Cases **Repeat/Reverse excels at:** - Scanning diagnostics: `]d . . . .` - Spell checking: `]s . . . .` - Reviewing quickfix: `]q . . . .` - Going back: `. . . , , ,` **Navigation Mode excels at:** - Mixed workflow: `]q` → multiple `]c` hunks → check `]d` diagnostic - When you want visual confirmation of direction - Exploring unfamiliar code (trying different navigation types) ### Implementation Strategy **Both can coexist!** They solve slightly different problems: 1. **Start with Repeat/Reverse** (simpler, covers 80% of cases) - Use for linear scanning (diagnostics, spelling, quickfix) - Bind to `z.` and `z,` (or `;`/`,` if you don't use `f`/`t` repeat often) 2. **Add Navigation Mode later** (optional, for mixed navigation) - Use when you need to rapidly switch types - Bind to `z[` and `z]` You'll naturally reach for whichever fits the situation better. --- ## Common Bracket Pairs Reference - `[c`/`]c` - Previous/next git hunk (gitsigns) - `[d`/`]d` - Previous/next diagnostic - `[s`/`]s` - Previous/next misspelling - `[q`/`]q` - Previous/next quickfix item - `[l`/`]l` - Previous/next location list item - `[m`/`]m` - Previous/next method (treesitter textobjects) - `[f`/`]f` - Previous/next function (treesitter textobjects) - `[p`/`]p` - Previous/next parameter (treesitter textobjects) - `[t`/`]t` - Previous/next tag - `[b`/`]b` - Previous/next buffer (if custom mapping exists) --- ## Testing Checklists ### Repeat/Reverse Testing - [ ] Setup completes without errors - [ ] `]d` followed by `.` repeats diagnostic navigation - [ ] `]s` followed by `.` repeats spelling navigation - [ ] `]q` followed by `.` repeats quickfix navigation - [ ] `,` reverses last navigation direction - [ ] Warning shown when no navigation to repeat/reverse - [ ] Works across different buffers - [ ] Multiple reverses work: `. . . , , ,` ### Navigation Mode Testing - [ ] Enter mode with `z[` and `z]` - [ ] Verify visual prompt shows `NAV [ ←:` or `NAV ] →:` - [ ] Press `c` → navigates to previous/next git hunk - [ ] Press `d` → navigates to previous/next diagnostic - [ ] Press `m` → navigates to previous/next method - [ ] Press `f` → navigates to previous/next function - [ ] Toggle with `[` → prompt updates to `NAV [ ←:` - [ ] Toggle with `]` → prompt updates to `NAV ] →:` - [ ] Exit with `` → prompt clears, back to normal mode - [ ] Exit with `` → handles gracefully - [ ] Works across different buffers - [ ] Invalid keys (e.g., `z`) show error but don't crash - [ ] Existing keymaps still work after exit --- ## Implementation Priority **Recommended order:** 1. **Phase 1: Repeat/Reverse** (start here) - Simpler, faster to implement - Covers most common use cases - Get immediate value 2. **Phase 2: Navigation Mode** (optional, evaluate need) - Implement only if you find yourself wanting mixed navigation - Can be added anytime without conflicts - Both features work together **Time estimate:** - Phase 1: 15-30 minutes - Phase 2: 30-60 minutes - Total: 45-90 minutes if both implemented --- ## Implementation Steps ### For Repeat/Reverse (Start Here) 1. **Create `lua/bracket-repeat.lua`** (~40 lines) - Implement tracking state - Implement `setup()`, `repeat_last()`, `reverse_last()` 2. **Add to `init.lua`** ```lua require('bracket-repeat').setup() ``` 3. **Add keymaps** (`lua/keymaps.lua`) ```lua vim.keymap.set('n', 'z.', function() require('bracket-repeat').repeat_last() end) vim.keymap.set('n', 'z,', function() require('bracket-repeat').reverse_last() end) ``` 4. **Test** with diagnostics, spelling, quickfix 5. **Document** in README.md ### For Navigation Mode (Optional Later) 1. **Create `lua/navigation-mode.lua`** (~50 lines) - Implement `M.enter(prefix)` with getchar() loop - Handle ESC, `[`, `]`, and general keys - Add visual feedback via `nvim_echo` 2. **Add keymaps** (`lua/keymaps.lua`) ```lua vim.keymap.set('n', 'z[', function() require('navigation-mode').enter('[') end, { desc = 'Navigation mode (backward)' }) vim.keymap.set('n', 'z]', function() require('navigation-mode').enter(']') end, { desc = 'Navigation mode (forward)' }) ``` 3. **Test basic functionality** - Enter with `z[` / `z]` - Navigate with `c`, `d`, `m`, `f`, `q` - Toggle prefix with `[` / `]` - Exit with `` 4. **Polish** (optional) - Add help text on first use - Handle `` gracefully (already done with `pcall`) - Consider adding common navigation cheatsheet 5. **Document** - Update `README.md` with new keymaps - Add navigation pairs reference