feat(highlight): add treesitter context padding from disk
Problem: treesitter parses each diff hunk in isolation, so incomplete syntax constructs at hunk boundaries (e.g., a function definition with no body) produce ERROR nodes and drop captures. Solution: read N lines from the on-disk file before/after each hunk and prepend/append them as unmapped padding lines. The line_map guard in highlight_treesitter skips extmarks for unmapped lines, so padding provides syntax context without visual output. Controlled by highlights.context (default 25, 0 to disable). Also applies to the vim syntax fallback path via a leading_offset filter.
This commit is contained in:
parent
ba1f830629
commit
2e1ebdee03
7 changed files with 308 additions and 24 deletions
|
|
@ -37,6 +37,7 @@ describe('highlight', function()
|
|||
highlights = {
|
||||
background = false,
|
||||
gutter = false,
|
||||
context = 0,
|
||||
treesitter = {
|
||||
enabled = true,
|
||||
max_lines = 500,
|
||||
|
|
@ -1055,6 +1056,99 @@ describe('highlight', function()
|
|||
assert.is_true(min_line_bg < min_char_bg)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('context padding produces no extmarks on padding lines', function()
|
||||
local repo_root = '/tmp/diffs-test-context'
|
||||
vim.fn.mkdir(repo_root, 'p')
|
||||
|
||||
local f = io.open(repo_root .. '/test.lua', 'w')
|
||||
f:write('local M = {}\n')
|
||||
f:write('function M.hello()\n')
|
||||
f:write(' return "hi"\n')
|
||||
f:write('end\n')
|
||||
f:write('return M\n')
|
||||
f:close()
|
||||
|
||||
local bufnr = create_buffer({
|
||||
'@@ -3,1 +3,2 @@',
|
||||
' return "hi"',
|
||||
'+"bye"',
|
||||
})
|
||||
|
||||
local hunk = {
|
||||
filename = 'test.lua',
|
||||
lang = 'lua',
|
||||
start_line = 1,
|
||||
lines = { ' return "hi"', '+"bye"' },
|
||||
file_old_start = 3,
|
||||
file_old_count = 1,
|
||||
file_new_start = 3,
|
||||
file_new_count = 2,
|
||||
repo_root = repo_root,
|
||||
}
|
||||
|
||||
highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ highlights = { context = 25 } }))
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
for _, mark in ipairs(extmarks) do
|
||||
local row = mark[2]
|
||||
assert.is_true(row >= 1 and row <= 2)
|
||||
end
|
||||
|
||||
delete_buffer(bufnr)
|
||||
os.remove(repo_root .. '/test.lua')
|
||||
vim.fn.delete(repo_root, 'rf')
|
||||
end)
|
||||
|
||||
it('context = 0 matches behavior without padding', 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' },
|
||||
file_new_start = 1,
|
||||
file_new_count = 2,
|
||||
repo_root = '/nonexistent',
|
||||
}
|
||||
|
||||
highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ highlights = { context = 0 } }))
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
assert.is_true(#extmarks > 0)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('gracefully handles missing file for context padding', 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' },
|
||||
file_new_start = 1,
|
||||
file_new_count = 2,
|
||||
repo_root = '/nonexistent/path',
|
||||
}
|
||||
|
||||
assert.has_no.errors(function()
|
||||
highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ highlights = { context = 25 } }))
|
||||
end)
|
||||
|
||||
local extmarks = get_extmarks(bufnr)
|
||||
assert.is_true(#extmarks > 0)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('diff header highlighting', function()
|
||||
|
|
@ -1086,6 +1180,7 @@ describe('highlight', function()
|
|||
highlights = {
|
||||
background = false,
|
||||
gutter = false,
|
||||
context = 0,
|
||||
treesitter = { enabled = true, max_lines = 500 },
|
||||
vim = { enabled = false, max_lines = 200 },
|
||||
},
|
||||
|
|
@ -1242,6 +1337,7 @@ describe('highlight', function()
|
|||
highlights = {
|
||||
background = false,
|
||||
gutter = false,
|
||||
context = 0,
|
||||
treesitter = { enabled = true, max_lines = 500 },
|
||||
vim = { enabled = false, max_lines = 200 },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -421,5 +421,84 @@ describe('parser', function()
|
|||
os.remove(file_path)
|
||||
vim.fn.delete(repo_root, 'rf')
|
||||
end)
|
||||
|
||||
it('extracts file line numbers from @@ header', function()
|
||||
local bufnr = create_buffer({
|
||||
'M lua/test.lua',
|
||||
'@@ -1,3 +1,4 @@',
|
||||
' local M = {}',
|
||||
'+local new = true',
|
||||
' return M',
|
||||
})
|
||||
local hunks = parser.parse_buffer(bufnr)
|
||||
|
||||
assert.are.equal(1, #hunks)
|
||||
assert.are.equal(1, hunks[1].file_old_start)
|
||||
assert.are.equal(3, hunks[1].file_old_count)
|
||||
assert.are.equal(1, hunks[1].file_new_start)
|
||||
assert.are.equal(4, hunks[1].file_new_count)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('extracts large line numbers from @@ header', function()
|
||||
local bufnr = create_buffer({
|
||||
'M lua/test.lua',
|
||||
'@@ -100,20 +200,30 @@',
|
||||
' local M = {}',
|
||||
})
|
||||
local hunks = parser.parse_buffer(bufnr)
|
||||
|
||||
assert.are.equal(1, #hunks)
|
||||
assert.are.equal(100, hunks[1].file_old_start)
|
||||
assert.are.equal(20, hunks[1].file_old_count)
|
||||
assert.are.equal(200, hunks[1].file_new_start)
|
||||
assert.are.equal(30, hunks[1].file_new_count)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('defaults count to 1 when omitted in @@ header', function()
|
||||
local bufnr = create_buffer({
|
||||
'M lua/test.lua',
|
||||
'@@ -1 +1 @@',
|
||||
' local M = {}',
|
||||
})
|
||||
local hunks = parser.parse_buffer(bufnr)
|
||||
|
||||
assert.are.equal(1, #hunks)
|
||||
assert.are.equal(1, hunks[1].file_old_start)
|
||||
assert.are.equal(1, hunks[1].file_old_count)
|
||||
assert.are.equal(1, hunks[1].file_new_start)
|
||||
assert.are.equal(1, hunks[1].file_new_count)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('stores repo_root on hunk when available', function()
|
||||
local bufnr = create_buffer({
|
||||
'M lua/test.lua',
|
||||
'@@ -1,3 +1,4 @@',
|
||||
' local M = {}',
|
||||
'+local new = true',
|
||||
' return M',
|
||||
})
|
||||
vim.api.nvim_buf_set_var(bufnr, 'diffs_repo_root', '/tmp/test-repo')
|
||||
local hunks = parser.parse_buffer(bufnr)
|
||||
|
||||
assert.are.equal(1, #hunks)
|
||||
assert.are.equal('/tmp/test-repo', hunks[1].repo_root)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
|
||||
it('repo_root is nil when not available', function()
|
||||
local bufnr = create_buffer({
|
||||
'M lua/test.lua',
|
||||
'@@ -1,3 +1,4 @@',
|
||||
' local M = {}',
|
||||
})
|
||||
local hunks = parser.parse_buffer(bufnr)
|
||||
|
||||
assert.are.equal(1, #hunks)
|
||||
assert.is_nil(hunks[1].repo_root)
|
||||
delete_buffer(bufnr)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue