From 6d4654e70afdfcb9fb6b6d28bdd5f9eb4d0be6b8 Mon Sep 17 00:00:00 2001 From: Cesar Enrique Ramirez Date: Sun, 2 Aug 2026 17:36:02 +0200 Subject: [PATCH 1/2] fix(compact): align zero-width hunk context --- lua/codediff/ui/view/compact.lua | 8 +++----- tests/ui/view/compact_spec.lua | 26 +++++++++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lua/codediff/ui/view/compact.lua b/lua/codediff/ui/view/compact.lua index ce60c409..23154dfb 100644 --- a/lua/codediff/ui/view/compact.lua +++ b/lua/codediff/ui/view/compact.lua @@ -79,11 +79,9 @@ function M.compute_visible_lines(changes, side, line_count, context_lines) local range_start = range.start_line local range_end = range.end_line -- exclusive - -- For zero-width ranges (pure insertion/deletion), use start_line as anchor - if range_start == range_end then - range_end = range_start + 1 - end - + -- A zero-width range is a boundary between unchanged lines. The formula + -- below therefore selects `context_lines` real lines on either side + -- without inventing a changed anchor line on the empty side. local ctx_start = math.max(1, range_start - context_lines) local ctx_end = math.min(line_count, range_end - 1 + context_lines) for l = ctx_start, ctx_end do diff --git a/tests/ui/view/compact_spec.lua b/tests/ui/view/compact_spec.lua index 91b8bbcd..9e1013bb 100644 --- a/tests/ui/view/compact_spec.lua +++ b/tests/ui/view/compact_spec.lua @@ -125,16 +125,16 @@ describe("compact mode (#344)", function() assert.is_nil(visible[11], "buffer only has 10 lines") end) - it("treats zero-width ranges (pure insertion/deletion) as a one-line anchor", function() - -- Zero-width range at line 5 — gets treated as the one-line slice [5,6). - -- With context=2: visible = max(1, 5-2)..min(N, 6-1+2) = 3..7. + it("shows context on both sides of zero-width ranges without an extra anchor line", function() + -- A zero-width range is a boundary between lines, not a changed line. + -- With context=2, show two real lines on either side: 3..6. local changes = { make_change(5, 5, 5, 8) } local visible = compact.compute_visible_lines(changes, "original", 100, 2) - for l = 3, 7 do + for l = 3, 6 do assert.is_true(visible[l], "line " .. l .. " should be visible") end assert.is_nil(visible[2]) - assert.is_nil(visible[8]) + assert.is_nil(visible[7]) end) it("merges overlapping hunks' visible ranges via set union", function() @@ -162,6 +162,8 @@ describe("compact mode (#344)", function() -- Original side: anchor at 5, ±2 context → 3..6. assert.is_true(orig_visible[5]) assert.is_true(orig_visible[3]) + assert.is_true(orig_visible[6]) + assert.is_nil(orig_visible[7]) assert.is_nil(orig_visible[10]) -- Modified side: hunk 10..12, ±2 context → 8..14. @@ -170,6 +172,20 @@ describe("compact mode (#344)", function() assert.is_nil(mod_visible[5]) end) + it("aligns the first folded line after a pure insertion", function() + local changes = { make_change(50, 50, 60, 61) } + local original = compact.compute_visible_lines(changes, "original", 100, 3) + local modified = compact.compute_visible_lines(changes, "modified", 110, 3) + + -- Context ends at 52/63, so the next folds begin at corresponding + -- unchanged lines 53/64. Before the fix, the original fold began at 54. + assert.is_true(original[52]) + assert.is_true(modified[63]) + assert.is_nil(original[53]) + assert.is_nil(modified[64]) + assert.equal(64, compact.compute_corresponding_lnum(changes, "original", "modified", 53)) + end) + it("supports zero context (only the hunk lines visible)", function() local changes = { make_change(10, 13, 10, 13) } local visible = compact.compute_visible_lines(changes, "original", 100, 0) From 053c99c67cc277ac3fd2d798ffa6df03e780a200 Mon Sep 17 00:00:00 2001 From: Cesar Enrique Ramirez Date: Mon, 3 Aug 2026 11:35:51 +0200 Subject: [PATCH 2/2] pr-feedback: remove comment not longer relevant --- lua/codediff/ui/view/compact.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lua/codediff/ui/view/compact.lua b/lua/codediff/ui/view/compact.lua index 23154dfb..34e3c0c7 100644 --- a/lua/codediff/ui/view/compact.lua +++ b/lua/codediff/ui/view/compact.lua @@ -79,9 +79,6 @@ function M.compute_visible_lines(changes, side, line_count, context_lines) local range_start = range.start_line local range_end = range.end_line -- exclusive - -- A zero-width range is a boundary between unchanged lines. The formula - -- below therefore selects `context_lines` real lines on either side - -- without inventing a changed anchor line on the empty side. local ctx_start = math.max(1, range_start - context_lines) local ctx_end = math.min(line_count, range_end - 1 + context_lines) for l = ctx_start, ctx_end do @@ -96,7 +93,17 @@ end -- closes folds — navigation keys like zj/zk/]z/[z are excluded since they -- don't change fold state). local FOLD_KEYS = { - "zo", "zO", "zc", "zC", "za", "zA", "zv", "zx", "zX", "zM", "zR", + "zo", + "zO", + "zc", + "zC", + "za", + "zA", + "zv", + "zx", + "zX", + "zM", + "zR", } --- Install buffer-local keymap wraps that propagate fold-open/close actions @@ -124,8 +131,7 @@ local function setup_fold_sync(session, tabpage) lifecycle.begin_keymap_scope(tabpage, "compact") for _, pane in ipairs(panes) do - if pane.win and vim.api.nvim_win_is_valid(pane.win) - and pane.buf and vim.api.nvim_buf_is_valid(pane.buf) then + if pane.win and vim.api.nvim_win_is_valid(pane.win) and pane.buf and vim.api.nvim_buf_is_valid(pane.buf) then for _, key in ipairs(FOLD_KEYS) do lifecycle.set_buf_keymap(tabpage, pane.buf, "n", key, function() local count = vim.v.count > 0 and tostring(vim.v.count) or "" @@ -151,8 +157,7 @@ local function setup_fold_sync(session, tabpage) _syncing = true local ok, err = pcall(function() for _, other in ipairs(panes) do - if other.win ~= pane.win - and other.win and vim.api.nvim_win_is_valid(other.win) then + if other.win ~= pane.win and other.win and vim.api.nvim_win_is_valid(other.win) then local target = M.compute_corresponding_lnum(changes, pane.side, other.side, src_lnum) target = math.max(1, math.min(target, vim.api.nvim_buf_line_count(other.buf))) -- Save & restore cursor so the partner pane doesn't visibly jump.