feat(git): syntax-highlight deleted lines in the diff overlay

This commit is contained in:
2026-05-20 06:42:41 +02:00
parent aaef6621dd
commit 2064c629ed
2 changed files with 149 additions and 12 deletions
+105 -6
View File
@@ -22,6 +22,7 @@ local NS_OVERLAY = vim.api.nvim_create_namespace("ow.git.hunks.overlay")
---@field rel string
---@field index string[]?
---@field index_sha string?
---@field index_hl { src: string[], lines: table[][]? }?
---@field hunks ow.Git.Hunks.Hunk[]
---@field overlay boolean
---@field autocmds integer[]
@@ -153,20 +154,117 @@ local function render_signs(buf)
end
end
local SKIP_CAPTURES = { spell = true, nospell = true, conceal = true }
---@param buf integer
---@param lines string[]
---@return table[][]?
local function highlight_index(buf, lines)
if not vim.treesitter.highlighter.active[buf] then
return nil
end
local got, parser = pcall(vim.treesitter.get_parser, buf)
if not got or not parser then
return nil
end
local lang = parser:lang()
local query = vim.treesitter.query.get(lang, "highlights")
if not query then
return nil
end
local source = table.concat(lines, "\n")
local got_root, root = pcall(function()
local trees = vim.treesitter.get_string_parser(source, lang):parse()
local tree = trees and trees[1]
return tree and tree:root()
end)
if not got_root or not root then
return nil
end
---@type table<integer, table<integer, string>>
local groups = {}
for id, node in query:iter_captures(root, source) do
local name = query.captures[id]
if name and name:sub(1, 1) ~= "_" and not SKIP_CAPTURES[name] then
local sr, sc, er, ec = node:range()
for row = sr, math.min(er, #lines - 1) do
local row_groups = groups[row] or {}
groups[row] = row_groups
local from = row == sr and sc or 0
local to = row == er and ec or #(lines[row + 1] or "")
for col = from, to - 1 do
row_groups[col] = name
end
end
end
end
local out = {}
for row = 0, #lines - 1 do
local line = lines[row + 1] or ""
local row_groups = groups[row] or {}
local chunks = {}
local col = 0
while col < #line do
local name = row_groups[col]
local stop = col + 1
while stop < #line and row_groups[stop] == name do
stop = stop + 1
end
local hl ---@type string|string[]
if name then
hl = { "GitHunkDeleteLine", "@" .. name }
else
hl = "GitHunkDeleteLine"
end
table.insert(chunks, { line:sub(col + 1, stop), hl })
col = stop
end
out[row + 1] = chunks
end
return out
end
---@param h ow.Git.Hunks.Hunk
---@param hl_lines table[][]? per-index-line syntax chunks, or nil
---@return table[]
local function delete_virt_lines(h)
local function delete_virt_lines(h, hl_lines)
local width = vim.o.columns
local virt = {}
for _, line in ipairs(h.old_lines) do
for i, line in ipairs(h.old_lines) do
local pad = math.max(width - vim.api.nvim_strwidth(line), 0)
table.insert(virt, {
{ line .. string.rep(" ", pad), "GitHunkDeleteLine" },
})
local cached = hl_lines and hl_lines[h.old_start + i - 1]
if cached then
local chunks = vim.list_extend({}, cached)
table.insert(chunks, {
string.rep(" ", pad),
"GitHunkDeleteLine",
})
table.insert(virt, chunks)
else
table.insert(virt, {
{ line .. string.rep(" ", pad), "GitHunkDeleteLine" },
})
end
end
return virt
end
---@param state ow.Git.Hunks.BufState
---@param buf integer
---@return table[][]?
local function index_spans(state, buf)
if not state.index then
return nil
end
local cache = state.index_hl
if cache and cache.src == state.index then
return cache.lines
end
local lines = highlight_index(buf, state.index)
state.index_hl = { src = state.index, lines = lines }
return lines
end
---@param buf integer
local function render_overlay(buf)
if not vim.api.nvim_buf_is_valid(buf) then
@@ -178,6 +276,7 @@ local function render_overlay(buf)
return
end
local line_count = vim.api.nvim_buf_line_count(buf)
local hl_lines = index_spans(state, buf)
for _, h in ipairs(state.hunks) do
if h.type ~= "delete" then
for r = h.new_start, h.new_start + h.new_count - 1 do
@@ -211,7 +310,7 @@ local function render_overlay(buf)
row, above = math.max(h.new_start - 1, 0), true
end
pcall(vim.api.nvim_buf_set_extmark, buf, NS_OVERLAY, row, 0, {
virt_lines = delete_virt_lines(h),
virt_lines = delete_virt_lines(h, hl_lines),
virt_lines_above = above,
right_gravity = false,
invalidate = true,