require('spec.helpers') local highlight = require('diffs.highlight') describe('highlight', function() describe('highlight_hunk', function() local ns before_each(function() ns = vim.api.nvim_create_namespace('diffs_test') 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, 'DiffsAdd', { bg = diff_add.bg }) vim.api.nvim_set_hl(0, 'DiffsDelete', { bg = diff_delete.bg }) end) local function create_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) return bufnr end local function delete_buffer(bufnr) if vim.api.nvim_buf_is_valid(bufnr) then vim.api.nvim_buf_delete(bufnr, { force = true }) end end local function get_extmarks(bufnr) return vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) end local function default_opts(overrides) local opts = { hide_prefix = false, highlights = { background = false, gutter = false, treesitter = { enabled = true, max_lines = 500, }, vim = { enabled = false, max_lines = 200, }, intra = { enabled = false, algorithm = 'native', max_lines = 500, }, }, } if overrides then if overrides.highlights then opts.highlights = vim.tbl_deep_extend('force', opts.highlights, overrides.highlights) end if overrides.hide_prefix ~= nil then opts.hide_prefix = overrides.hide_prefix end end return opts end it('applies extmarks for lua code', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) delete_buffer(bufnr) end) it('applies Normal extmarks to clear diff colors', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local has_normal = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'Normal' then has_normal = true break end end assert.is_true(has_normal) delete_buffer(bufnr) end) it('skips hunks larger than max_lines', function() local lines = { '@@ -1,100 +1,101 @@' } local hunk_lines = {} for i = 1, 600 do table.insert(lines, ' line ' .. i) table.insert(hunk_lines, ' line ' .. i) end local bufnr = create_buffer(lines) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = hunk_lines, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) assert.are.equal(0, #extmarks) delete_buffer(bufnr) end) it('does nothing for nil lang and nil ft', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' some content', '+more content', }) local hunk = { filename = 'test.unknown', ft = nil, lang = nil, start_line = 1, lines = { ' some content', '+more content' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) assert.are.equal(0, #extmarks) delete_buffer(bufnr) end) it('highlights header context when enabled', function() local bufnr = create_buffer({ '@@ -10,3 +10,4 @@ function hello()', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, header_context = 'function hello()', header_context_col = 18, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local has_header_extmark = false for _, mark in ipairs(extmarks) do if mark[2] == 0 then has_header_extmark = true break end end assert.is_true(has_header_extmark) delete_buffer(bufnr) end) it('does not highlight header when no header_context', function() local bufnr = create_buffer({ '@@ -10,3 +10,4 @@', ' local x = 1', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local header_extmarks = 0 for _, mark in ipairs(extmarks) do if mark[2] == 0 then header_extmarks = header_extmarks + 1 end end assert.are.equal(0, header_extmarks) delete_buffer(bufnr) end) it('handles empty hunk lines', function() local bufnr = create_buffer({ '@@ -1,0 +1,0 @@', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = {}, } assert.has_no.errors(function() highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) end) delete_buffer(bufnr) end) it('handles code that is just whitespace', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' ', '+ ', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' ', '+ ' }, } assert.has_no.errors(function() highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) end) delete_buffer(bufnr) end) it('applies overlay extmarks when hide_prefix enabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ hide_prefix = true })) local extmarks = get_extmarks(bufnr) local overlay_count = 0 for _, mark in ipairs(extmarks) do if mark[4] and mark[4].virt_text_pos == 'overlay' then overlay_count = overlay_count + 1 end end assert.are.equal(2, overlay_count) delete_buffer(bufnr) end) it('does not apply overlay extmarks when hide_prefix disabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ hide_prefix = false })) local extmarks = get_extmarks(bufnr) local overlay_count = 0 for _, mark in ipairs(extmarks) do if mark[4] and mark[4].virt_text_pos == 'overlay' then overlay_count = overlay_count + 1 end end assert.are.equal(0, overlay_count) delete_buffer(bufnr) end) it('applies DiffAdd background to + lines when background enabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true } }) ) local extmarks = get_extmarks(bufnr) local has_diff_add = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end end assert.is_true(has_diff_add) delete_buffer(bufnr) end) it('applies DiffDelete background to - lines when background enabled', function() local bufnr = create_buffer({ '@@ -1,2 +1,1 @@', ' local x = 1', '-local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '-local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true } }) ) local extmarks = get_extmarks(bufnr) local has_diff_delete = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'DiffsDelete' then has_diff_delete = true break end end assert.is_true(has_diff_delete) delete_buffer(bufnr) end) it('does not apply background when background disabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = false } }) ) local extmarks = get_extmarks(bufnr) local has_line_hl = false for _, mark in ipairs(extmarks) do if mark[4] and (mark[4].hl_group == 'DiffsAdd' or mark[4].hl_group == 'DiffsDelete') then has_line_hl = true break end end assert.is_false(has_line_hl) delete_buffer(bufnr) end) it('applies number_hl_group when gutter enabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true, gutter = true } }) ) local extmarks = get_extmarks(bufnr) local has_number_hl = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].number_hl_group then has_number_hl = true break end end assert.is_true(has_number_hl) delete_buffer(bufnr) end) it('does not apply number_hl_group when gutter disabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true, gutter = false } }) ) local extmarks = get_extmarks(bufnr) local has_number_hl = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].number_hl_group then has_number_hl = true break end end assert.is_false(has_number_hl) delete_buffer(bufnr) end) it('skips treesitter highlights when treesitter disabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { treesitter = { enabled = false }, background = true } }) ) local extmarks = get_extmarks(bufnr) local has_ts_highlight = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group and mark[4].hl_group:match('^@') then has_ts_highlight = true break end end assert.is_false(has_ts_highlight) delete_buffer(bufnr) end) it('still applies background when treesitter disabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { treesitter = { enabled = false }, background = true } }) ) local extmarks = get_extmarks(bufnr) local has_diff_add = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end end assert.is_true(has_diff_add) delete_buffer(bufnr) end) it('applies vim syntax extmarks when vim.enabled and no TS parser', function() local orig_synID = vim.fn.synID local orig_synIDtrans = vim.fn.synIDtrans local orig_synIDattr = vim.fn.synIDattr vim.fn.synID = function(_line, _col, _trans) return 1 end vim.fn.synIDtrans = function(id) return id end vim.fn.synIDattr = function(_id, _what) return 'Identifier' end local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', ft = 'abap', lang = nil, start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { vim = { enabled = true } } }) ) vim.fn.synID = orig_synID vim.fn.synIDtrans = orig_synIDtrans vim.fn.synIDattr = orig_synIDattr 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 has_syntax_hl = true break end end assert.is_true(has_syntax_hl) delete_buffer(bufnr) end) it('skips vim fallback when vim.enabled is false', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', ft = 'abap', lang = nil, start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { vim = { enabled = false } } }) ) 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 has_syntax_hl = true break end end assert.is_false(has_syntax_hl) delete_buffer(bufnr) end) it('respects vim.max_lines', function() local lines = { '@@ -1,100 +1,101 @@' } local hunk_lines = {} for i = 1, 250 do table.insert(lines, ' line ' .. i) table.insert(hunk_lines, ' line ' .. i) end local bufnr = create_buffer(lines) local hunk = { filename = 'test.lua', ft = 'abap', lang = nil, start_line = 1, lines = hunk_lines, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { vim = { enabled = true, max_lines = 200 } } }) ) local extmarks = get_extmarks(bufnr) assert.are.equal(0, #extmarks) delete_buffer(bufnr) end) it('applies background for vim fallback hunks', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', ft = 'abap', lang = nil, start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { vim = { enabled = true }, background = true } }) ) local extmarks = get_extmarks(bufnr) local has_diff_add = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end end assert.is_true(has_diff_add) delete_buffer(bufnr) end) it('applies Normal blanking for vim fallback hunks', function() local orig_synID = vim.fn.synID local orig_synIDtrans = vim.fn.synIDtrans local orig_synIDattr = vim.fn.synIDattr vim.fn.synID = function(_line, _col, _trans) return 1 end vim.fn.synIDtrans = function(id) return id end vim.fn.synIDattr = function(_id, _what) return 'Identifier' end local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', ft = 'abap', lang = nil, start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { vim = { enabled = true } } }) ) vim.fn.synID = orig_synID vim.fn.synIDtrans = orig_synIDtrans vim.fn.synIDattr = orig_synIDattr local extmarks = get_extmarks(bufnr) local has_normal = false for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'Normal' then has_normal = true break end end assert.is_true(has_normal) delete_buffer(bufnr) end) it('uses hl_group not line_hl_group for line backgrounds', function() local bufnr = create_buffer({ '@@ -1,2 +1,1 @@', '-local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '-local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true } }) ) local extmarks = get_extmarks(bufnr) for _, mark in ipairs(extmarks) do local d = mark[4] if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') then assert.is_true(d.hl_eol == true) assert.is_nil(d.line_hl_group) end end delete_buffer(bufnr) end) it('line bg priority > Normal priority', function() local bufnr = create_buffer({ '@@ -1,2 +1,1 @@', '-local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '-local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true } }) ) local extmarks = get_extmarks(bufnr) local normal_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 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(line_bg_priority) assert.is_true(line_bg_priority > normal_priority) delete_buffer(bufnr) end) it('char-level extmarks have higher priority than line bg', function() vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 }) vim.api.nvim_set_hl(0, 'DiffsDeleteText', { bg = 0xFF0000 }) local bufnr = create_buffer({ '@@ -1,2 +1,2 @@', '-local x = 1', '+local x = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '-local x = 1', '+local x = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true, intra = { enabled = true, algorithm = 'native', max_lines = 500 }, }, }) ) local extmarks = get_extmarks(bufnr) local line_bg_priority = nil local char_bg_priority = nil for _, mark in ipairs(extmarks) do local d = mark[4] if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') then line_bg_priority = d.priority end if d and (d.hl_group == 'DiffsAddText' or d.hl_group == 'DiffsDeleteText') then char_bg_priority = d.priority end end assert.is_not_nil(line_bg_priority) assert.is_not_nil(char_bg_priority) assert.is_true(char_bg_priority > line_bg_priority) delete_buffer(bufnr) end) it('creates char-level extmarks for changed characters', function() vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 }) vim.api.nvim_set_hl(0, 'DiffsDeleteText', { bg = 0xFF0000 }) local bufnr = create_buffer({ '@@ -1,2 +1,2 @@', '-local x = 1', '+local x = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '-local x = 1', '+local x = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { intra = { enabled = true, algorithm = 'native', max_lines = 500 } }, }) ) local extmarks = get_extmarks(bufnr) local add_text_marks = {} local del_text_marks = {} for _, mark in ipairs(extmarks) do local d = mark[4] if d and d.hl_group == 'DiffsAddText' then table.insert(add_text_marks, mark) end if d and d.hl_group == 'DiffsDeleteText' then table.insert(del_text_marks, mark) end end assert.is_true(#add_text_marks > 0) assert.is_true(#del_text_marks > 0) delete_buffer(bufnr) end) it('does not create char-level extmarks when intra disabled', function() local bufnr = create_buffer({ '@@ -1,2 +1,2 @@', '-local x = 1', '+local x = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '-local x = 1', '+local x = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { intra = { enabled = false, algorithm = 'native', max_lines = 500 } }, }) ) local extmarks = get_extmarks(bufnr) for _, mark in ipairs(extmarks) do local d = mark[4] assert.is_not_equal('DiffsAddText', d and d.hl_group) assert.is_not_equal('DiffsDeleteText', d and d.hl_group) end delete_buffer(bufnr) end) it('does not create char-level extmarks for pure additions', function() vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 }) local bufnr = create_buffer({ '@@ -1,0 +1,2 @@', '+local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '+local x = 1', '+local y = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { intra = { enabled = true, algorithm = 'native', max_lines = 500 } }, }) ) local extmarks = get_extmarks(bufnr) for _, mark in ipairs(extmarks) do local d = mark[4] assert.is_not_equal('DiffsAddText', d and d.hl_group) assert.is_not_equal('DiffsDeleteText', d and d.hl_group) end delete_buffer(bufnr) end) it('enforces priority order: Normal < line bg < syntax < char bg', function() vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 }) vim.api.nvim_set_hl(0, 'DiffsDeleteText', { bg = 0xFF0000 }) local bufnr = create_buffer({ '@@ -1,2 +1,2 @@', '-local x = 1', '+local x = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { '-local x = 1', '+local x = 2' }, } highlight.highlight_hunk( bufnr, ns, hunk, default_opts({ highlights = { background = true, intra = { enabled = true, algorithm = 'native', max_lines = 500 }, }, }) ) local extmarks = get_extmarks(bufnr) local priorities = { normal = {}, 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) 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 table.insert(priorities.char_bg, d.priority) elseif d.hl_group and d.hl_group:match('^@.*%.lua$') then table.insert(priorities.syntax, d.priority) end end end assert.is_true(#priorities.normal > 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 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) delete_buffer(bufnr) end) end) describe('diff header highlighting', function() local ns before_each(function() ns = vim.api.nvim_create_namespace('diffs_test_header') end) local function create_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) return bufnr end local function delete_buffer(bufnr) if vim.api.nvim_buf_is_valid(bufnr) then vim.api.nvim_buf_delete(bufnr, { force = true }) end end local function get_extmarks(bufnr) return vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) end local function default_opts() return { hide_prefix = false, highlights = { background = false, gutter = false, treesitter = { enabled = true, max_lines = 500 }, vim = { enabled = false, max_lines = 200 }, }, } end it('applies treesitter extmarks to diff header lines', function() local bufnr = create_buffer({ 'diff --git a/parser.lua b/parser.lua', 'index 3e8afa0..018159c 100644', '--- a/parser.lua', '+++ b/parser.lua', '@@ -1,2 +1,3 @@', ' local M = {}', '+local x = 1', }) local hunk = { filename = 'parser.lua', lang = 'lua', start_line = 5, lines = { ' local M = {}', '+local x = 1' }, header_start_line = 1, header_lines = { 'diff --git a/parser.lua b/parser.lua', 'index 3e8afa0..018159c 100644', '--- a/parser.lua', '+++ b/parser.lua', }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local header_extmarks = {} for _, mark in ipairs(extmarks) do if mark[2] < 4 and mark[4] and mark[4].hl_group then table.insert(header_extmarks, mark) end end assert.is_true(#header_extmarks > 0) local has_function_hl = false local has_keyword_hl = false for _, mark in ipairs(header_extmarks) do local hl = mark[4].hl_group if hl == '@function' or hl == '@function.diff' then has_function_hl = true end if hl == '@keyword' or hl == '@keyword.diff' then has_keyword_hl = true end end assert.is_true(has_function_hl or has_keyword_hl) delete_buffer(bufnr) end) it('does not apply header highlights when header_lines missing', function() local bufnr = create_buffer({ '@@ -1,2 +1,3 @@', ' local M = {}', '+local x = 1', }) local hunk = { filename = 'parser.lua', lang = 'lua', start_line = 1, lines = { ' local M = {}', '+local x = 1' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local header_extmarks = 0 for _, mark in ipairs(extmarks) do if mark[2] < 0 and mark[4] and mark[4].hl_group then header_extmarks = header_extmarks + 1 end end assert.are.equal(0, header_extmarks) delete_buffer(bufnr) end) it('does not apply header highlights when treesitter disabled', function() local bufnr = create_buffer({ 'diff --git a/parser.lua b/parser.lua', 'index 3e8afa0..018159c 100644', '--- a/parser.lua', '+++ b/parser.lua', '@@ -1,2 +1,3 @@', ' local M = {}', '+local x = 1', }) local hunk = { filename = 'parser.lua', lang = 'lua', start_line = 5, lines = { ' local M = {}', '+local x = 1' }, header_start_line = 1, header_lines = { 'diff --git a/parser.lua b/parser.lua', 'index 3e8afa0..018159c 100644', '--- a/parser.lua', '+++ b/parser.lua', }, } local opts = default_opts() opts.highlights.treesitter.enabled = false highlight.highlight_hunk(bufnr, ns, hunk, opts) local extmarks = get_extmarks(bufnr) local header_extmarks = 0 for _, mark in ipairs(extmarks) do if mark[2] < 4 and mark[4] and mark[4].hl_group and mark[4].hl_group:match('^@') then header_extmarks = header_extmarks + 1 end end assert.are.equal(0, header_extmarks) delete_buffer(bufnr) end) end) describe('extmark priority', function() local ns before_each(function() ns = vim.api.nvim_create_namespace('diffs_test_priority') end) local function create_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) return bufnr end local function delete_buffer(bufnr) if vim.api.nvim_buf_is_valid(bufnr) then vim.api.nvim_buf_delete(bufnr, { force = true }) end end local function get_extmarks(bufnr) return vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) end local function default_opts() return { hide_prefix = false, highlights = { background = false, gutter = false, treesitter = { enabled = true, max_lines = 500 }, vim = { enabled = false, max_lines = 200 }, }, } end it('uses priority 200 for code languages', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 1, lines = { ' local x = 1', '+local y = 2' }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local has_priority_200 = 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 break end end end assert.is_true(has_priority_200) delete_buffer(bufnr) end) it('uses treesitter priority for diff language', function() local bufnr = create_buffer({ 'diff --git a/test.lua b/test.lua', '--- a/test.lua', '+++ b/test.lua', '@@ -1,1 +1,2 @@', ' local x = 1', '+local y = 2', }) local hunk = { filename = 'test.lua', lang = 'lua', start_line = 5, lines = { ' local x = 1', '+local y = 2' }, header_start_line = 1, header_lines = { 'diff --git a/test.lua b/test.lua', '--- a/test.lua', '+++ b/test.lua', }, } highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) local diff_extmark_priorities = {} for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group and mark[4].hl_group:match('^@.*%.diff$') then table.insert(diff_extmark_priorities, mark[4].priority) end end assert.is_true(#diff_extmark_priorities > 0) for _, priority in ipairs(diff_extmark_priorities) do assert.is_true(priority < 200) end delete_buffer(bufnr) end) end) describe('coalesce_syntax_spans', function() it('coalesces adjacent chars with same hl group', function() local function query_fn(_line, _col) return 1, 'Keyword' end local spans = highlight.coalesce_syntax_spans(query_fn, { 'hello' }) assert.are.equal(1, #spans) assert.are.equal(1, spans[1].col_start) assert.are.equal(6, spans[1].col_end) assert.are.equal('Keyword', spans[1].hl_name) end) it('splits spans at hl group boundaries', function() local function query_fn(_line, col) if col <= 3 then return 1, 'Keyword' end return 2, 'String' end local spans = highlight.coalesce_syntax_spans(query_fn, { 'abcdef' }) assert.are.equal(2, #spans) assert.are.equal('Keyword', spans[1].hl_name) assert.are.equal(1, spans[1].col_start) assert.are.equal(4, spans[1].col_end) assert.are.equal('String', spans[2].hl_name) assert.are.equal(4, spans[2].col_start) assert.are.equal(7, spans[2].col_end) end) it('skips syn_id 0 gaps', function() local function query_fn(_line, col) if col == 2 or col == 3 then return 0, '' end return 1, 'Identifier' end local spans = highlight.coalesce_syntax_spans(query_fn, { 'abcd' }) assert.are.equal(2, #spans) assert.are.equal(1, spans[1].col_start) assert.are.equal(2, spans[1].col_end) assert.are.equal(4, spans[2].col_start) assert.are.equal(5, spans[2].col_end) end) it('skips empty hl_name spans', function() local function query_fn(_line, _col) return 1, '' end local spans = highlight.coalesce_syntax_spans(query_fn, { 'abc' }) assert.are.equal(0, #spans) end) end) end)