fix(highlight): use hl_group instead of line_hl_group for diff backgrounds

line_hl_group bg occupies a separate rendering channel from hl_group in
Neovim's extmark system, causing character-level bg-only highlights to be
invisible regardless of priority. Switching to hl_group + hl_eol ensures
all backgrounds compete in the same channel.

Also reorders priorities (Normal 198 < line bg 199 < syntax 200 < char
bg 201), bumps char-level blend alpha from 0.4 to 0.7 for visibility,
and adds debug logging throughout the intra pipeline.
This commit is contained in:
Barrett Ruth 2026-02-06 18:28:22 -05:00
parent f1c13966ba
commit 3482e25c41
5 changed files with 429 additions and 20 deletions

68
lua/diffs/debug.lua Normal file
View file

@ -0,0 +1,68 @@
local M = {}
local ns = vim.api.nvim_create_namespace('diffs')
function M.dump()
local bufnr = vim.api.nvim_get_current_buf()
local marks = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local by_line = {}
for _, mark in ipairs(marks) do
local id, row, col, details = mark[1], mark[2], mark[3], mark[4]
local entry = {
id = id,
row = row,
col = col,
end_row = details.end_row,
end_col = details.end_col,
hl_group = details.hl_group,
priority = details.priority,
line_hl_group = details.line_hl_group,
number_hl_group = details.number_hl_group,
virt_text = details.virt_text,
}
if not by_line[row] then
by_line[row] = { text = lines[row + 1] or '', marks = {} }
end
table.insert(by_line[row].marks, entry)
end
local all_ns_marks = vim.api.nvim_buf_get_extmarks(bufnr, -1, 0, -1, { details = true })
local non_diffs = {}
for _, mark in ipairs(all_ns_marks) do
local details = mark[4]
if details.ns_id ~= ns then
table.insert(non_diffs, {
ns_id = details.ns_id,
row = mark[2],
col = mark[3],
end_row = details.end_row,
end_col = details.end_col,
hl_group = details.hl_group,
priority = details.priority,
})
end
end
local result = {
bufnr = bufnr,
buf_name = vim.api.nvim_buf_get_name(bufnr),
ns_id = ns,
total_diffs_marks = #marks,
total_all_marks = #all_ns_marks,
non_diffs_marks = non_diffs,
lines = by_line,
}
local state_dir = vim.fn.stdpath('state')
local path = state_dir .. '/diffs_debug.json'
local f = io.open(path, 'w')
if f then
f:write(vim.json.encode(result))
f:close()
vim.notify('[diffs.nvim] debug dump: ' .. path, vim.log.levels.INFO)
end
end
return M

View file

@ -312,13 +312,28 @@ function M.compute_intra_hunks(hunk_lines, algorithm)
---@type diffs.CharSpan[]
local all_del = {}
for _, group in ipairs(groups) do
dbg(
'intra: %d change groups, algorithm=%s, vscode=%s',
#groups,
algorithm,
vscode_handle and 'yes' or 'no'
)
for gi, group in ipairs(groups) do
dbg('group %d: %d del lines, %d add lines', gi, #group.del_lines, #group.add_lines)
local ds, as
if vscode_handle then
ds, as = diff_group_vscode(group, vscode_handle)
else
ds, as = diff_group_native(group)
end
dbg('group %d result: %d del spans, %d add spans', gi, #ds, #as)
for _, s in ipairs(ds) do
dbg(' del span: line=%d col=%d..%d', s.line, s.col_start, s.col_end)
end
for _, s in ipairs(as) do
dbg(' add span: line=%d col=%d..%d', s.line, s.col_start, s.col_end)
end
vim.list_extend(all_del, ds)
vim.list_extend(all_add, as)
end

View file

@ -287,7 +287,17 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
local intra = nil
local intra_cfg = opts.highlights.intra
if intra_cfg and intra_cfg.enabled and #hunk.lines <= intra_cfg.max_lines then
dbg('computing intra for hunk %s:%d (%d lines)', hunk.filename, hunk.start_line, #hunk.lines)
intra = diff.compute_intra_hunks(hunk.lines, intra_cfg.algorithm)
if intra then
dbg('intra result: %d add spans, %d del spans', #intra.add_spans, #intra.del_spans)
else
dbg('intra result: nil (no change groups)')
end
elseif intra_cfg and not intra_cfg.enabled then
dbg('intra disabled by config')
elseif intra_cfg and #hunk.lines > intra_cfg.max_lines then
dbg('intra skipped: %d lines > %d max', #hunk.lines, intra_cfg.max_lines)
end
---@type table<integer, diffs.CharSpan[]>
@ -324,21 +334,20 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
})
end
if opts.highlights.background and is_diff_line then
local extmark_opts = {
line_hl_group = line_hl,
priority = 198,
}
if opts.highlights.gutter then
extmark_opts.number_hl_group = number_hl
end
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, extmark_opts)
end
if line_len > 1 and syntax_applied then
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 1, {
end_col = line_len,
hl_group = 'Normal',
priority = 198,
})
end
if opts.highlights.background and is_diff_line then
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, {
end_col = line_len,
hl_group = line_hl,
hl_eol = true,
number_hl_group = opts.highlights.gutter and number_hl or nil,
priority = 199,
})
end
@ -346,11 +355,23 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
if char_spans_by_line[i] then
local char_hl = prefix == '+' and 'DiffsAddText' or 'DiffsDeleteText'
for _, span in ipairs(char_spans_by_line[i]) do
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span.col_start, {
dbg(
'char extmark: line=%d buf_line=%d col=%d..%d hl=%s text="%s"',
i,
buf_line,
span.col_start,
span.col_end,
char_hl,
line:sub(span.col_start + 1, span.col_end)
)
local ok, err = pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span.col_start, {
end_col = span.col_end,
hl_group = char_hl,
priority = 201,
})
if not ok then
dbg('char extmark FAILED: %s', err)
end
extmark_count = extmark_count + 1
end
end

View file

@ -183,8 +183,8 @@ local function compute_highlight_groups()
local blended_add = blend_color(add_bg, bg, 0.4)
local blended_del = blend_color(del_bg, bg, 0.4)
local blended_add_text = blend_color(add_fg, bg, 0.4)
local blended_del_text = blend_color(del_fg, bg, 0.4)
local blended_add_text = blend_color(add_fg, bg, 0.7)
local blended_del_text = blend_color(del_fg, bg, 0.7)
vim.api.nvim_set_hl(0, 'DiffsAdd', { default = true, bg = blended_add })
vim.api.nvim_set_hl(0, 'DiffsDelete', { default = true, bg = blended_del })
@ -193,6 +193,15 @@ local function compute_highlight_groups()
vim.api.nvim_set_hl(0, 'DiffsAddText', { default = true, bg = blended_add_text })
vim.api.nvim_set_hl(0, 'DiffsDeleteText', { default = true, bg = blended_del_text })
dbg('highlight groups: Normal.bg=#%06x DiffAdd.bg=#%06x diffAdded.fg=#%06x', bg, add_bg, add_fg)
dbg(
'DiffsAdd.bg=#%06x DiffsAddText.bg=#%06x DiffsAddNr.fg=#%06x',
blended_add,
blended_add_text,
add_fg
)
dbg('DiffsDelete.bg=#%06x DiffsDeleteText.bg=#%06x', blended_del, blended_del_text)
local diff_change = resolve_hl('DiffChange')
local diff_text = resolve_hl('DiffText')