From 9b700ce8692f77da617d293301c2f0259a5be641 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Feb 2026 16:37:51 -0500 Subject: [PATCH 1/5] feat: diffsplit support --- lua/fugitive-ts/init.lua | 79 ++++++++++++++++++++++++++++++++++++++++ plugin/fugitive-ts.lua | 11 ++++++ 2 files changed, 90 insertions(+) diff --git a/lua/fugitive-ts/init.lua b/lua/fugitive-ts/init.lua index 5a2d064..0f42e7e 100644 --- a/lua/fugitive-ts/init.lua +++ b/lua/fugitive-ts/init.lua @@ -10,6 +10,9 @@ ---@field enabled boolean ---@field max_lines integer +---@class fugitive-ts.DiffsplitConfig +---@field enabled boolean + ---@class fugitive-ts.Config ---@field enabled boolean ---@field debug boolean @@ -18,6 +21,7 @@ ---@field treesitter fugitive-ts.TreesitterConfig ---@field vim fugitive-ts.VimConfig ---@field highlights fugitive-ts.Highlights +---@field diffsplit fugitive-ts.DiffsplitConfig ---@class fugitive-ts ---@field attach fun(bufnr?: integer) @@ -80,6 +84,9 @@ local default_config = { background = true, gutter = true, }, + diffsplit = { + enabled = true, + }, } ---@type fugitive-ts.Config @@ -88,6 +95,15 @@ local config = vim.deepcopy(default_config) ---@type table local attached_buffers = {} +---@type table +local diff_windows = {} + +---@param bufnr integer +---@return boolean +local function is_fugitive_buffer(bufnr) + return vim.api.nvim_buf_get_name(bufnr):match('^fugitive://') ~= nil +end + ---@param msg string ---@param ... any local function dbg(msg, ...) @@ -222,6 +238,62 @@ local function compute_highlight_groups() vim.api.nvim_set_hl(0, 'FugitiveTsDelete', { bg = blended_del }) vim.api.nvim_set_hl(0, 'FugitiveTsAddNr', { fg = add_fg, bg = blended_add }) vim.api.nvim_set_hl(0, 'FugitiveTsDeleteNr', { fg = del_fg, bg = blended_del }) + + local diff_change = resolve_hl('DiffChange') + local diff_text = resolve_hl('DiffText') + + vim.api.nvim_set_hl(0, 'FugitiveTsDiffAdd', { bg = diff_add.bg }) + vim.api.nvim_set_hl(0, 'FugitiveTsDiffDelete', { bg = diff_delete.bg }) + vim.api.nvim_set_hl(0, 'FugitiveTsDiffChange', { bg = diff_change.bg }) + vim.api.nvim_set_hl(0, 'FugitiveTsDiffText', { bg = diff_text.bg }) +end + +local DIFF_WINHIGHLIGHT = table.concat({ + 'DiffAdd:FugitiveTsDiffAdd', + 'DiffDelete:FugitiveTsDiffDelete', + 'DiffChange:FugitiveTsDiffChange', + 'DiffText:FugitiveTsDiffText', +}, ',') + +function M.attach_diff() + if not config.enabled or not config.diffsplit.enabled then + return + end + + local tabpage = vim.api.nvim_get_current_tabpage() + local wins = vim.api.nvim_tabpage_list_wins(tabpage) + + local has_fugitive = false + local diff_wins = {} + + for _, win in ipairs(wins) do + if vim.api.nvim_win_is_valid(win) and vim.wo[win].diff then + table.insert(diff_wins, win) + local bufnr = vim.api.nvim_win_get_buf(win) + if is_fugitive_buffer(bufnr) then + has_fugitive = true + end + end + end + + if not has_fugitive then + return + end + + for _, win in ipairs(diff_wins) do + vim.api.nvim_set_option_value('winhighlight', DIFF_WINHIGHLIGHT, { win = win }) + diff_windows[win] = true + dbg('applied diff winhighlight to window %d', win) + end +end + +function M.detach_diff() + for win, _ in pairs(diff_windows) do + if vim.api.nvim_win_is_valid(win) then + vim.api.nvim_set_option_value('winhighlight', '', { win = win }) + end + diff_windows[win] = nil + end end ---@param opts? fugitive-ts.Config @@ -236,8 +308,15 @@ function M.setup(opts) treesitter = { opts.treesitter, 'table', true }, vim = { opts.vim, 'table', true }, highlights = { opts.highlights, 'table', true }, + diffsplit = { opts.diffsplit, 'table', true }, }) + if opts.diffsplit then + vim.validate({ + ['diffsplit.enabled'] = { opts.diffsplit.enabled, 'boolean', true }, + }) + end + if opts.treesitter then vim.validate({ ['treesitter.enabled'] = { opts.treesitter.enabled, 'boolean', true }, diff --git a/plugin/fugitive-ts.lua b/plugin/fugitive-ts.lua index cfe7a03..6b815ce 100644 --- a/plugin/fugitive-ts.lua +++ b/plugin/fugitive-ts.lua @@ -9,3 +9,14 @@ vim.api.nvim_create_autocmd('FileType', { require('fugitive-ts').attach(args.buf) end, }) + +vim.api.nvim_create_autocmd('OptionSet', { + pattern = 'diff', + callback = function() + if vim.wo.diff then + require('fugitive-ts').attach_diff() + else + require('fugitive-ts').detach_diff() + end + end, +}) From e2a62a6aa56a64bbeaa1ff78977e8b5371e63416 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Feb 2026 16:39:37 -0500 Subject: [PATCH 2/5] feat: doc updates and cleanups --- doc/fugitive-ts.nvim.txt | 13 +++++++++++++ lua/fugitive-ts/init.lua | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/doc/fugitive-ts.nvim.txt b/doc/fugitive-ts.nvim.txt index 7ade1bc..1bfd9e4 100644 --- a/doc/fugitive-ts.nvim.txt +++ b/doc/fugitive-ts.nvim.txt @@ -62,6 +62,10 @@ CONFIGURATION *fugitive-ts-config* Vim syntax highlighting options (experimental). See |fugitive-ts.VimConfig| for fields. + {diffsplit} (table, default: see below) + Diffsplit highlighting options. + See |fugitive-ts.DiffsplitConfig| for fields. + {highlights} (table, default: see below) Controls which highlight features are enabled. See |fugitive-ts.Highlights| for fields. @@ -90,6 +94,15 @@ CONFIGURATION *fugitive-ts-config* this many lines. Lower than the treesitter default due to the per-character cost of |synID()|. + *fugitive-ts.DiffsplitConfig* + Diffsplit config fields: ~ + {enabled} (boolean, default: true) + Override diff highlight foreground colors in + |:Gdiffsplit| and |:Gvdiffsplit| windows so + treesitter syntax is visible through the diff + backgrounds. Uses window-local 'winhighlight' + and only applies to fugitive-owned buffers. + *fugitive-ts.Highlights* Highlights table fields: ~ {background} (boolean, default: true) diff --git a/lua/fugitive-ts/init.lua b/lua/fugitive-ts/init.lua index 0f42e7e..f76085e 100644 --- a/lua/fugitive-ts/init.lua +++ b/lua/fugitive-ts/init.lua @@ -352,6 +352,15 @@ function M.setup(opts) end end, }) + + vim.api.nvim_create_autocmd('WinClosed', { + callback = function(args) + local win = tonumber(args.match) + if win and diff_windows[win] then + diff_windows[win] = nil + end + end, + }) end return M From 686b02b790dff7b40fcd997c3ca7759d0cdafe69 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Feb 2026 16:46:58 -0500 Subject: [PATCH 3/5] feat: update autocmds for git filetypes with `fugiive:///` bufnames --- lua/fugitive-ts/init.lua | 4 ++-- lua/fugitive-ts/parser.lua | 2 +- plugin/fugitive-ts.lua | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lua/fugitive-ts/init.lua b/lua/fugitive-ts/init.lua index f76085e..f7f46c9 100644 --- a/lua/fugitive-ts/init.lua +++ b/lua/fugitive-ts/init.lua @@ -100,7 +100,7 @@ local diff_windows = {} ---@param bufnr integer ---@return boolean -local function is_fugitive_buffer(bufnr) +function M.is_fugitive_buffer(bufnr) return vim.api.nvim_buf_get_name(bufnr):match('^fugitive://') ~= nil end @@ -270,7 +270,7 @@ function M.attach_diff() if vim.api.nvim_win_is_valid(win) and vim.wo[win].diff then table.insert(diff_wins, win) local bufnr = vim.api.nvim_win_get_buf(win) - if is_fugitive_buffer(bufnr) then + if M.is_fugitive_buffer(bufnr) then has_fugitive = true end end diff --git a/lua/fugitive-ts/parser.lua b/lua/fugitive-ts/parser.lua index 025186b..e5b720e 100644 --- a/lua/fugitive-ts/parser.lua +++ b/lua/fugitive-ts/parser.lua @@ -93,7 +93,7 @@ function M.parse_buffer(bufnr) end for i, line in ipairs(lines) do - local filename = line:match('^[MADRC%?!]%s+(.+)$') + local filename = line:match('^[MADRC%?!]%s+(.+)$') or line:match('^diff %-%-git a/.+ b/(.+)$') if filename then flush_hunk() current_filename = filename diff --git a/plugin/fugitive-ts.lua b/plugin/fugitive-ts.lua index 6b815ce..dae4eee 100644 --- a/plugin/fugitive-ts.lua +++ b/plugin/fugitive-ts.lua @@ -4,9 +4,13 @@ end vim.g.loaded_fugitive_ts = 1 vim.api.nvim_create_autocmd('FileType', { - pattern = 'fugitive', + pattern = { 'fugitive', 'git' }, callback = function(args) - require('fugitive-ts').attach(args.buf) + local ft = require('fugitive-ts') + if args.match == 'git' and not ft.is_fugitive_buffer(args.buf) then + return + end + ft.attach(args.buf) end, }) From ec028dd0e5b1a39480fd8eb6eff2642159f24eff Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Feb 2026 16:51:48 -0500 Subject: [PATCH 4/5] feat(doc): more updates --- doc/fugitive-ts.nvim.txt | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/doc/fugitive-ts.nvim.txt b/doc/fugitive-ts.nvim.txt index 1bfd9e4..7afeeb1 100644 --- a/doc/fugitive-ts.nvim.txt +++ b/doc/fugitive-ts.nvim.txt @@ -10,6 +10,14 @@ fugitive-ts.nvim adds treesitter-based syntax highlighting to vim-fugitive diff views. It overlays language-aware highlights on top of fugitive's default regex-based diff highlighting. +Features: ~ +- Syntax highlighting in |:Git| summary diffs and commit detail views +- Syntax highlighting in |:Gdiffsplit| / |:Gvdiffsplit| side-by-side diffs +- Vim syntax fallback for languages without a treesitter parser +- Blended diff background colors that preserve syntax visibility +- Optional diff prefix (`+`/`-`/` `) concealment +- Gutter (line number) highlighting + ============================================================================== REQUIREMENTS *fugitive-ts-requirements* @@ -148,19 +156,31 @@ refresh({bufnr}) *fugitive-ts.refresh()* ============================================================================== IMPLEMENTATION *fugitive-ts-implementation* -1. The `FileType fugitive` autocmd triggers |fugitive-ts.attach()| -2. The buffer is parsed to detect file headers (`M path/to/file.lua`) and - hunk headers (`@@ -10,3 +10,4 @@`) +Summary / commit detail views: ~ +1. `FileType fugitive` or `FileType git` (for `fugitive://` buffers) + triggers |fugitive-ts.attach()| +2. The buffer is parsed to detect file headers (`M path/to/file`, + `diff --git a/... b/...`) and hunk headers (`@@ -10,3 +10,4 @@`) 3. For each hunk: - Language is detected from the filename using |vim.filetype.match()| - Diff prefixes (`+`/`-`/` `) are stripped from code lines - Code is parsed with |vim.treesitter.get_string_parser()| + - If no treesitter parser and `vim.enabled`: vim syntax fallback via + scratch buffer and |synID()| - Background extmarks (`FugitiveTsAdd`/`FugitiveTsDelete`) at priority 198 - `Normal` extmarks at priority 199 clear underlying diff foreground - - Treesitter highlights are applied as extmarks at priority 200 + - Syntax highlights are applied as extmarks at priority 200 - Conceal extmarks hide diff prefixes when `hide_prefix` is enabled 4. Re-highlighting occurs on `TextChanged` (debounced) and `Syntax` events +Diffsplit views: ~ +1. `OptionSet diff` detects when a window enters diff mode +2. If any `&diff` window in the tabpage contains a `fugitive://` buffer, + all `&diff` windows receive a window-local 'winhighlight' override +3. The override remaps `DiffAdd`/`DiffDelete`/`DiffChange`/`DiffText` to + background-only variants, allowing existing treesitter highlighting to + show through the diff colors + ============================================================================== KNOWN LIMITATIONS *fugitive-ts-limitations* From 0b9a914f7e72caa441597d89847d86203f74e412 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Feb 2026 16:55:21 -0500 Subject: [PATCH 5/5] feat(doc): update to show more highlighting --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7318bb5..5d08a83 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,11 @@ diffs. ## Features -- **Language-aware highlighting**: Full treesitter syntax highlighting for code - in diff hunks -- **Automatic language detection**: Detects language from filenames using - Neovim's filetype detection -- **Header context highlighting**: Highlights function signatures in hunk - headers (`@@ ... @@ function foo()`) -- **Performance optimized**: Debounced updates, configurable max lines per hunk -- **Zero configuration**: Works out of the box with sensible defaults +- Treesitter syntax highlighting in `:Git` diffs and commit views +- `:Gdiffsplit` / `:Gvdiffsplit` syntax through diff backgrounds +- Vim syntax fallback for languages without a treesitter parser +- Hunk header context highlighting (`@@ ... @@ function foo()`) +- Configurable debouncing, max lines, and diff prefix concealment ## Requirements