From e5b0a617a66b139f972efed3dceb927969a5c64f Mon Sep 17 00:00:00 2001
From: Barrett Ruth
Date: Mon, 9 Nov 2026 08:15:06 -0600
Subject: [PATCH] fix: highlight PHP hunks without an opening tag
---
lua/diffs/highlight.lua | 100 ++++++++++++++++++++++++++++++++++++----
1 file changed, 91 insertions(+), 9 deletions(-)
diff --git a/lua/diffs/highlight.lua b/lua/diffs/highlight.lua
index cc9d9df..3aa356c 100644
--- a/lua/diffs/highlight.lua
+++ b/lua/diffs/highlight.lua
@@ -105,6 +105,35 @@ local function put_vim_syntax_cache(ft, code_lines, spans)
return spans
end
+---@param code_lines string[]
+---@return "open"|"close"|nil
+local function first_php_tag(code_lines)
+ local code = table.concat(code_lines, '\n')
+ local open = code:find('', 1, true)
+ local close = code:find('?>', 1, true)
+ if close and (not open or close < open) then
+ return 'close'
+ end
+ return open and 'open' or nil
+end
+
+--- PHP hunks rarely contain the file's opening tag, and PHP parsers treat
+--- everything before an opening tag as HTML.
+---@param lang string?
+---@param new_code string[]
+---@param old_code string[]
+---@return string?
+local function php_parse_prefix(lang, new_code, old_code)
+ if lang ~= 'php' then
+ return nil
+ end
+ local new_tag, old_tag = first_php_tag(new_code), first_php_tag(old_code)
+ if new_tag == 'close' or old_tag == 'close' or not (new_tag or old_tag) then
+ return ' 0 then
parts[#parts + 1] = table.concat(context_lines, '\n')
end
@@ -141,7 +186,7 @@ local function highlight_text(bufnr, ns, hunk, col_offset, text, lang, context_l
for id, node, metadata in query:iter_captures(trees[1]:root(), parse_text) do
local sr, sc, _, ec = node:range()
- if sr == 0 then
+ if sr == target_row then
local capture_name = '@' .. query.captures[id] .. '.' .. lang
local buf_sr = header_line
@@ -242,6 +287,7 @@ end
---@param covered_lines? table
---@param priorities diffs.HighlightPriorities
---@param force_high_priority? boolean
+---@param prefix? string
---@return integer
local function highlight_treesitter(
bufnr,
@@ -252,12 +298,18 @@ local function highlight_treesitter(
col_offset,
covered_lines,
priorities,
- force_high_priority
+ force_high_priority,
+ prefix
)
local code = table.concat(code_lines, '\n')
if code == '' then
return 0
end
+ local row_shift = 0
+ if prefix then
+ code = prefix .. '\n' .. code
+ row_shift = 1
+ end
local ok, parser_obj = pcall(vim.treesitter.get_string_parser, code, lang)
if not ok or not parser_obj then
@@ -284,6 +336,7 @@ local function highlight_treesitter(
if capture ~= 'spell' and capture ~= 'nospell' then
local capture_name = '@' .. capture .. '.' .. tree_lang
local sr, sc, er, ec = node:range()
+ sr, er = sr - row_shift, er - row_shift
local buf_sr = line_map[sr]
if buf_sr then
@@ -553,10 +606,32 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
end
end
- extmark_count =
- highlight_treesitter(bufnr, ns, new_code, hunk.lang, new_map, pw + qw, covered_lines, p)
+ local prefix = php_parse_prefix(hunk.lang, new_code, old_code)
+ extmark_count = highlight_treesitter(
+ bufnr,
+ ns,
+ new_code,
+ hunk.lang,
+ new_map,
+ pw + qw,
+ covered_lines,
+ p,
+ nil,
+ prefix
+ )
extmark_count = extmark_count
- + highlight_treesitter(bufnr, ns, old_code, hunk.lang, old_map, pw + qw, covered_lines, p)
+ + highlight_treesitter(
+ bufnr,
+ ns,
+ old_code,
+ hunk.lang,
+ old_map,
+ pw + qw,
+ covered_lines,
+ p,
+ nil,
+ prefix
+ )
if hunk.header_context and hunk.header_context_col then
local header_extmarks = highlight_text(
@@ -567,7 +642,8 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
hunk.header_context,
hunk.lang,
new_code,
- p
+ p,
+ prefix
)
if header_extmarks > 0 then
dbg('header %s:%d applied %d extmarks', hunk.filename, hunk.start_line, header_extmarks)
@@ -579,11 +655,17 @@ function M.highlight_hunk(bufnr, ns, hunk, opts)
for _, line in ipairs(hunk.lines) do
table.insert(code_lines, line:sub(pw + 1))
end
+ local leading = 0
+ local prefix = php_parse_prefix(hunk.ft, code_lines, {})
+ if prefix then
+ table.insert(code_lines, 1, prefix)
+ leading = 1
+ end
if opts.defer_vim_syntax then
extmark_count, vim_cache_hit =
- highlight_cached_vim_syntax(bufnr, ns, hunk, code_lines, covered_lines, 0, p)
+ highlight_cached_vim_syntax(bufnr, ns, hunk, code_lines, covered_lines, leading, p)
else
- extmark_count = highlight_vim_syntax(bufnr, ns, hunk, code_lines, covered_lines, 0, p)
+ extmark_count = highlight_vim_syntax(bufnr, ns, hunk, code_lines, covered_lines, leading, p)
end
end