Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 157 additions & 7 deletions chezmoi/dot_config/nvim/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,162 @@ end
vim.defer_fn(setup_fast_cursor_move, 500)


--------------------------------------------------------------------------------
-- Static Leap: lightweight 2-character motion plugin (inline, no dependencies)
-- Inspired by leap.nvim - press s/S + 2 chars to jump to a match
--------------------------------------------------------------------------------

local leap_ns = vim.api.nvim_create_namespace("static_leap")
local leap_labels = "sfnjklhodweimbuyvrgtaqpcxz"

local function leap_get_input()
local ok, ch = pcall(vim.fn.getcharstr)
if not ok or ch == "\27" or ch == "" then return nil end
return ch
end

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
Comment on lines +149 to +193
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.
end

return matches
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.
vim.api.nvim_set_hl(0, "LeapLabel", { fg = "#ffffff", bg = "#ff2299", bold = true })

local function do_leap(backward)
-- Prompt for first character
vim.api.nvim_echo({ { "leap>", "Comment" }, { " ", "Normal" } }, false, {})
local ch1 = leap_get_input()
if not ch1 then
vim.api.nvim_echo({ { "", "Normal" } }, false, {})
return
end

-- Prompt for second character
vim.api.nvim_echo({ { "leap>" .. ch1, "Comment" }, { " ", "Normal" } }, false, {})
local ch2 = leap_get_input()
if not ch2 then
vim.api.nvim_echo({ { "", "Normal" } }, false, {})
return
end

local pattern = ch1 .. ch2

-- Respect vim case settings
local case_sensitive = true
if vim.o.ignorecase then
if vim.o.smartcase then
case_sensitive = pattern:find("[A-Z]") ~= nil
else
case_sensitive = false
end
end

local matches = leap_find_matches(pattern, backward, case_sensitive)
vim.api.nvim_echo({ { "", "Normal" } }, false, {})

if #matches == 0 then
vim.api.nvim_echo({ { "No matches", "WarningMsg" } }, false, {})
return
end

-- Single match: jump directly
if #matches == 1 then
-- Force inclusive motion in operator-pending mode
if vim.fn.mode(true):match("o") then
vim.cmd("normal! v")
end
Comment on lines +242 to +245
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.
vim.api.nvim_win_set_cursor(0, { matches[1].lnum, matches[1].col })
return
end

-- Multiple matches: show labels as extmark overlays
local buf = vim.api.nvim_get_current_buf()
local label_map = {}

for i, match in ipairs(matches) do
if i > #leap_labels then break end
local label = leap_labels:sub(i, i)
label_map[label] = match
vim.api.nvim_buf_set_extmark(buf, leap_ns, match.lnum - 1, match.col, {
virt_text = { { label, "LeapLabel" } },
virt_text_pos = "overlay",
priority = 1000,
})
end

vim.cmd("redraw")

-- Get label selection
local selected = leap_get_input()

-- Clear all labels
vim.api.nvim_buf_clear_namespace(buf, leap_ns, 0, -1)
vim.cmd("redraw")

if selected and label_map[selected] then
if vim.fn.mode(true):match("o") then
vim.cmd("normal! v")
end
Comment on lines +275 to +277
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.
vim.api.nvim_win_set_cursor(0, { label_map[selected].lnum, label_map[selected].col })
end
end

vim.keymap.set({ "n", "x", "o" }, "s", function() do_leap(false) end,
{ silent = true, desc = "Leap forward" })
vim.keymap.set({ "n", "x", "o" }, "S", function() do_leap(true) end,
{ silent = true, desc = "Leap backward" })
end

vim.defer_fn(setup_static_leap, 500)


--------------------------------------------------------------------------------
-- Lazy.nvim and other plugin configurations
--------------------------------------------------------------------------------
Expand Down Expand Up @@ -267,13 +423,7 @@ require("lazy").setup({
})
end
},
{
url = "https://codeberg.org/andyg/Leap.nvim",
event = "VeryLazy",
config = function()
require("leap").add_default_mappings()
end,
},
-- leap.nvim replaced by static inline implementation above (no external dependency)
{
"karb94/neoscroll.nvim",
event = "VeryLazy",
Expand Down