diff --git a/lua/jumpy/diff.lua b/lua/jumpy/diff.lua index a1a7f08..3548c38 100644 --- a/lua/jumpy/diff.lua +++ b/lua/jumpy/diff.lua @@ -77,7 +77,7 @@ local function myers_diff(old_lines, new_lines) if d > 1 then if x == prev_x then - table.insert(edits, 1, { op = "insert", new_idx = y }) + table.insert(edits, 1, { op = "insert", old_idx = x + 1, new_idx = y }) y = y - 1 else table.insert(edits, 1, { op = "delete", old_idx = x }) @@ -120,6 +120,9 @@ function M.compute(old_lines, new_lines) current_hunk.old_count = current_hunk.old_count + 1 table.insert(current_hunk.removed_lines, old_lines[edit.old_idx]) elseif edit.op == "insert" then + if not current_hunk.old_start then + current_hunk.old_start = edit.old_idx + end if not current_hunk.new_start then current_hunk.new_start = edit.new_idx end diff --git a/lua/jumpy/navigate.lua b/lua/jumpy/navigate.lua index a938cbd..941ebc1 100644 --- a/lua/jumpy/navigate.lua +++ b/lua/jumpy/navigate.lua @@ -5,6 +5,23 @@ local render = require("jumpy.render") local MSG_NO_HUNKS = "jumpy: no hunks" local MSG_NO_HUNK_UNDER_CURSOR = "jumpy: no hunk under cursor" +local function center_cursor() + vim.cmd("normal! zz") +end + +local function set_cursor_centered(line) + local line_count = vim.api.nvim_buf_line_count(0) + local target_line = math.min(math.max(line, 1), line_count) + vim.api.nvim_win_set_cursor(0, { target_line, 0 }) + center_cursor() +end + +local function current_hunk_line(bufnr, idx, hunk) + local line = hunk.old_start + M._get_offset(bufnr, idx) + local line_count = vim.api.nvim_buf_line_count(bufnr) + return math.min(math.max(line, 1), line_count) +end + local function get_active_hunks(bufnr) local state = render.get_state(bufnr) if not state then @@ -32,8 +49,8 @@ function M.hunk_at_cursor() for _, entry in ipairs(active) do local hunk = entry.hunk - local hunk_start = hunk.old_start - local hunk_end = hunk.old_start + math.max(hunk.old_count, 1) - 1 + local hunk_start = current_hunk_line(bufnr, entry.idx, hunk) + local hunk_end = hunk_start + math.max(hunk.old_count, 1) - 1 if cursor_line >= hunk_start and cursor_line <= hunk_end then return entry.idx @@ -54,13 +71,14 @@ function M.next_hunk() end for _, entry in ipairs(active) do - if entry.hunk.old_start > cursor_line then - vim.api.nvim_win_set_cursor(0, { entry.hunk.old_start, 0 }) + local hunk_line = current_hunk_line(bufnr, entry.idx, entry.hunk) + if hunk_line > cursor_line then + set_cursor_centered(hunk_line) return end end - vim.api.nvim_win_set_cursor(0, { active[1].hunk.old_start, 0 }) + set_cursor_centered(current_hunk_line(bufnr, active[1].idx, active[1].hunk)) end function M.prev_hunk() @@ -74,13 +92,14 @@ function M.prev_hunk() end for i = #active, 1, -1 do - if active[i].hunk.old_start < cursor_line then - vim.api.nvim_win_set_cursor(0, { active[i].hunk.old_start, 0 }) + local hunk_line = current_hunk_line(bufnr, active[i].idx, active[i].hunk) + if hunk_line < cursor_line then + set_cursor_centered(hunk_line) return end end - vim.api.nvim_win_set_cursor(0, { active[#active].hunk.old_start, 0 }) + set_cursor_centered(current_hunk_line(bufnr, active[#active].idx, active[#active].hunk)) end function M.accept() @@ -267,7 +286,7 @@ local function jump_to(bufnr, line) end vim.cmd("normal! m'") vim.api.nvim_win_set_buf(0, bufnr) - vim.api.nvim_win_set_cursor(0, { line, 0 }) + set_cursor_centered(line) end function M.first_hunk_any_buf() @@ -285,12 +304,13 @@ function M._advance_to_next(bufnr) if #active > 0 then local cursor_line = vim.api.nvim_win_get_cursor(0)[1] for _, entry in ipairs(active) do - if entry.hunk.old_start >= cursor_line then - vim.api.nvim_win_set_cursor(0, { entry.hunk.old_start, 0 }) + local hunk_line = current_hunk_line(bufnr, entry.idx, entry.hunk) + if hunk_line >= cursor_line then + set_cursor_centered(hunk_line) return end end - vim.api.nvim_win_set_cursor(0, { active[1].hunk.old_start, 0 }) + set_cursor_centered(current_hunk_line(bufnr, active[1].idx, active[1].hunk)) else offset_table[bufnr] = nil local all = get_all_active_hunks() diff --git a/lua/jumpy/render.lua b/lua/jumpy/render.lua index 9690f3b..6bf7964 100644 --- a/lua/jumpy/render.lua +++ b/lua/jumpy/render.lua @@ -10,6 +10,13 @@ local function build_virt_lines(added_lines) return virt_lines end +local function insertion_anchor(bufnr, old_start) + local line_count = vim.api.nvim_buf_line_count(bufnr) + local is_eof = old_start > line_count + local anchor_line = is_eof and line_count - 1 or old_start - 1 + return math.max(0, anchor_line), not is_eof +end + local buf_states = {} function M.get_state(bufnr) @@ -89,12 +96,11 @@ function M.show(bufnr, hunks, original_lines, proposed_lines) local virt_lines = build_virt_lines(hunk.added_lines) - local anchor_line = math.max(0, hunk.old_start - 1) - anchor_line = math.min(anchor_line, vim.api.nvim_buf_line_count(bufnr) - 1) + local anchor_line, virt_lines_above = insertion_anchor(bufnr, hunk.old_start) local id = vim.api.nvim_buf_set_extmark(bufnr, ns, anchor_line, 0, { virt_lines = virt_lines, - virt_lines_above = anchor_line ~= 0, + virt_lines_above = virt_lines_above, sign_text = "+", sign_hl_group = "JumpyAddedSign", priority = 100, @@ -179,17 +185,17 @@ function M.update_hunk_lines(bufnr, hunk_idx, new_added_lines) local virt_lines = build_virt_lines(new_added_lines) local anchor_line + local virt_lines_above = false if hunk.old_count > 0 then anchor_line = math.min(hunk.old_start - 1 + hunk.old_count - 1, vim.api.nvim_buf_line_count(bufnr) - 1) anchor_line = math.max(0, anchor_line) else - anchor_line = math.max(0, hunk.old_start - 1) - anchor_line = math.min(anchor_line, vim.api.nvim_buf_line_count(bufnr) - 1) + anchor_line, virt_lines_above = insertion_anchor(bufnr, hunk.old_start) end local id = vim.api.nvim_buf_set_extmark(bufnr, ns, anchor_line, 0, { virt_lines = virt_lines, - virt_lines_above = hunk.old_count == 0 and anchor_line ~= 0, + virt_lines_above = virt_lines_above, priority = 100, }) table.insert(hunk.extmarks, id) diff --git a/tests/diff_spec.lua b/tests/diff_spec.lua index 0c8913a..9563d8b 100644 --- a/tests/diff_spec.lua +++ b/tests/diff_spec.lua @@ -89,6 +89,7 @@ describe("diff.compute", function() local hunks = diff.compute(old, new) assert.are.equal(1, #hunks) + assert.are.equal(1, hunks[1].old_start) assert.are.same({ "a" }, hunks[1].added_lines) end) @@ -98,9 +99,45 @@ describe("diff.compute", function() local hunks = diff.compute(old, new) assert.are.equal(1, #hunks) + assert.are.equal(3, hunks[1].old_start) assert.are.same({ "c" }, hunks[1].added_lines) end) + it("keeps repeated insertion hunks anchored to old-file lines", function() + local old = { + "local function one()", + "end", + "", + "local function two()", + "end", + "", + "local function three()", + "end", + } + local new = { + "--- One.", + "local function one()", + "end", + "", + "--- Two.", + "local function two()", + "end", + "", + "--- Three.", + "local function three()", + "end", + } + local hunks = diff.compute(old, new) + + assert.are.equal(3, #hunks) + assert.are.equal(1, hunks[1].old_start) + assert.are.equal(4, hunks[2].old_start) + assert.are.equal(7, hunks[3].old_start) + assert.are.same({ "--- One." }, hunks[1].added_lines) + assert.are.same({ "--- Two." }, hunks[2].added_lines) + assert.are.same({ "--- Three." }, hunks[3].added_lines) + end) + it("handles complete replacement", function() local old = { "a", "b" } local new = { "x", "y", "z" }