fix(highlight): support combined diff format for unmerged files

Problem: fugitive shows combined diffs (@@@ headers, 2-char prefixes)
for unmerged (UU) files. The parser and highlight pipeline assumed
unified diff format (@@, 1-char prefix), causing broken prefix
concealment, missing background colors on ` +`/`+ ` lines, and no
treesitter highlights due to garbage prefix chars in code arrays.

Solution: detect prefix width from the number of leading @ signs in
hunk headers. Propagate prefix_width through the parser (new field on
diffs.Hunk) and highlight pipeline (prefix stripping, col_offset,
concealment, line classification). Add U to filename pattern for
unmerged file detection. Skip intra-line diffing for combined diffs
since the 2-char prefix semantics don't produce meaningful change
groups.
This commit is contained in:
Barrett Ruth 2026-02-09 17:03:17 -05:00
parent 59fcf14817
commit a6dd0503b3
4 changed files with 232 additions and 29 deletions

View file

@ -12,6 +12,7 @@
---@field file_old_count integer?
---@field file_new_start integer?
---@field file_new_count integer?
---@field prefix_width integer
---@field repo_root string?
local M = {}
@ -133,6 +134,8 @@ function M.parse_buffer(bufnr)
local hunk_lines = {}
---@type integer?
local hunk_count = nil
---@type integer
local hunk_prefix_width = 1
---@type integer?
local header_start = nil
---@type string[]
@ -156,6 +159,7 @@ function M.parse_buffer(bufnr)
header_context = hunk_header_context,
header_context_col = hunk_header_context_col,
lines = hunk_lines,
prefix_width = hunk_prefix_width,
file_old_start = file_old_start,
file_old_count = file_old_count,
file_new_start = file_new_start,
@ -179,7 +183,7 @@ function M.parse_buffer(bufnr)
end
for i, line in ipairs(lines) do
local filename = line:match('^[MADRC%?!]%s+(.+)$') or line:match('^diff %-%-git a/.+ b/(.+)$')
local filename = line:match('^[MADRCU%?!]%s+(.+)$') or line:match('^diff %-%-git a/.+ b/(.+)$')
if filename then
flush_hunk()
current_filename = filename
@ -191,22 +195,33 @@ function M.parse_buffer(bufnr)
dbg('file: %s -> ft: %s (no ts parser)', filename, current_ft)
end
hunk_count = 0
hunk_prefix_width = 1
header_start = i
header_lines = {}
elseif line:match('^@@.-@@') then
elseif line:match('^@@+') then
flush_hunk()
hunk_start = i
local hs, hc, hs2, hc2 = line:match('^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@')
if hs then
file_old_start = tonumber(hs)
file_old_count = tonumber(hc) or 1
file_new_start = tonumber(hs2)
file_new_count = tonumber(hc2) or 1
local at_prefix = line:match('^(@@+)')
hunk_prefix_width = #at_prefix - 1
if #at_prefix == 2 then
local hs, hc, hs2, hc2 = line:match('^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@')
if hs then
file_old_start = tonumber(hs)
file_old_count = tonumber(hc) or 1
file_new_start = tonumber(hs2)
file_new_count = tonumber(hc2) or 1
end
else
local hs2, hc2 = line:match('%+(%d+),?(%d*) @@')
if hs2 then
file_new_start = tonumber(hs2)
file_new_count = tonumber(hc2) or 1
end
end
local prefix, context = line:match('^(@@.-@@%s*)(.*)')
local at_end, context = line:match('^(@@+.-@@+%s*)(.*)')
if context and context ~= '' then
hunk_header_context = context
hunk_header_context_col = #prefix
hunk_header_context_col = #at_end
end
if hunk_count then
hunk_count = hunk_count + 1