diff --git a/lua/diffview/scene/layouts/diff_1_inline.lua b/lua/diffview/scene/layouts/diff_1_inline.lua index cfa24c68..1affd7ad 100644 --- a/lua/diffview/scene/layouts/diff_1_inline.lua +++ b/lua/diffview/scene/layouts/diff_1_inline.lua @@ -23,17 +23,19 @@ local M = {} ---`'diffopt'` forces `vim.diff` to disable the heuristic instead of falling ---back to whatever default the vim.diff implementation currently uses. --- ----`linematch` is intentionally not forwarded. In `result_type = "indices"` ----mode it splits a single modify-hunk into smaller hunks that pair lines by ----similarity rather than by position — e.g. an old `-- foo = X, -- comment` ----line gets paired with a new `-- bar = Y, -- comment` line further down ----because both share the `-- ` prefix and a `, -- ` separator, even though ----the natural (positional) pair is the new uncommented `foo = X` line. The ----inline renderer pairs lines positionally inside each hunk, so a non-zero ----linematch causes deletions to render anchored against the wrong new line. +---`linematch` defaults to 60 (git's default) so a single modify-hunk whose +---old and new sides differ in length is split into properly-aligned +---sub-hunks. Without it, the renderer pairs lines positionally inside the +---one large hunk, so e.g., commenting-out a block (8 old lines vs 9 new +---lines: a new TEMP header plus 8 lines each gaining a `-- ` prefix) pairs +---every `vim.api.nvim_set_hl(...)` line with the wrong commented copy, +---producing > `INTRALINE_MAX_HUNKS` per pair and falling back to whole-line +---add+delete instead of showing only the `-- ` insertion. The user's +---`'diffopt'` `linematch:N` entry overrides this default (including `0` to +---opt back into positional pairing). ---@return InlineDiffOpts local function effective_diffopt() - local out = { indent_heuristic = false } + local out = { indent_heuristic = false, linematch = 60 } local diffopt = vim.opt.diffopt --[[@as vim.Option]] for _, v in ipairs(diffopt:get() --[[@as string[] ]]) @@ -41,6 +43,8 @@ local function effective_diffopt() local key, val = v:match("^([%w_-]+):(.+)$") if key == "algorithm" then out.algorithm = val + elseif key == "linematch" then + out.linematch = tonumber(val) elseif v == "indent-heuristic" then out.indent_heuristic = true elseif v == "iwhite" then diff --git a/lua/diffview/tests/functional/inline_diff_spec.lua b/lua/diffview/tests/functional/inline_diff_spec.lua index 937efbeb..5e25e16e 100644 --- a/lua/diffview/tests/functional/inline_diff_spec.lua +++ b/lua/diffview/tests/functional/inline_diff_spec.lua @@ -519,6 +519,56 @@ describe("diffview.scene.inline_diff", function() assert.are.equal("DiffviewDiffDeleteInline", h) end end) + + -- Commenting-out a block prepends `-- ` to N lines and inserts a header + -- line above them, producing an N:N+1 modify hunk. With positional + -- pairing inside one hunk the new header pairs with the first old line + -- and every subsequent pair becomes a shifted, dissimilar pair — so the + -- char-level diff fragments, returns `"skipped"`, and the block renders + -- as a full add+delete instead of inline `-- ` insertions. Linematch + -- splits the hunk into a pure-add for the header plus an aligned N:N + -- modify, which the renderer can pair positionally without skipping. + it("renders a commented-out block as inline `-- ` insertions with linematch", function() + local bufnr = fresh_buf({ + "-- TEMP: disable overrides.", + "-- vim.api.nvim_set_hl(0, 'A')", + "-- vim.api.nvim_set_hl(0, 'B')", + }) + inline_diff.render(bufnr, { "vim.api.nvim_set_hl(0, 'A')", "vim.api.nvim_set_hl(0, 'B')" }, { + "-- TEMP: disable overrides.", + "-- vim.api.nvim_set_hl(0, 'A')", + "-- vim.api.nvim_set_hl(0, 'B')", + }, { style = "overleaf", linematch = 60 }) + + local marks = extmarks(bufnr) + -- The header is a pure addition and should be the only row carrying a + -- full `DiffviewDiffAdd` line highlight; the commented lines below + -- must NOT pick up that backdrop (which is the symptom of the + -- positional-pairing fallback). + local add_rows = {} + for _, h in ipairs(line_hls(marks)) do + if h.hl == "DiffviewDiffAdd" then + add_rows[#add_rows + 1] = h.row + end + end + assert.are.same({ 0 }, add_rows) + + -- Each commented line carries a `DiffviewDiffAddInline` extmark over + -- the inserted `-- ` prefix. + local adds = char_ranges(marks, "DiffviewDiffAddInline") + local rows_with_add = {} + for _, a in ipairs(adds) do + rows_with_add[a.row] = true + end + assert.is_true(rows_with_add[1], "expected inline add on row 1") + assert.is_true(rows_with_add[2], "expected inline add on row 2") + + -- No paired old lines are echoed as deletion virt_lines (the + -- skipped-fallback symptom): only the inline strikethrough virt_text + -- representing the empty old prefix would appear, and even that is + -- empty for a pure prefix insertion. + assert.are.same({}, virt_line_counts(marks)) + end) end) describe("deletion_highlight", function() diff --git a/lua/diffview/tests/functional/layouts_spec.lua b/lua/diffview/tests/functional/layouts_spec.lua index 5896fcbc..8efb67f9 100644 --- a/lua/diffview/tests/functional/layouts_spec.lua +++ b/lua/diffview/tests/functional/layouts_spec.lua @@ -1769,14 +1769,24 @@ describe("diffview.scene.layouts.diff_1_inline diffopt forwarding", function() eq(true, effective_diffopt().indent_heuristic) end) - it("never forwards linematch even when set in 'diffopt'", function() - set_diffopt({ "linematch:60", "iblank" }) + it("defaults linematch to 60 when absent from 'diffopt'", function() + set_diffopt({ "internal" }) + eq(60, effective_diffopt().linematch) + end) + + it("forwards linematch:N from 'diffopt'", function() + set_diffopt({ "linematch:30", "iblank" }) local opts = effective_diffopt() - assert.is_nil(opts.linematch) + eq(30, opts.linematch) -- Sanity-check that other entries still parse (confirms the loop ran). eq(true, opts.ignore_blank_lines) end) + it("honours an explicit linematch:0 to opt out of line-matching", function() + set_diffopt({ "linematch:0" }) + eq(0, effective_diffopt().linematch) + end) + it("leaves ignore flags nil when 'diffopt' has no corresponding entry", function() set_diffopt({ "internal" }) local opts = effective_diffopt()