nvim/NAVIGATION_MODE_PLAN.md

6.1 KiB

Navigation Mode Feature Plan

Concept

A custom navigation mode where keypresses are automatically prefixed with [ or ], making it easier to navigate using Neovim's bracket-based navigation pairs without repeatedly typing brackets.

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

  • <Esc> - Exit navigation mode and restore normal mappings

Technical Implementation

State Management

local NavMode = {
  active = false,
  prefix = '[',  -- '[' or ']'
}

Key Remapping Strategy

  1. Store original mappings for letters a-z, A-Z
  2. On mode entry, create new mappings: key → prefix .. key
  3. On mode exit, restore original mappings
  4. On prefix toggle, update all mappings with new prefix

Keys to Remap

  • Lowercase: a-z (26 keys)
  • Uppercase: A-Z (26 keys)
  • Total: 52 keys dynamically remapped

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:

local M = {}

local state = {
  active = false,
  prefix = '[',
  stored_mappings = {},
}

local LETTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

function M.enter(prefix)
  if state.active then return end
  
  state.prefix = prefix or '['
  state.active = true
  
  -- Store original mappings and create prefixed ones
  for i = 1, #LETTERS do
    local key = LETTERS:sub(i, i)
    -- Store original mapping (if exists)
    -- Create new mapping: key → prefix .. key
    vim.keymap.set('n', key, state.prefix .. key, { noremap = true, silent = true })
  end
  
  -- Special mappings for [ and ] to toggle prefix
  vim.keymap.set('n', '[', function() M.set_prefix('[') end, { noremap = true, silent = true })
  vim.keymap.set('n', ']', function() M.set_prefix(']') end, { noremap = true, silent = true })
  
  -- Exit mapping
  vim.keymap.set('n', '<Esc>', function() M.exit() end, { noremap = true, silent = true })
  
  -- TODO: Set visual feedback (statusline, notification, etc.)
end

function M.set_prefix(new_prefix)
  if not state.active then return end
  state.prefix = new_prefix
  
  -- Remap all letters with new prefix
  for i = 1, #LETTERS do
    local key = LETTERS:sub(i, i)
    vim.keymap.set('n', key, state.prefix .. key, { noremap = true, silent = true })
  end
  
  -- TODO: Update visual feedback
end

function M.exit()
  if not state.active then return end
  
  -- Restore original mappings
  for i = 1, #LETTERS do
    local key = LETTERS:sub(i, i)
    vim.keymap.del('n', key)
    -- Restore stored mapping if it existed
  end
  
  -- Clean up special mappings
  vim.keymap.del('n', '[')
  vim.keymap.del('n', ']')
  vim.keymap.del('n', '<Esc>')
  
  state.active = false
  
  -- TODO: Clear visual feedback
end

return M

Integration in keymaps.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 Options (TODO)

Need to decide on one or more:

  1. Statusline indicator: Show [NAV ←] or [NAV →] in statusline
  2. Notification: Brief message on mode entry/exit
  3. Command line: echo message showing current prefix
  4. Cursor highlight: Change cursor color/shape
  5. Virtual text: Floating indicator in corner of window

Open Questions

  1. Mapping conflicts: How to handle if a letter already has a mapping?

    • Overwrite temporarily?
    • Skip that letter?
    • Warn user?
  2. Buffer-local mappings: Should mode respect buffer-local mappings?

    • Store and restore per-buffer?
    • Global mode only?
  3. Visual feedback: Which approach is clearest without being intrusive?

  4. Number keys: Should 0-9 also be prefixed?

    • Useful for some navigation pairs
    • But might conflict with counts
  5. Operators: Should d, c, y still work as operators or only as navigation?

    • Current plan: They become navigation only while in mode
    • Trade-off: Can't delete/change while navigating

Implementation Phases

  1. Core functionality (essential)

    • Mode enter/exit
    • Key remapping for a-z
    • Prefix toggle
    • Basic state management
  2. Visual feedback (important)

    • Choose and implement feedback mechanism
    • Ensure clarity when mode is active
  3. Edge cases (polish)

    • Handle existing mappings gracefully
    • Buffer-local mapping preservation
    • Mode timeout/auto-exit?
  4. Documentation (completion)

    • Update README.md with keymaps
    • Add examples of useful navigation pairs
    • Consider adding to help docs

Estimated Complexity

  • Core: ~100-150 lines of Lua
  • Visual feedback: +20-50 lines depending on approach
  • Edge case handling: +30-50 lines
  • Total: 150-250 lines

Testing Checklist

  • Enter mode with z[ and z]
  • Verify letter keys are prefixed correctly
  • Toggle between [ and ] prefix
  • Exit cleanly with <Esc>
  • No residual mappings after exit
  • Works across different buffers
  • Visual feedback is clear
  • Common navigation pairs work (c, d, m, f, q)