Merge branch 'main' into feat/deslop

This commit is contained in:
Barrett Ruth 2026-02-02 21:09:54 -05:00 committed by GitHub
commit 1ab85b698d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 172 additions and 36 deletions

View file

@ -125,6 +125,47 @@ local function highlight_treesitter(bufnr, ns, hunk, code_lines)
return extmark_count
end
---@alias fugitive-ts.SyntaxQueryFn fun(line: integer, col: integer): integer, string
---@param query_fn fugitive-ts.SyntaxQueryFn
---@param code_lines string[]
---@return {line: integer, col_start: integer, col_end: integer, hl_name: string}[]
function M.coalesce_syntax_spans(query_fn, code_lines)
local spans = {}
for i, line in ipairs(code_lines) do
local col = 1
local line_len = #line
while col <= line_len do
local syn_id, hl_name = query_fn(i, col)
if syn_id == 0 then
col = col + 1
else
local span_start = col
col = col + 1
while col <= line_len do
local next_id, next_name = query_fn(i, col)
if next_id == 0 or next_name ~= hl_name then
break
end
col = col + 1
end
if hl_name ~= '' then
table.insert(spans, {
line = i,
col_start = span_start,
col_end = col,
hl_name = hl_name,
})
end
end
end
end
return spans
end
---@param bufnr integer
---@param ns integer
---@param hunk fugitive-ts.Hunk
@ -144,52 +185,38 @@ local function highlight_vim_syntax(bufnr, ns, hunk, code_lines)
vim.api.nvim_buf_set_lines(scratch, 0, -1, false, code_lines)
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = scratch })
local extmark_count = 0
local spans = {}
vim.api.nvim_buf_call(scratch, function()
vim.cmd('setlocal syntax=' .. ft)
vim.cmd('redraw')
for i, line in ipairs(code_lines) do
local col = 1
local line_len = #line
while col <= line_len do
local syn_id = vim.fn.synID(i, col, 1)
---@param line integer
---@param col integer
---@return integer, string
local function query_fn(line, col)
local syn_id = vim.fn.synID(line, col, 1)
if syn_id == 0 then
col = col + 1
else
local hl_name = vim.fn.synIDattr(vim.fn.synIDtrans(syn_id), 'name')
local span_start = col
col = col + 1
while col <= line_len do
local next_id = vim.fn.synID(i, col, 1)
if next_id == 0 then
break
return 0, ''
end
local next_name = vim.fn.synIDattr(vim.fn.synIDtrans(next_id), 'name')
if next_name ~= hl_name then
break
end
col = col + 1
return syn_id, vim.fn.synIDattr(vim.fn.synIDtrans(syn_id), 'name')
end
if hl_name ~= '' then
local buf_line = hunk.start_line + i - 1
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span_start, {
end_col = col,
hl_group = hl_name,
spans = M.coalesce_syntax_spans(query_fn, code_lines)
end)
vim.api.nvim_buf_delete(scratch, { force = true })
local extmark_count = 0
for _, span in ipairs(spans) do
local buf_line = hunk.start_line + span.line - 1
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span.col_start, {
end_col = span.col_end,
hl_group = span.hl_name,
priority = 200,
})
extmark_count = extmark_count + 1
end
end
end
end
end)
vim.api.nvim_buf_delete(scratch, { force = true })
return extmark_count
end

View file

@ -533,6 +533,19 @@ describe('highlight', function()
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',
@ -549,6 +562,10 @@ describe('highlight', function()
highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ 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
@ -654,6 +671,19 @@ describe('highlight', function()
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',
@ -670,6 +700,10 @@ describe('highlight', function()
highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ 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
@ -682,4 +716,57 @@ describe('highlight', function()
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)

View file

@ -76,6 +76,25 @@ describe('parser', function()
end)
it('detects hunks across multiple files', function()
local orig_get_lang = vim.treesitter.language.get_lang
local orig_inspect = vim.treesitter.language.inspect
vim.treesitter.language.get_lang = function(ft)
local result = orig_get_lang(ft)
if result then
return result
end
if ft == 'python' then
return 'python'
end
return nil
end
vim.treesitter.language.inspect = function(lang)
if lang == 'python' then
return {}
end
return orig_inspect(lang)
end
local bufnr = create_buffer({
'M lua/foo.lua',
'@@ -1,1 +1,2 @@',
@ -88,6 +107,9 @@ describe('parser', function()
})
local hunks = parser.parse_buffer(bufnr)
vim.treesitter.language.get_lang = orig_get_lang
vim.treesitter.language.inspect = orig_inspect
assert.are.equal(2, #hunks)
assert.are.equal('lua/foo.lua', hunks[1].filename)
assert.are.equal('lua', hunks[1].lang)