Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 2 additions & 3 deletions lua/diffs/highlight.lua
Original file line number Diff line number Diff line change
Expand Up @@ -636,10 +636,9 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
end

if opts.highlights.background and is_diff_line then
local bg_end_col = raw_len or (line_len + qw)
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, {
end_row = buf_line,
end_col = bg_end_col,
end_row = buf_line + 1,
end_col = 0,
hl_group = line_hl,
hl_eol = true,
priority = p.line_bg,
Expand Down
25 changes: 23 additions & 2 deletions lua/diffs/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,10 @@ local function ensure_cache(bufnr)
if config.highlights.context.enabled then
compute_hunk_context(hunks, config.highlights.context.lines)
end
local carried = entry and not entry.pending_clear and carry_forward_highlighted(entry, hunks)
local carried = entry
and not entry.pending_clear
and #entry.hunks == #hunks
and carry_forward_highlighted(entry, hunks)
hunk_cache[bufnr] = {
hunks = hunks,
tick = tick,
Expand Down Expand Up @@ -514,6 +517,23 @@ local function find_visible_hunks(hunks, toprow, botrow)
return first, last
end

---@param bufnr integer
---@param ns_id integer
---@param start_row integer
---@param end_row integer
local function clear_ns_by_start(bufnr, ns_id, start_row, end_row)
local marks = vim.api.nvim_buf_get_extmarks(
bufnr,
ns_id,
{ start_row, 0 },
{ end_row - 1, 2147483647 },
{}
)
for _, m in ipairs(marks) do
vim.api.nvim_buf_del_extmark(bufnr, ns_id, m[1])
end
end

local function compute_highlight_groups(is_default)
local normal = vim.api.nvim_get_hl(0, { name = 'Normal' })
local diff_add = vim.api.nvim_get_hl(0, { name = 'DiffAdd' })
Expand Down Expand Up @@ -945,7 +965,7 @@ local function init()
if hunk.header_start_line then
clear_start = hunk.header_start_line - 1
end
vim.api.nvim_buf_clear_namespace(bufnr, ns, clear_start, clear_end)
clear_ns_by_start(bufnr, ns, clear_start, clear_end)
highlight.highlight_hunk(bufnr, ns, hunk, fast_hl_opts)
entry.highlighted[i] = true
count = count + 1
Expand Down Expand Up @@ -1185,6 +1205,7 @@ M._test = {
invalidate_cache = invalidate_cache,
hunks_eq = hunks_eq,
process_pending_clear = process_pending_clear,
clear_ns_by_start = clear_ns_by_start,
ft_retry_pending = ft_retry_pending,
compute_hunk_context = compute_hunk_context,
compute_highlight_groups = compute_highlight_groups,
Expand Down
15 changes: 6 additions & 9 deletions spec/decoration_provider_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe('decoration_provider', function()
end)

describe('hunk stability', function()
it('carries forward highlighted for stable hunks on section expansion', function()
it('forces full clear on section expansion (hunk count changed)', function()
local bufnr = create_buffer({
'M test.lua',
'@@ -1,2 +1,2 @@',
Expand Down Expand Up @@ -193,14 +193,12 @@ describe('decoration_provider', function()

local updated = diffs._test.hunk_cache[bufnr]
assert.are.equal(3, #updated.hunks)
assert.is_true(updated.highlighted[1] == true)
assert.is_nil(updated.highlighted[2])
assert.is_true(updated.highlighted[3] == true)
assert.is_false(updated.pending_clear)
assert.are.same({}, updated.highlighted)
assert.is_true(updated.pending_clear)
delete_buffer(bufnr)
end)

it('carries forward highlighted for stable hunks on section collapse', function()
it('forces full clear on section collapse (stale extmarks from removed hunks)', function()
local bufnr = create_buffer({
'M test.lua',
'@@ -1,2 +1,2 @@',
Expand All @@ -227,9 +225,8 @@ describe('decoration_provider', function()

local updated = diffs._test.hunk_cache[bufnr]
assert.are.equal(2, #updated.hunks)
assert.is_true(updated.highlighted[1] == true)
assert.is_true(updated.highlighted[2] == true)
assert.is_false(updated.pending_clear)
assert.are.same({}, updated.highlighted)
assert.is_true(updated.pending_clear)
delete_buffer(bufnr)
end)

Expand Down
126 changes: 88 additions & 38 deletions spec/highlight_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -397,48 +397,98 @@ describe('highlight', function()
delete_buffer(bufnr)
end)

it('line bg extmark survives adjacent clear_namespace starting at next row', function()
local bufnr = create_buffer({
'diff --git a/foo.py b/foo.py',
'@@ -1,2 +1,2 @@',
'-old',
'+new',
})

local hunk = {
filename = 'foo.py',
header_start_line = 1,
start_line = 2,
lines = { '-old', '+new' },
prefix_width = 1,
}

highlight.highlight_hunk(
bufnr,
ns,
hunk,
default_opts({ highlights = { background = true, treesitter = { enabled = false } } })
)
it(
'nvim_buf_clear_namespace kills line bg extmark whose end_row bleeds into cleared range',
function()
local bufnr = create_buffer({
'diff --git a/foo.py b/foo.py',
'@@ -1,2 +1,2 @@',
'-old',
'+new',
})

local last_body_row = hunk.start_line + #hunk.lines - 1
vim.api.nvim_buf_clear_namespace(bufnr, ns, last_body_row + 1, last_body_row + 10)
local hunk = {
filename = 'foo.py',
header_start_line = 1,
start_line = 2,
lines = { '-old', '+new' },
prefix_width = 1,
}

highlight.highlight_hunk(
bufnr,
ns,
hunk,
default_opts({ highlights = { background = true, treesitter = { enabled = false } } })
)

local last_body_row = hunk.start_line + #hunk.lines - 1
vim.api.nvim_buf_clear_namespace(bufnr, ns, last_body_row + 1, last_body_row + 10)

local marks = vim.api.nvim_buf_get_extmarks(
bufnr,
ns,
{ last_body_row, 0 },
{ last_body_row, -1 },
{ details = true }
)
local has_line_bg = false
for _, mark in ipairs(marks) do
if mark[4] and mark[4].hl_group == 'DiffsAdd' then
has_line_bg = true
end
end
assert.is_false(has_line_bg)
delete_buffer(bufnr)
end
)

it(
'clear_ns_by_start preserves line bg extmark whose end_row bleeds past cleared range',
function()
local bufnr = create_buffer({
'diff --git a/foo.py b/foo.py',
'@@ -1,2 +1,2 @@',
'-old',
'+new',
})

local marks = vim.api.nvim_buf_get_extmarks(
bufnr,
ns,
{ last_body_row, 0 },
{ last_body_row, -1 },
{ details = true }
)
local has_line_bg = false
for _, mark in ipairs(marks) do
if mark[4] and mark[4].hl_group == 'DiffsAdd' then
has_line_bg = true
local hunk = {
filename = 'foo.py',
header_start_line = 1,
start_line = 2,
lines = { '-old', '+new' },
prefix_width = 1,
}

highlight.highlight_hunk(
bufnr,
ns,
hunk,
default_opts({ highlights = { background = true, treesitter = { enabled = false } } })
)

local last_body_row = hunk.start_line + #hunk.lines - 1
local clear_ns_by_start = require('diffs')._test.clear_ns_by_start
clear_ns_by_start(bufnr, ns, last_body_row + 1, last_body_row + 10)

local marks = vim.api.nvim_buf_get_extmarks(
bufnr,
ns,
{ last_body_row, 0 },
{ last_body_row, -1 },
{ details = true }
)
local has_line_bg = false
for _, mark in ipairs(marks) do
if mark[4] and mark[4].hl_group == 'DiffsAdd' then
has_line_bg = true
end
end
assert.is_true(has_line_bg)
delete_buffer(bufnr)
end
assert.is_true(has_line_bg)
delete_buffer(bufnr)
end)
)

it('clear range covers last body line of hunk with header', function()
local bufnr = create_buffer({
Expand Down
Loading