Problem: gitsigns blame popups show flat `GitSignsAddPreview`/ `GitSignsDeletePreview` highlights with no treesitter syntax or character-level intra-line diffs. Solution: add `lua/diffs/gitsigns.lua` which patches `Popup.create` and `Popup.update` to intercept blame popups, parse hunk sections, clear gitsigns' own highlights, and apply `highlight_hunk` with manual `@diff.plus`/`@diff.minus` prefix extmarks. Wired in `plugin/diffs.lua` with `User GitAttach` lazy-load retry.
236 lines
6.8 KiB
Lua
236 lines
6.8 KiB
Lua
require('spec.helpers')
|
|
local gs = require('diffs.gitsigns')
|
|
|
|
local function setup_highlight_groups()
|
|
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 or 0x2e4a3a })
|
|
vim.api.nvim_set_hl(0, 'DiffsDelete', { bg = diff_delete.bg or 0x4a2e3a })
|
|
vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 })
|
|
vim.api.nvim_set_hl(0, 'DiffsDeleteText', { bg = 0xFF0000 })
|
|
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 or {})
|
|
return bufnr
|
|
end
|
|
|
|
local function delete_buffer(bufnr)
|
|
if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
|
|
vim.api.nvim_buf_delete(bufnr, { force = true })
|
|
end
|
|
end
|
|
|
|
describe('gitsigns', function()
|
|
describe('parse_blame_hunks', function()
|
|
it('parses a single hunk', function()
|
|
local bufnr = create_buffer({
|
|
'commit abc1234',
|
|
'Author: Test User',
|
|
'',
|
|
'Hunk 1 of 1',
|
|
' local x = 1',
|
|
'-local y = 2',
|
|
'+local y = 3',
|
|
' local z = 4',
|
|
})
|
|
local hunks = gs.parse_blame_hunks(bufnr, 'test.lua', 'lua', 'lua')
|
|
assert.are.equal(1, #hunks)
|
|
assert.are.equal('test.lua', hunks[1].filename)
|
|
assert.are.equal('lua', hunks[1].ft)
|
|
assert.are.equal('lua', hunks[1].lang)
|
|
assert.are.equal(1, hunks[1].prefix_width)
|
|
assert.are.equal(0, hunks[1].quote_width)
|
|
assert.are.equal(4, #hunks[1].lines)
|
|
assert.are.equal(4, hunks[1].start_line)
|
|
assert.are.equal(' local x = 1', hunks[1].lines[1])
|
|
assert.are.equal('-local y = 2', hunks[1].lines[2])
|
|
assert.are.equal('+local y = 3', hunks[1].lines[3])
|
|
delete_buffer(bufnr)
|
|
end)
|
|
|
|
it('parses multiple hunks', function()
|
|
local bufnr = create_buffer({
|
|
'commit abc1234',
|
|
'',
|
|
'Hunk 1 of 2',
|
|
'-local a = 1',
|
|
'+local a = 2',
|
|
'Hunk 2 of 2',
|
|
' local b = 3',
|
|
'+local c = 4',
|
|
})
|
|
local hunks = gs.parse_blame_hunks(bufnr, 'test.lua', 'lua', 'lua')
|
|
assert.are.equal(2, #hunks)
|
|
assert.are.equal(2, #hunks[1].lines)
|
|
assert.are.equal(2, #hunks[2].lines)
|
|
delete_buffer(bufnr)
|
|
end)
|
|
|
|
it('skips guessed-offset lines', function()
|
|
local bufnr = create_buffer({
|
|
'commit abc1234',
|
|
'',
|
|
'Hunk 1 of 1',
|
|
'(guessed: hunk offset may be wrong)',
|
|
' local x = 1',
|
|
'+local y = 2',
|
|
})
|
|
local hunks = gs.parse_blame_hunks(bufnr, 'test.lua', 'lua', 'lua')
|
|
assert.are.equal(1, #hunks)
|
|
assert.are.equal(2, #hunks[1].lines)
|
|
assert.are.equal(' local x = 1', hunks[1].lines[1])
|
|
delete_buffer(bufnr)
|
|
end)
|
|
|
|
it('returns empty table when no hunks present', function()
|
|
local bufnr = create_buffer({
|
|
'commit abc1234',
|
|
'Author: Test User',
|
|
'Date: 2024-01-01',
|
|
})
|
|
local hunks = gs.parse_blame_hunks(bufnr, 'test.lua', 'lua', 'lua')
|
|
assert.are.equal(0, #hunks)
|
|
delete_buffer(bufnr)
|
|
end)
|
|
|
|
it('handles hunk with no diff lines after header', function()
|
|
local bufnr = create_buffer({
|
|
'Hunk 1 of 1',
|
|
'some non-diff text',
|
|
})
|
|
local hunks = gs.parse_blame_hunks(bufnr, 'test.lua', 'lua', 'lua')
|
|
assert.are.equal(0, #hunks)
|
|
delete_buffer(bufnr)
|
|
end)
|
|
end)
|
|
|
|
describe('on_preview', function()
|
|
before_each(function()
|
|
setup_highlight_groups()
|
|
end)
|
|
|
|
it('applies extmarks to popup buffer with diff content', function()
|
|
local bufnr = create_buffer({
|
|
'commit abc1234',
|
|
'',
|
|
'Hunk 1 of 1',
|
|
' local x = 1',
|
|
'-local y = 2',
|
|
'+local y = 3',
|
|
})
|
|
|
|
local winid = vim.api.nvim_open_win(bufnr, false, {
|
|
relative = 'editor',
|
|
width = 40,
|
|
height = 10,
|
|
row = 0,
|
|
col = 0,
|
|
})
|
|
|
|
gs._test.on_preview(winid, bufnr)
|
|
|
|
local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, gs._test.ns, 0, -1, { details = true })
|
|
assert.is_true(#extmarks > 0)
|
|
|
|
vim.api.nvim_win_close(winid, true)
|
|
delete_buffer(bufnr)
|
|
end)
|
|
|
|
it('clears gitsigns_popup namespace on diff region', function()
|
|
local bufnr = create_buffer({
|
|
'commit abc1234',
|
|
'',
|
|
'Hunk 1 of 1',
|
|
' local x = 1',
|
|
'+local y = 2',
|
|
})
|
|
|
|
vim.api.nvim_buf_set_extmark(bufnr, gs._test.gs_popup_ns, 3, 0, {
|
|
end_col = 12,
|
|
hl_group = 'GitSignsAddPreview',
|
|
})
|
|
vim.api.nvim_buf_set_extmark(bufnr, gs._test.gs_popup_ns, 4, 0, {
|
|
end_col = 12,
|
|
hl_group = 'GitSignsAddPreview',
|
|
})
|
|
|
|
local winid = vim.api.nvim_open_win(bufnr, false, {
|
|
relative = 'editor',
|
|
width = 40,
|
|
height = 10,
|
|
row = 0,
|
|
col = 0,
|
|
})
|
|
|
|
gs._test.on_preview(winid, bufnr)
|
|
|
|
local gs_extmarks =
|
|
vim.api.nvim_buf_get_extmarks(bufnr, gs._test.gs_popup_ns, 0, -1, { details = true })
|
|
assert.are.equal(0, #gs_extmarks)
|
|
|
|
vim.api.nvim_win_close(winid, true)
|
|
delete_buffer(bufnr)
|
|
end)
|
|
|
|
it('does not error on invalid buffer', function()
|
|
assert.has_no.errors(function()
|
|
gs._test.on_preview(0, 99999)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('setup', function()
|
|
it('returns false when gitsigns.popup is not available', function()
|
|
local saved = package.loaded['gitsigns.popup']
|
|
package.loaded['gitsigns.popup'] = nil
|
|
package.preload['gitsigns.popup'] = nil
|
|
|
|
local fresh = loadfile('lua/diffs/gitsigns.lua')()
|
|
local result = fresh.setup()
|
|
assert.is_false(result)
|
|
|
|
package.loaded['gitsigns.popup'] = saved
|
|
end)
|
|
|
|
it('patches gitsigns.popup when available', function()
|
|
local create_called = false
|
|
local update_called = false
|
|
local mock_popup = {
|
|
create = function()
|
|
create_called = true
|
|
local bufnr = create_buffer({ 'test' })
|
|
local winid = vim.api.nvim_open_win(bufnr, false, {
|
|
relative = 'editor',
|
|
width = 10,
|
|
height = 1,
|
|
row = 0,
|
|
col = 0,
|
|
})
|
|
return winid, bufnr
|
|
end,
|
|
update = function()
|
|
update_called = true
|
|
end,
|
|
}
|
|
|
|
local saved = package.loaded['gitsigns.popup']
|
|
package.loaded['gitsigns.popup'] = mock_popup
|
|
|
|
local fresh = loadfile('lua/diffs/gitsigns.lua')()
|
|
local result = fresh.setup()
|
|
assert.is_true(result)
|
|
|
|
mock_popup.create()
|
|
assert.is_true(create_called)
|
|
|
|
mock_popup.update(0, 0)
|
|
assert.is_true(update_called)
|
|
|
|
package.loaded['gitsigns.popup'] = saved
|
|
end)
|
|
end)
|
|
end)
|