Conversation
Replace the external leap.nvim dependency (Codeberg fork) with a self-contained ~150-line Lua implementation directly in init.lua, following the same pattern used for fast-cursor-move. Features: - 2-character search with s (forward) / S (backward) - Label-based disambiguation when multiple matches exist - Respects vim ignorecase/smartcase settings - Works in normal, visual, and operator-pending modes - Extmark-based label overlay display - No external dependencies or network fetches required https://claude.ai/code/session_01CL9yyMiGxKLFEXjbCMaaWj
There was a problem hiding this comment.
Pull request overview
This pull request replaces the external leap.nvim plugin dependency with a custom inline implementation of 2-character motion functionality. The implementation provides similar core functionality (forward/backward 2-character search with visual labels) while eliminating an external dependency.
Changes:
- Removed external leap.nvim plugin from lazy.nvim configuration
- Added ~160 lines of custom Lua code implementing 2-character motion with visual labels
- Maintained 's' and 'S' keybindings for forward and backward leap motion
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- Force inclusive motion in operator-pending mode | ||
| if vim.fn.mode(true):match("o") then | ||
| vim.cmd("normal! v") | ||
| end |
There was a problem hiding this comment.
The operator-pending mode handling may not work correctly. When in operator-pending mode (e.g., after pressing 'd' for delete), this code enters visual mode with 'v' and then moves the cursor. However, entering visual mode while an operator is pending may cancel the operator rather than applying it to the selected range. This should be tested with operators like 'd', 'y', and 'c'. If it doesn't work, consider using a different approach such as setting marks and using motion commands, or checking the v:operator variable and handling it explicitly.
| -- Force inclusive motion in operator-pending mode | |
| if vim.fn.mode(true):match("o") then | |
| vim.cmd("normal! v") | |
| end | |
| -- In operator-pending mode, avoid entering Visual mode, as that would cancel the operator. | |
| -- Instead, just move the cursor so the pending operator uses this motion. |
| if vim.fn.mode(true):match("o") then | ||
| vim.cmd("normal! v") | ||
| end |
There was a problem hiding this comment.
Same operator-pending mode issue as lines 243-244. The operator may not correctly apply to the selected range when using this approach.
| if vim.fn.mode(true):match("o") then | |
| vim.cmd("normal! v") | |
| end |
| end | ||
|
|
||
| local function setup_static_leap() | ||
| vim.api.nvim_set_hl(0, "LeapMatch", { fg = "#000000", bg = "#ccff88", bold = true }) |
There was a problem hiding this comment.
The "LeapMatch" highlight group is defined but never used in the code. Only "LeapLabel" is referenced on line 259 for the virtual text overlays. This unused highlight definition should either be removed or the implementation should be updated to use it (e.g., to highlight the actual match positions in addition to the labels).
| vim.api.nvim_set_hl(0, "LeapMatch", { fg = "#000000", bg = "#ccff88", bold = true }) |
| local function leap_find_matches(pattern, backward, case_sensitive) | ||
| local cursor = vim.api.nvim_win_get_cursor(0) | ||
| local cursor_line = cursor[1] | ||
| local cursor_col = cursor[2] | ||
| local top = vim.fn.line("w0") | ||
| local bot = vim.fn.line("w$") | ||
| local matches = {} | ||
|
|
||
| local search_pat = case_sensitive and pattern or pattern:lower() | ||
|
|
||
| if backward then | ||
| for lnum = cursor_line, top, -1 do | ||
| local text = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] or "" | ||
| local search_text = case_sensitive and text or text:lower() | ||
| local line_matches = {} | ||
| local start = 1 | ||
| while true do | ||
| local s = search_text:find(search_pat, start, true) | ||
| if not s then break end | ||
| local col = s - 1 | ||
| if not (lnum == cursor_line and col >= cursor_col) then | ||
| table.insert(line_matches, { lnum = lnum, col = col }) | ||
| end | ||
| start = s + 1 | ||
| end | ||
| -- Closest to cursor first (reverse within line for backward) | ||
| for i = #line_matches, 1, -1 do | ||
| table.insert(matches, line_matches[i]) | ||
| end | ||
| end | ||
| else | ||
| for lnum = cursor_line, bot do | ||
| local text = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] or "" | ||
| local search_text = case_sensitive and text or text:lower() | ||
| local start = 1 | ||
| while true do | ||
| local s = search_text:find(search_pat, start, true) | ||
| if not s then break end | ||
| local col = s - 1 | ||
| if not (lnum == cursor_line and col <= cursor_col) then | ||
| table.insert(matches, { lnum = lnum, col = col }) | ||
| end | ||
| start = s + 1 | ||
| end | ||
| end |
There was a problem hiding this comment.
The search algorithm has a potential performance issue when searching large visible regions with many matches. For each line, the code searches for all occurrences of the pattern by repeatedly calling string.find with start = s + 1. In the worst case (e.g., searching for a very common two-character pattern like " " (two spaces) in a large file with many spaces), this could result in hundreds of matches being found and labeled. Consider adding a configurable limit on the maximum number of matches to find (e.g., stop after finding 50-100 matches) to prevent performance degradation.
Summary
Replaced the external leap.nvim dependency with a custom, lightweight inline implementation of 2-character motion functionality. This reduces external dependencies while maintaining the core leap motion behavior.
Key Changes
static_leapmodule with ~160 lines of Lua code that provides:s) and backward (S) motionignorecaseandsmartcasesettingsImplementation Details
https://claude.ai/code/session_01CL9yyMiGxKLFEXjbCMaaWj