Skip to content

Replace leap.nvim with lightweight inline 2-character motion plugin#6

Merged
wardnath merged 1 commit intomainfrom
claude/port-leap-nvim-static-a7Z0L
Feb 26, 2026
Merged

Replace leap.nvim with lightweight inline 2-character motion plugin#6
wardnath merged 1 commit intomainfrom
claude/port-leap-nvim-static-a7Z0L

Conversation

@wardnath
Copy link
Owner

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

  • Removed external dependency: Deleted the leap.nvim plugin configuration from the lazy.nvim setup
  • Added inline implementation: Created a complete static_leap module with ~160 lines of Lua code that provides:
    • 2-character pattern matching with forward (s) and backward (S) motion
    • Support for vim's ignorecase and smartcase settings
    • Visual label overlays for multiple matches using extmarks
    • Direct jump for single matches
    • Operator-pending mode support for motions in commands
  • Deferred initialization: Setup runs after 500ms to ensure proper initialization

Implementation Details

  • Uses vim's native extmark API for rendering match labels with custom highlighting
  • Implements case-sensitive search logic respecting vim's case settings
  • Provides visual feedback with "LeapMatch" (green) and "LeapLabel" (pink) highlights
  • Searches within the visible window bounds for performance
  • Handles edge cases like empty input (ESC) and no matches gracefully
  • Maintains compatibility with normal, visual, and operator-pending modes

https://claude.ai/code/session_01CL9yyMiGxKLFEXjbCMaaWj

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
Copilot AI review requested due to automatic review settings February 26, 2026 22:52
@wardnath wardnath merged commit 75e81d0 into main Feb 26, 2026
3 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +242 to +245
-- Force inclusive motion in operator-pending mode
if vim.fn.mode(true):match("o") then
vim.cmd("normal! v")
end
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
-- 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.

Copilot uses AI. Check for mistakes.
Comment on lines +275 to +277
if vim.fn.mode(true):match("o") then
vim.cmd("normal! v")
end
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same operator-pending mode issue as lines 243-244. The operator may not correctly apply to the selected range when using this approach.

Suggested change
if vim.fn.mode(true):match("o") then
vim.cmd("normal! v")
end

Copilot uses AI. Check for mistakes.
end

local function setup_static_leap()
vim.api.nvim_set_hl(0, "LeapMatch", { fg = "#000000", bg = "#ccff88", bold = true })
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
vim.api.nvim_set_hl(0, "LeapMatch", { fg = "#000000", bg = "#ccff88", bold = true })

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +193
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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants