feat(highlight): wire highlights.context into treesitter pipeline (#151)
Some checks are pending
luarocks / quality (push) Waiting to run
luarocks / publish (push) Blocked by required conditions

## Problem

`highlights.context.enabled` and `highlights.context.lines` were
defined, validated, and range-checked but never read during
highlighting. Hunks inside incomplete constructs (e.g., a table literal
or function body whose opening is beyond the hunk's own context lines)
parsed incorrectly because treesitter had no surrounding code.

## Solution

`compute_hunk_context` in `init.lua` reads the working tree file using
the hunk's `@@ +start,count @@` line numbers to collect up to `lines`
(default 25) surrounding code lines in each direction. Files are read
once via `io.open` and cached across hunks in the same file.
`highlight_treesitter` in `highlight.lua` accepts an optional context
parameter that prepends/appends context lines to the parse string and
offsets capture rows by the prefix count, so extmarks only land on
actual hunk lines. Wired through `highlight_hunk` for the two
code-language treesitter calls (not headers, not `highlight_text`, not
vim syntax).

Closes #148.
This commit is contained in:
Barrett Ruth 2026-03-05 11:14:31 -05:00 committed by GitHub
parent 29e624d9f0
commit e7d56e3bbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 534 additions and 17 deletions

View file

@ -297,6 +297,69 @@ local function carry_forward_highlighted(old_entry, new_hunks)
return highlighted
end
---@param path string
---@return string[]?
local function read_file_lines(path)
local f = io.open(path, 'r')
if not f then
return nil
end
local lines = {}
for line in f:lines() do
lines[#lines + 1] = line
end
f:close()
return lines
end
---@param hunks diffs.Hunk[]
---@param max_lines integer
local function compute_hunk_context(hunks, max_lines)
---@type table<string, string[]|false>
local file_cache = {}
for _, hunk in ipairs(hunks) do
if not hunk.repo_root or not hunk.filename or not hunk.file_new_start then
goto continue
end
local path = vim.fs.joinpath(hunk.repo_root, hunk.filename)
local file_lines = file_cache[path]
if file_lines == nil then
file_lines = read_file_lines(path) or false
file_cache[path] = file_lines
end
if not file_lines then
goto continue
end
local new_start = hunk.file_new_start
local new_count = hunk.file_new_count or 0
local total = #file_lines
local before_start = math.max(1, new_start - max_lines)
if before_start < new_start then
local before = {}
for i = before_start, new_start - 1 do
before[#before + 1] = file_lines[i]
end
hunk.context_before = before
end
local after_start = new_start + new_count
local after_end = math.min(total, after_start + max_lines - 1)
if after_start <= total then
local after = {}
for i = after_start, after_end do
after[#after + 1] = file_lines[i]
end
hunk.context_after = after
end
::continue::
end
end
---@param bufnr integer
local function ensure_cache(bufnr)
if not vim.api.nvim_buf_is_valid(bufnr) then
@ -321,6 +384,9 @@ local function ensure_cache(bufnr)
local lc = vim.api.nvim_buf_line_count(bufnr)
local bc = vim.api.nvim_buf_get_offset(bufnr, lc)
dbg('parsed %d hunks in buffer %d (tick %d)', #hunks, bufnr, tick)
if config.highlights.context.enabled then
compute_hunk_context(hunks, config.highlights.context.lines)
end
local carried = entry and not entry.pending_clear and carry_forward_highlighted(entry, hunks)
hunk_cache[bufnr] = {
hunks = hunks,
@ -941,6 +1007,7 @@ M._test = {
hunks_eq = hunks_eq,
process_pending_clear = process_pending_clear,
ft_retry_pending = ft_retry_pending,
compute_hunk_context = compute_hunk_context,
}
return M