Merge pull request #33 from barrettruth/feat/diffsplit
highlighting for diffsplit & more fugitive buftypes
This commit is contained in:
commit
e25af2503e
5 changed files with 148 additions and 15 deletions
13
README.md
13
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
||||
|
|
@ -62,6 +70,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 +102,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)
|
||||
|
|
@ -135,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*
|
||||
|
||||
|
|
|
|||
|
|
@ -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<integer, boolean>
|
||||
local attached_buffers = {}
|
||||
|
||||
---@type table<integer, boolean>
|
||||
local diff_windows = {}
|
||||
|
||||
---@param bufnr integer
|
||||
---@return boolean
|
||||
function M.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 M.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 },
|
||||
|
|
@ -273,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,8 +4,23 @@ 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,
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue