fix(parser): detect filetype from existing buffer

Files detected via shebang or modeline (e.g., `build` with `#!/bin/bash`)
weren't getting syntax highlighting because `vim.filetype.match()` only
does filename-based detection.

Now `get_ft_from_filename()` first checks if a buffer already exists for
the file and uses its detected filetype. This requires knowing the repo
root to construct the full path, so:

- `parse_buffer()` reads `b:diffs_repo_root` or `b:git_dir` (fugitive)
- `commands.lua` sets `b:diffs_repo_root` on diff buffers it creates

Co-authored-by: phanen <phanen@qq.com>
This commit is contained in:
Barrett Ruth 2026-02-05 00:13:32 -05:00
parent b4e40e4093
commit c51d625dc6
3 changed files with 98 additions and 2 deletions

View file

@ -70,6 +70,8 @@ function M.gdiff(revision, vertical)
return
end
local repo_root = git.get_repo_root(filepath)
local diff_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(diff_buf, 0, -1, false, diff_lines)
vim.api.nvim_set_option_value('buftype', 'nofile', { buf = diff_buf })
@ -77,6 +79,9 @@ function M.gdiff(revision, vertical)
vim.api.nvim_set_option_value('modifiable', false, { buf = diff_buf })
vim.api.nvim_set_option_value('filetype', 'diff', { buf = diff_buf })
vim.api.nvim_buf_set_name(diff_buf, 'diffs://' .. revision .. ':' .. rel_path)
if repo_root then
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
end
vim.cmd(vertical and 'vsplit' or 'split')
vim.api.nvim_win_set_buf(0, diff_buf)
@ -154,6 +159,8 @@ function M.gdiff_file(filepath, opts)
return
end
local repo_root = git.get_repo_root(filepath)
local diff_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(diff_buf, 0, -1, false, diff_lines)
vim.api.nvim_set_option_value('buftype', 'nofile', { buf = diff_buf })
@ -161,6 +168,9 @@ function M.gdiff_file(filepath, opts)
vim.api.nvim_set_option_value('modifiable', false, { buf = diff_buf })
vim.api.nvim_set_option_value('filetype', 'diff', { buf = diff_buf })
vim.api.nvim_buf_set_name(diff_buf, 'diffs://' .. diff_label .. ':' .. rel_path)
if repo_root then
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
end
vim.cmd(opts.vertical and 'vsplit' or 'split')
vim.api.nvim_win_set_buf(0, diff_buf)
@ -205,6 +215,7 @@ function M.gdiff_section(repo_root, opts)
vim.api.nvim_set_option_value('modifiable', false, { buf = diff_buf })
vim.api.nvim_set_option_value('filetype', 'diff', { buf = diff_buf })
vim.api.nvim_buf_set_name(diff_buf, 'diffs://' .. diff_label .. ':all')
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
vim.cmd(opts.vertical and 'vsplit' or 'split')
vim.api.nvim_win_set_buf(0, diff_buf)

View file

@ -14,8 +14,21 @@ local M = {}
local dbg = require('diffs.log').dbg
---@param filename string
---@param repo_root string?
---@return string?
local function get_ft_from_filename(filename)
local function get_ft_from_filename(filename, repo_root)
if repo_root then
local full_path = vim.fs.joinpath(repo_root, filename)
local buf = vim.fn.bufnr(full_path)
if buf ~= -1 then
local ft = vim.api.nvim_get_option_value('filetype', { buf = buf })
if ft and ft ~= '' then
dbg('filetype from existing buffer %d: %s', buf, ft)
return ft
end
end
end
local ft = vim.filetype.match({ filename = filename })
if not ft then
dbg('no filetype for: %s', filename)
@ -39,10 +52,27 @@ local function get_lang_from_ft(ft)
return nil
end
---@param bufnr integer
---@return string?
local function get_repo_root(bufnr)
local ok, repo_root = pcall(vim.api.nvim_buf_get_var, bufnr, 'diffs_repo_root')
if ok and repo_root then
return repo_root
end
local ok2, git_dir = pcall(vim.api.nvim_buf_get_var, bufnr, 'git_dir')
if ok2 and git_dir then
return vim.fn.fnamemodify(git_dir, ':h')
end
return nil
end
---@param bufnr integer
---@return diffs.Hunk[]
function M.parse_buffer(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local repo_root = get_repo_root(bufnr)
---@type diffs.Hunk[]
local hunks = {}
@ -95,7 +125,7 @@ function M.parse_buffer(bufnr)
if filename then
flush_hunk()
current_filename = filename
current_ft = get_ft_from_filename(filename)
current_ft = get_ft_from_filename(filename, repo_root)
current_lang = current_ft and get_lang_from_ft(current_ft) or nil
if current_lang then
dbg('file: %s -> lang: %s', filename, current_lang)

View file

@ -304,5 +304,60 @@ describe('parser', function()
assert.are.equal(4, #hunks[1].lines)
delete_buffer(bufnr)
end)
it('uses filetype from existing buffer when available', function()
local repo_root = '/tmp/test-repo'
local file_path = repo_root .. '/build'
local file_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(file_buf, file_path)
vim.api.nvim_set_option_value('filetype', 'bash', { buf = file_buf })
local diff_buf = create_buffer({
'M build',
'@@ -1,2 +1,3 @@',
' echo "hello"',
'+set -e',
' echo "done"',
})
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
local hunks = parser.parse_buffer(diff_buf)
assert.are.equal(1, #hunks)
assert.are.equal('build', hunks[1].filename)
assert.are.equal('bash', hunks[1].ft)
delete_buffer(file_buf)
delete_buffer(diff_buf)
end)
it('uses filetype from existing buffer via git_dir', function()
local git_dir = '/tmp/test-repo/.git'
local repo_root = '/tmp/test-repo'
local file_path = repo_root .. '/script'
local file_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(file_buf, file_path)
vim.api.nvim_set_option_value('filetype', 'python', { buf = file_buf })
local diff_buf = create_buffer({
'M script',
'@@ -1,2 +1,3 @@',
' def main():',
'+ print("hi")',
' pass',
})
vim.api.nvim_buf_set_var(diff_buf, 'git_dir', git_dir)
local hunks = parser.parse_buffer(diff_buf)
assert.are.equal(1, #hunks)
assert.are.equal('script', hunks[1].filename)
assert.are.equal('python', hunks[1].ft)
delete_buffer(file_buf)
delete_buffer(diff_buf)
end)
end)
end)