fix(highlight): split old/new treesitter parsing
Problem: highlight_treesitter concatenated all hunk lines (context, -, +) into a single string. Mixed old/new code produced invalid syntax (e.g. two return statements), causing treesitter error recovery to drop captures on lines after the syntax error. Solution: split hunk lines into two versions — new (context + added) and old (context + deleted) — each parsed independently. Use a line_map to resolve treesitter row indices to buffer lines, with the old version only mapping deleted lines to avoid duplicate extmarks on context. Also fixes three related issues exposed by the improved TS coverage: - Replace Normal extmark with DiffsClear (explicit fg from Normal.fg). Normal in extmarks doesn't reliably override vim :syntax foreground. - Reorder priority stack to DiffsClear(198) < syntax(199) < line bg(200) < char bg(201). TS capture groups can carry colorscheme backgrounds that would override diff line backgrounds at higher priority. - Gate DiffsClear on per-line coverage tracking. Only clear fugitive syntax fg on lines where TS/vim actually produced captures, preventing force-clearing on lines where error recovery drops captures.
This commit is contained in:
parent
93f6627dd2
commit
bbb87b660e
3 changed files with 175 additions and 97 deletions
|
|
@ -7,8 +7,10 @@ describe('highlight', function()
|
|||
|
||||
before_each(function()
|
||||
ns = vim.api.nvim_create_namespace('diffs_test')
|
||||
local normal = vim.api.nvim_get_hl(0, { name = 'Normal' })
|
||||
local diff_add = vim.api.nvim_get_hl(0, { name = 'DiffAdd' })
|
||||
local diff_delete = vim.api.nvim_get_hl(0, { name = 'DiffDelete' })
|
||||
vim.api.nvim_set_hl(0, 'DiffsClear', { fg = normal.fg or 0xc0c0c0 })
|
||||
vim.api.nvim_set_hl(0, 'DiffsAdd', { bg = diff_add.bg })
|
||||
vim.api.nvim_set_hl(0, 'DiffsDelete', { bg = diff_delete.bg })
|
||||
end)
|
||||
|
|
@ -82,7 +84,7 @@ describe('highlight', function()
|
|||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('applies Normal extmarks to clear diff colors', function()
|
||||
it('applies DiffsClear extmarks to clear diff colors', function()
|
||||
local bufnr = create_buffer({
|
||||
'@@ -1,1 +1,2 @@',
|
||||
' local x = 1',
|
||||
|
|
@ -99,14 +101,46 @@ describe('highlight', function()
|
|||
highlight.highlight_hunk(bufnr, ns, hunk, default_opts())
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
local has_normal = false
|
||||
local has_clear = false
|
||||
for _, mark in ipairs(extmarks) do
|
||||
if mark[4] and mark[4].hl_group == 'Normal' then
|
||||
has_normal = true
|
||||
if mark[4] and mark[4].hl_group == 'DiffsClear' then
|
||||
has_clear = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(has_normal)
|
||||
assert.is_true(has_clear)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('produces treesitter captures on all lines with split parsing', function()
|
||||
local bufnr = create_buffer({
|
||||
'@@ -1,3 +1,3 @@',
|
||||
' local x = 1',
|
||||
'-local y = 2',
|
||||
'+local y = 3',
|
||||
' return x',
|
||||
})
|
||||
|
||||
local hunk = {
|
||||
filename = 'test.lua',
|
||||
lang = 'lua',
|
||||
start_line = 1,
|
||||
lines = { ' local x = 1', '-local y = 2', '+local y = 3', ' return x' },
|
||||
}
|
||||
|
||||
highlight.highlight_hunk(bufnr, ns, hunk, default_opts())
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
local lines_with_ts = {}
|
||||
for _, mark in ipairs(extmarks) do
|
||||
if mark[4] and mark[4].hl_group and mark[4].hl_group:match('^@.*%.lua$') then
|
||||
lines_with_ts[mark[2]] = true
|
||||
end
|
||||
end
|
||||
assert.is_true(lines_with_ts[1] ~= nil)
|
||||
assert.is_true(lines_with_ts[2] ~= nil)
|
||||
assert.is_true(lines_with_ts[3] ~= nil)
|
||||
assert.is_true(lines_with_ts[4] ~= nil)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
|
|
@ -576,7 +610,7 @@ describe('highlight', function()
|
|||
local extmarks = get_extmarks(bufnr)
|
||||
local has_syntax_hl = false
|
||||
for _, mark in ipairs(extmarks) do
|
||||
if mark[4] and mark[4].hl_group and mark[4].hl_group ~= 'Normal' then
|
||||
if mark[4] and mark[4].hl_group and mark[4].hl_group ~= 'DiffsClear' then
|
||||
has_syntax_hl = true
|
||||
break
|
||||
end
|
||||
|
|
@ -610,7 +644,7 @@ describe('highlight', function()
|
|||
local extmarks = get_extmarks(bufnr)
|
||||
local has_syntax_hl = false
|
||||
for _, mark in ipairs(extmarks) do
|
||||
if mark[4] and mark[4].hl_group and mark[4].hl_group ~= 'Normal' then
|
||||
if mark[4] and mark[4].hl_group and mark[4].hl_group ~= 'DiffsClear' then
|
||||
has_syntax_hl = true
|
||||
break
|
||||
end
|
||||
|
|
@ -682,7 +716,7 @@ describe('highlight', function()
|
|||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('applies Normal blanking for vim fallback hunks', function()
|
||||
it('applies DiffsClear blanking for vim fallback hunks', function()
|
||||
local orig_synID = vim.fn.synID
|
||||
local orig_synIDtrans = vim.fn.synIDtrans
|
||||
local orig_synIDattr = vim.fn.synIDattr
|
||||
|
|
@ -722,14 +756,14 @@ describe('highlight', function()
|
|||
vim.fn.synIDattr = orig_synIDattr
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
local has_normal = false
|
||||
local has_clear = false
|
||||
for _, mark in ipairs(extmarks) do
|
||||
if mark[4] and mark[4].hl_group == 'Normal' then
|
||||
has_normal = true
|
||||
if mark[4] and mark[4].hl_group == 'DiffsClear' then
|
||||
has_clear = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(has_normal)
|
||||
assert.is_true(has_clear)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
|
|
@ -765,7 +799,7 @@ describe('highlight', function()
|
|||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('line bg priority > Normal priority', function()
|
||||
it('line bg priority > DiffsClear priority', function()
|
||||
local bufnr = create_buffer({
|
||||
'@@ -1,2 +1,1 @@',
|
||||
'-local x = 1',
|
||||
|
|
@ -787,20 +821,20 @@ describe('highlight', function()
|
|||
)
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
local normal_priority = nil
|
||||
local clear_priority = nil
|
||||
local line_bg_priority = nil
|
||||
for _, mark in ipairs(extmarks) do
|
||||
local d = mark[4]
|
||||
if d and d.hl_group == 'Normal' then
|
||||
normal_priority = d.priority
|
||||
if d and d.hl_group == 'DiffsClear' then
|
||||
clear_priority = d.priority
|
||||
end
|
||||
if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') then
|
||||
line_bg_priority = d.priority
|
||||
end
|
||||
end
|
||||
assert.is_not_nil(normal_priority)
|
||||
assert.is_not_nil(clear_priority)
|
||||
assert.is_not_nil(line_bg_priority)
|
||||
assert.is_true(line_bg_priority > normal_priority)
|
||||
assert.is_true(line_bg_priority > clear_priority)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
|
|
@ -960,7 +994,7 @@ describe('highlight', function()
|
|||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('enforces priority order: Normal < line bg < syntax < char bg', function()
|
||||
it('enforces priority order: DiffsClear < syntax < line bg < char bg', function()
|
||||
vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 })
|
||||
vim.api.nvim_set_hl(0, 'DiffsDeleteText', { bg = 0xFF0000 })
|
||||
|
||||
|
|
@ -990,12 +1024,12 @@ describe('highlight', function()
|
|||
)
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
local priorities = { normal = {}, line_bg = {}, syntax = {}, char_bg = {} }
|
||||
local priorities = { clear = {}, line_bg = {}, syntax = {}, char_bg = {} }
|
||||
for _, mark in ipairs(extmarks) do
|
||||
local d = mark[4]
|
||||
if d then
|
||||
if d.hl_group == 'Normal' then
|
||||
table.insert(priorities.normal, d.priority)
|
||||
if d.hl_group == 'DiffsClear' then
|
||||
table.insert(priorities.clear, d.priority)
|
||||
elseif d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete' then
|
||||
table.insert(priorities.line_bg, d.priority)
|
||||
elseif d.hl_group == 'DiffsAddText' or d.hl_group == 'DiffsDeleteText' then
|
||||
|
|
@ -1006,19 +1040,19 @@ describe('highlight', function()
|
|||
end
|
||||
end
|
||||
|
||||
assert.is_true(#priorities.normal > 0)
|
||||
assert.is_true(#priorities.clear > 0)
|
||||
assert.is_true(#priorities.line_bg > 0)
|
||||
assert.is_true(#priorities.syntax > 0)
|
||||
assert.is_true(#priorities.char_bg > 0)
|
||||
|
||||
local max_normal = math.max(unpack(priorities.normal))
|
||||
local max_clear = math.max(unpack(priorities.clear))
|
||||
local min_line_bg = math.min(unpack(priorities.line_bg))
|
||||
local min_syntax = math.min(unpack(priorities.syntax))
|
||||
local min_char_bg = math.min(unpack(priorities.char_bg))
|
||||
|
||||
assert.is_true(max_normal < min_line_bg)
|
||||
assert.is_true(min_line_bg < min_syntax)
|
||||
assert.is_true(min_syntax < min_char_bg)
|
||||
assert.is_true(max_clear < min_syntax)
|
||||
assert.is_true(min_syntax < min_line_bg)
|
||||
assert.is_true(min_line_bg < min_char_bg)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1214,7 +1248,7 @@ describe('highlight', function()
|
|||
}
|
||||
end
|
||||
|
||||
it('uses priority 200 for code languages', function()
|
||||
it('uses priority 199 for code languages', function()
|
||||
local bufnr = create_buffer({
|
||||
'@@ -1,1 +1,2 @@',
|
||||
' local x = 1',
|
||||
|
|
@ -1231,16 +1265,16 @@ describe('highlight', function()
|
|||
highlight.highlight_hunk(bufnr, ns, hunk, default_opts())
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
local has_priority_200 = false
|
||||
local has_priority_199 = false
|
||||
for _, mark in ipairs(extmarks) do
|
||||
if mark[4] and mark[4].hl_group and mark[4].hl_group:match('^@.*%.lua$') then
|
||||
if mark[4].priority == 200 then
|
||||
has_priority_200 = true
|
||||
if mark[4].priority == 199 then
|
||||
has_priority_199 = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
assert.is_true(has_priority_200)
|
||||
assert.is_true(has_priority_199)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
|
|
@ -1278,7 +1312,7 @@ describe('highlight', function()
|
|||
end
|
||||
assert.is_true(#diff_extmark_priorities > 0)
|
||||
for _, priority in ipairs(diff_extmark_priorities) do
|
||||
assert.is_true(priority < 200)
|
||||
assert.is_true(priority < 199)
|
||||
end
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue