diffs.nvim/lua/diffs/init.lua
Barrett Ruth cb38865b96
fix: warn users when fugitive/neogit/diff integrations are unconfigured (#123)
## Problem

Commit 0f27488 changed fugitive and neogit integrations from enabled by
default
to disabled by default. Users who never explicitly set these keys in
their config
saw no deprecation notice and silently lost integration support. The
existing
deprecation warning only fires for the old `filetypes` key, missing the
far more
common case of users who had no explicit config at all.

## Solution

Add an ephemeral migration check in `init()` that emits a `vim.notify`
warning
at WARN level when `fugitive`, `neogit`, and `diff` (via
`extra_filetypes`) are
all absent from the user's config. This covers the gap between the old
`filetypes`
deprecation and users who relied on implicit defaults. To be removed in
0.3.0.
2026-02-15 16:48:19 -05:00

916 lines
26 KiB
Lua

---@class diffs.TreesitterConfig
---@field enabled boolean
---@field max_lines integer
---@class diffs.VimConfig
---@field enabled boolean
---@field max_lines integer
---@class diffs.IntraConfig
---@field enabled boolean
---@field algorithm string
---@field max_lines integer
---@class diffs.ContextConfig
---@field enabled boolean
---@field lines integer
---@class diffs.PrioritiesConfig
---@field clear integer
---@field syntax integer
---@field line_bg integer
---@field char_bg integer
---@class diffs.Highlights
---@field background boolean
---@field gutter boolean
---@field blend_alpha? number
---@field overrides? table<string, table>
---@field context diffs.ContextConfig
---@field treesitter diffs.TreesitterConfig
---@field vim diffs.VimConfig
---@field intra diffs.IntraConfig
---@field priorities diffs.PrioritiesConfig
---@class diffs.FugitiveConfig
---@field enabled boolean
---@field horizontal string|false
---@field vertical string|false
---@class diffs.NeogitConfig
---@field enabled boolean
---@class diffs.ConflictKeymaps
---@field ours string|false
---@field theirs string|false
---@field both string|false
---@field none string|false
---@field next string|false
---@field prev string|false
---@class diffs.ConflictConfig
---@field enabled boolean
---@field disable_diagnostics boolean
---@field show_virtual_text boolean
---@field format_virtual_text? fun(side: string, keymap: string|false): string?
---@field show_actions boolean
---@field priority integer
---@field keymaps diffs.ConflictKeymaps
---@class diffs.Config
---@field debug boolean|string
---@field hide_prefix boolean
---@field filetypes? string[] @deprecated use fugitive, neogit, extra_filetypes
---@field extra_filetypes string[]
---@field highlights diffs.Highlights
---@field fugitive diffs.FugitiveConfig
---@field neogit diffs.NeogitConfig
---@field conflict diffs.ConflictConfig
---@class diffs
---@field attach fun(bufnr?: integer)
---@field refresh fun(bufnr?: integer)
local M = {}
local highlight = require('diffs.highlight')
local log = require('diffs.log')
local parser = require('diffs.parser')
local ns = vim.api.nvim_create_namespace('diffs')
---@param hex integer
---@param bg_hex integer
---@param alpha number
---@return integer
local function blend_color(hex, bg_hex, alpha)
---@diagnostic disable: undefined-global
local r = bit.band(bit.rshift(hex, 16), 0xFF)
local g = bit.band(bit.rshift(hex, 8), 0xFF)
local b = bit.band(hex, 0xFF)
local bg_r = bit.band(bit.rshift(bg_hex, 16), 0xFF)
local bg_g = bit.band(bit.rshift(bg_hex, 8), 0xFF)
local bg_b = bit.band(bg_hex, 0xFF)
local blend_r = math.floor(r * alpha + bg_r * (1 - alpha))
local blend_g = math.floor(g * alpha + bg_g * (1 - alpha))
local blend_b = math.floor(b * alpha + bg_b * (1 - alpha))
return bit.bor(bit.lshift(blend_r, 16), bit.lshift(blend_g, 8), blend_b)
---@diagnostic enable: undefined-global
end
---@param name string
---@return table
local function resolve_hl(name)
local hl = vim.api.nvim_get_hl(0, { name = name })
while hl.link do
hl = vim.api.nvim_get_hl(0, { name = hl.link })
end
return hl
end
---@type diffs.Config
local default_config = {
debug = false,
hide_prefix = false,
extra_filetypes = {},
highlights = {
background = true,
gutter = true,
context = {
enabled = true,
lines = 25,
},
treesitter = {
enabled = true,
max_lines = 500,
},
vim = {
enabled = false,
max_lines = 200,
},
intra = {
enabled = true,
algorithm = 'default',
max_lines = 500,
},
priorities = {
clear = 198,
syntax = 199,
line_bg = 200,
char_bg = 201,
},
},
fugitive = {
enabled = false,
horizontal = 'du',
vertical = 'dU',
},
neogit = {
enabled = false,
},
conflict = {
enabled = true,
disable_diagnostics = true,
show_virtual_text = true,
show_actions = false,
priority = 200,
keymaps = {
ours = 'doo',
theirs = 'dot',
both = 'dob',
none = 'don',
next = ']x',
prev = '[x',
},
},
}
---@type diffs.Config
local config = vim.deepcopy(default_config)
local initialized = false
---@diagnostic disable-next-line: missing-fields
local fast_hl_opts = {} ---@type diffs.HunkOpts
---@type table<integer, boolean>
local attached_buffers = {}
---@type table<integer, boolean>
local diff_windows = {}
---@class diffs.HunkCacheEntry
---@field hunks diffs.Hunk[]
---@field tick integer
---@field highlighted table<integer, true>
---@field pending_clear boolean
---@field line_count integer
---@field byte_count integer
---@type table<integer, diffs.HunkCacheEntry>
local hunk_cache = {}
---@param bufnr integer
---@return boolean
function M.is_fugitive_buffer(bufnr)
return vim.api.nvim_buf_get_name(bufnr):match('^fugitive://') ~= nil
end
---@param opts table
---@return string[]
function M.compute_filetypes(opts)
if opts.filetypes then
return opts.filetypes
end
local fts = { 'git', 'gitcommit' }
local fug = opts.fugitive
if fug == true or (type(fug) == 'table' and fug.enabled ~= false) then
table.insert(fts, 'fugitive')
end
local neo = opts.neogit
if neo == true or (type(neo) == 'table' and neo.enabled ~= false) then
table.insert(fts, 'NeogitStatus')
table.insert(fts, 'NeogitCommitView')
table.insert(fts, 'NeogitDiffView')
end
if type(opts.extra_filetypes) == 'table' then
for _, ft in ipairs(opts.extra_filetypes) do
table.insert(fts, ft)
end
end
return fts
end
local dbg = log.dbg
---@param bufnr integer
local function invalidate_cache(bufnr)
local entry = hunk_cache[bufnr]
if entry then
entry.tick = -1
entry.pending_clear = true
end
end
---@param bufnr integer
local function ensure_cache(bufnr)
if not vim.api.nvim_buf_is_valid(bufnr) then
return
end
local tick = vim.api.nvim_buf_get_changedtick(bufnr)
local entry = hunk_cache[bufnr]
if entry and entry.tick == tick then
return
end
if entry and not entry.pending_clear then
local lc = vim.api.nvim_buf_line_count(bufnr)
local bc = vim.api.nvim_buf_get_offset(bufnr, lc)
if lc == entry.line_count and bc == entry.byte_count then
entry.tick = tick
entry.pending_clear = true
dbg('content unchanged in buffer %d (tick %d), skipping reparse', bufnr, tick)
return
end
end
local hunks = parser.parse_buffer(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)
hunk_cache[bufnr] = {
hunks = hunks,
tick = tick,
highlighted = {},
pending_clear = true,
line_count = lc,
byte_count = bc,
}
local has_nil_ft = false
for _, hunk in ipairs(hunks) do
if not has_nil_ft and not hunk.ft and hunk.filename then
has_nil_ft = true
end
end
if has_nil_ft and vim.fn.did_filetype() ~= 0 then
vim.schedule(function()
if vim.api.nvim_buf_is_valid(bufnr) and hunk_cache[bufnr] then
dbg('retrying filetype detection for buffer %d (was blocked by did_filetype)', bufnr)
invalidate_cache(bufnr)
end
end)
end
end
---@param hunks diffs.Hunk[]
---@param toprow integer
---@param botrow integer
---@return integer first
---@return integer last
local function find_visible_hunks(hunks, toprow, botrow)
local n = #hunks
if n == 0 then
return 0, 0
end
local lo, hi = 1, n + 1
while lo < hi do
local mid = math.floor((lo + hi) / 2)
local h = hunks[mid]
local bottom = h.start_line - 1 + #h.lines - 1
if bottom < toprow then
lo = mid + 1
else
hi = mid
end
end
if lo > n then
return 0, 0
end
local first = lo
local h = hunks[first]
local top = (h.header_start_line and (h.header_start_line - 1)) or (h.start_line - 1)
if top >= botrow then
return 0, 0
end
local last = first
for i = first + 1, n do
h = hunks[i]
top = (h.header_start_line and (h.header_start_line - 1)) or (h.start_line - 1)
if top >= botrow then
break
end
last = i
end
return first, last
end
local function compute_highlight_groups()
local normal = vim.api.nvim_get_hl(0, { name = 'Normal' })
local diff_add = vim.api.nvim_get_hl(0, { name = 'DiffAdd' })
local diff_delete = vim.api.nvim_get_hl(0, { name = 'DiffDelete' })
local diff_added = resolve_hl('diffAdded')
local diff_removed = resolve_hl('diffRemoved')
local bg = normal.bg or 0x1e1e2e
local add_bg = diff_add.bg or 0x2e4a3a
local del_bg = diff_delete.bg or 0x4a2e3a
local add_fg = diff_added.fg or diff_add.fg or 0x80c080
local del_fg = diff_removed.fg or diff_delete.fg or 0xc08080
local blended_add = blend_color(add_bg, bg, 0.4)
local blended_del = blend_color(del_bg, bg, 0.4)
local alpha = config.highlights.blend_alpha or 0.6
local blended_add_text = blend_color(add_fg, bg, alpha)
local blended_del_text = blend_color(del_fg, bg, alpha)
vim.api.nvim_set_hl(0, 'DiffsClear', { default = true, fg = normal.fg or 0xc0c0c0 })
vim.api.nvim_set_hl(0, 'DiffsAdd', { default = true, bg = blended_add })
vim.api.nvim_set_hl(0, 'DiffsDelete', { default = true, bg = blended_del })
vim.api.nvim_set_hl(0, 'DiffsAddNr', { default = true, fg = blended_add_text, bg = blended_add })
vim.api.nvim_set_hl(
0,
'DiffsDeleteNr',
{ default = true, fg = blended_del_text, bg = blended_del }
)
vim.api.nvim_set_hl(0, 'DiffsAddText', { default = true, bg = blended_add_text })
vim.api.nvim_set_hl(0, 'DiffsDeleteText', { default = true, bg = blended_del_text })
dbg('highlight groups: Normal.bg=#%06x DiffAdd.bg=#%06x diffAdded.fg=#%06x', bg, add_bg, add_fg)
dbg(
'DiffsAdd.bg=#%06x DiffsAddText.bg=#%06x DiffsAddNr.fg=#%06x',
blended_add,
blended_add_text,
add_fg
)
dbg('DiffsDelete.bg=#%06x DiffsDeleteText.bg=#%06x', blended_del, blended_del_text)
local diff_change = resolve_hl('DiffChange')
local diff_text = resolve_hl('DiffText')
vim.api.nvim_set_hl(0, 'DiffsDiffAdd', { default = true, bg = diff_add.bg })
vim.api.nvim_set_hl(
0,
'DiffsDiffDelete',
{ default = true, fg = diff_delete.fg, bg = diff_delete.bg }
)
vim.api.nvim_set_hl(0, 'DiffsDiffChange', { default = true, bg = diff_change.bg })
vim.api.nvim_set_hl(0, 'DiffsDiffText', { default = true, bg = diff_text.bg })
local change_bg = diff_change.bg or 0x3a3a4a
local text_bg = diff_text.bg or 0x4a4a5a
local change_fg = diff_change.fg or diff_text.fg or 0x80a0c0
local blended_ours = blend_color(add_bg, bg, 0.4)
local blended_theirs = blend_color(change_bg, bg, 0.4)
local blended_base = blend_color(text_bg, bg, 0.3)
local blended_ours_nr = blend_color(add_fg, bg, alpha)
local blended_theirs_nr = blend_color(change_fg, bg, alpha)
local blended_base_nr = blend_color(change_fg, bg, 0.4)
vim.api.nvim_set_hl(0, 'DiffsConflictOurs', { default = true, bg = blended_ours })
vim.api.nvim_set_hl(0, 'DiffsConflictTheirs', { default = true, bg = blended_theirs })
vim.api.nvim_set_hl(0, 'DiffsConflictBase', { default = true, bg = blended_base })
vim.api.nvim_set_hl(0, 'DiffsConflictMarker', { default = true, fg = 0x808080, bold = true })
vim.api.nvim_set_hl(0, 'DiffsConflictActions', { default = true, fg = 0x808080 })
vim.api.nvim_set_hl(
0,
'DiffsConflictOursNr',
{ default = true, fg = blended_ours_nr, bg = blended_ours }
)
vim.api.nvim_set_hl(
0,
'DiffsConflictTheirsNr',
{ default = true, fg = blended_theirs_nr, bg = blended_theirs }
)
vim.api.nvim_set_hl(
0,
'DiffsConflictBaseNr',
{ default = true, fg = blended_base_nr, bg = blended_base }
)
if config.highlights.overrides then
for group, hl in pairs(config.highlights.overrides) do
vim.api.nvim_set_hl(0, group, hl)
end
end
end
local neogit_attached = false
local neogit_hl_groups = {
'NeogitDiffAdd',
'NeogitDiffAddCursor',
'NeogitDiffAddHighlight',
'NeogitDiffDelete',
'NeogitDiffDeleteCursor',
'NeogitDiffDeleteHighlight',
'NeogitDiffContext',
'NeogitDiffContextCursor',
'NeogitDiffContextHighlight',
'NeogitDiffHeader',
'NeogitDiffHeaderHighlight',
'NeogitHunkHeader',
'NeogitHunkHeaderCursor',
'NeogitHunkHeaderHighlight',
'NeogitHunkMergeHeader',
'NeogitHunkMergeHeaderCursor',
'NeogitHunkMergeHeaderHighlight',
}
local function override_neogit_highlights()
for _, name in ipairs(neogit_hl_groups) do
vim.api.nvim_set_hl(0, name, {})
end
end
local function init()
if initialized then
return
end
initialized = true
local opts = vim.g.diffs or {}
if opts.filetypes then
vim.deprecate(
'vim.g.diffs.filetypes',
'fugitive, neogit, and extra_filetypes',
'0.3.0',
'diffs.nvim'
)
end
if not opts.filetypes and opts.fugitive == nil and opts.neogit == nil then
local has_diff_ft = false
if type(opts.extra_filetypes) == 'table' then
for _, ft in ipairs(opts.extra_filetypes) do
if ft == 'diff' then
has_diff_ft = true
break
end
end
end
if not has_diff_ft then
vim.notify(
'[diffs.nvim] fugitive, neogit, and diff filetypes are now opt-in.\n'
.. 'Add the integrations you use to your config:\n\n'
.. ' vim.g.diffs = {\n'
.. ' fugitive = true,\n'
.. ' neogit = true,\n'
.. " extra_filetypes = { 'diff' },\n"
.. ' }\n\n'
.. 'This warning will be removed in 0.3.0.',
vim.log.levels.WARN
)
end
end
if opts.fugitive == true then
opts.fugitive = { enabled = true }
elseif opts.fugitive == false then
opts.fugitive = { enabled = false }
elseif opts.fugitive == nil then
opts.fugitive = nil
elseif type(opts.fugitive) == 'table' and opts.fugitive.enabled == nil then
opts.fugitive.enabled = true
end
if opts.neogit == true then
opts.neogit = { enabled = true }
elseif opts.neogit == false then
opts.neogit = { enabled = false }
elseif opts.neogit == nil then
opts.neogit = nil
elseif type(opts.neogit) == 'table' and opts.neogit.enabled == nil then
opts.neogit.enabled = true
end
vim.validate({
debug = {
opts.debug,
function(v)
return v == nil or type(v) == 'boolean' or type(v) == 'string'
end,
'boolean or string (file path)',
},
hide_prefix = { opts.hide_prefix, 'boolean', true },
fugitive = { opts.fugitive, 'table', true },
neogit = { opts.neogit, 'table', true },
extra_filetypes = { opts.extra_filetypes, 'table', true },
highlights = { opts.highlights, 'table', true },
})
if opts.highlights then
vim.validate({
['highlights.background'] = { opts.highlights.background, 'boolean', true },
['highlights.gutter'] = { opts.highlights.gutter, 'boolean', true },
['highlights.blend_alpha'] = { opts.highlights.blend_alpha, 'number', true },
['highlights.overrides'] = { opts.highlights.overrides, 'table', true },
['highlights.context'] = { opts.highlights.context, 'table', true },
['highlights.treesitter'] = { opts.highlights.treesitter, 'table', true },
['highlights.vim'] = { opts.highlights.vim, 'table', true },
['highlights.intra'] = { opts.highlights.intra, 'table', true },
['highlights.priorities'] = { opts.highlights.priorities, 'table', true },
})
if opts.highlights.context then
vim.validate({
['highlights.context.enabled'] = { opts.highlights.context.enabled, 'boolean', true },
['highlights.context.lines'] = { opts.highlights.context.lines, 'number', true },
})
end
if opts.highlights.treesitter then
vim.validate({
['highlights.treesitter.enabled'] = { opts.highlights.treesitter.enabled, 'boolean', true },
['highlights.treesitter.max_lines'] = {
opts.highlights.treesitter.max_lines,
'number',
true,
},
})
end
if opts.highlights.vim then
vim.validate({
['highlights.vim.enabled'] = { opts.highlights.vim.enabled, 'boolean', true },
['highlights.vim.max_lines'] = { opts.highlights.vim.max_lines, 'number', true },
})
end
if opts.highlights.intra then
vim.validate({
['highlights.intra.enabled'] = { opts.highlights.intra.enabled, 'boolean', true },
['highlights.intra.algorithm'] = {
opts.highlights.intra.algorithm,
function(v)
return v == nil or v == 'default' or v == 'vscode'
end,
"'default' or 'vscode'",
},
['highlights.intra.max_lines'] = { opts.highlights.intra.max_lines, 'number', true },
})
end
if opts.highlights.priorities then
vim.validate({
['highlights.priorities.clear'] = { opts.highlights.priorities.clear, 'number', true },
['highlights.priorities.syntax'] = { opts.highlights.priorities.syntax, 'number', true },
['highlights.priorities.line_bg'] = { opts.highlights.priorities.line_bg, 'number', true },
['highlights.priorities.char_bg'] = { opts.highlights.priorities.char_bg, 'number', true },
})
end
end
if opts.fugitive then
vim.validate({
['fugitive.enabled'] = { opts.fugitive.enabled, 'boolean', true },
['fugitive.horizontal'] = {
opts.fugitive.horizontal,
function(v)
return v == nil or v == false or type(v) == 'string'
end,
'string or false',
},
['fugitive.vertical'] = {
opts.fugitive.vertical,
function(v)
return v == nil or v == false or type(v) == 'string'
end,
'string or false',
},
})
end
if opts.neogit then
vim.validate({
['neogit.enabled'] = { opts.neogit.enabled, 'boolean', true },
})
end
if opts.conflict then
vim.validate({
['conflict.enabled'] = { opts.conflict.enabled, 'boolean', true },
['conflict.disable_diagnostics'] = { opts.conflict.disable_diagnostics, 'boolean', true },
['conflict.show_virtual_text'] = { opts.conflict.show_virtual_text, 'boolean', true },
['conflict.format_virtual_text'] = { opts.conflict.format_virtual_text, 'function', true },
['conflict.show_actions'] = { opts.conflict.show_actions, 'boolean', true },
['conflict.priority'] = { opts.conflict.priority, 'number', true },
['conflict.keymaps'] = { opts.conflict.keymaps, 'table', true },
})
if opts.conflict.keymaps then
local keymap_validator = function(v)
return v == false or type(v) == 'string'
end
for _, key in ipairs({ 'ours', 'theirs', 'both', 'none', 'next', 'prev' }) do
vim.validate({
['conflict.keymaps.' .. key] = {
opts.conflict.keymaps[key],
keymap_validator,
'string or false',
},
})
end
end
end
if
opts.highlights
and opts.highlights.context
and opts.highlights.context.lines
and opts.highlights.context.lines < 0
then
error('diffs: highlights.context.lines must be >= 0')
end
if
opts.highlights
and opts.highlights.treesitter
and opts.highlights.treesitter.max_lines
and opts.highlights.treesitter.max_lines < 1
then
error('diffs: highlights.treesitter.max_lines must be >= 1')
end
if
opts.highlights
and opts.highlights.vim
and opts.highlights.vim.max_lines
and opts.highlights.vim.max_lines < 1
then
error('diffs: highlights.vim.max_lines must be >= 1')
end
if
opts.highlights
and opts.highlights.intra
and opts.highlights.intra.max_lines
and opts.highlights.intra.max_lines < 1
then
error('diffs: highlights.intra.max_lines must be >= 1')
end
if
opts.highlights
and opts.highlights.blend_alpha
and (opts.highlights.blend_alpha < 0 or opts.highlights.blend_alpha > 1)
then
error('diffs: highlights.blend_alpha must be >= 0 and <= 1')
end
if opts.highlights and opts.highlights.priorities then
for _, key in ipairs({ 'clear', 'syntax', 'line_bg', 'char_bg' }) do
local v = opts.highlights.priorities[key]
if v and v < 0 then
error('diffs: highlights.priorities.' .. key .. ' must be >= 0')
end
end
end
if opts.conflict and opts.conflict.priority and opts.conflict.priority < 0 then
error('diffs: conflict.priority must be >= 0')
end
config = vim.tbl_deep_extend('force', default_config, opts)
log.set_enabled(config.debug)
fast_hl_opts = {
hide_prefix = config.hide_prefix,
highlights = vim.tbl_deep_extend('force', config.highlights, {
treesitter = { enabled = false },
}),
defer_vim_syntax = true,
}
compute_highlight_groups()
vim.api.nvim_create_autocmd('ColorScheme', {
callback = function()
compute_highlight_groups()
if neogit_attached then
vim.schedule(override_neogit_highlights)
end
for bufnr, _ in pairs(attached_buffers) do
invalidate_cache(bufnr)
end
end,
})
vim.api.nvim_set_decoration_provider(ns, {
on_buf = function(_, bufnr)
if not attached_buffers[bufnr] then
return false
end
local t0 = config.debug and vim.uv.hrtime() or nil
ensure_cache(bufnr)
local entry = hunk_cache[bufnr]
if entry and entry.pending_clear then
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
entry.highlighted = {}
entry.pending_clear = false
end
if t0 then
dbg('on_buf %d: %.2fms', bufnr, (vim.uv.hrtime() - t0) / 1e6)
end
end,
on_win = function(_, _, bufnr, toprow, botrow)
if not attached_buffers[bufnr] then
return false
end
local entry = hunk_cache[bufnr]
if not entry then
return
end
local first, last = find_visible_hunks(entry.hunks, toprow, botrow)
if first == 0 then
return
end
local t0 = config.debug and vim.uv.hrtime() or nil
local deferred_syntax = {}
local count = 0
for i = first, last do
if not entry.highlighted[i] then
local hunk = entry.hunks[i]
highlight.highlight_hunk(bufnr, ns, hunk, fast_hl_opts)
entry.highlighted[i] = true
count = count + 1
local has_syntax = hunk.lang and config.highlights.treesitter.enabled
local needs_vim = not hunk.lang and hunk.ft and config.highlights.vim.enabled
if has_syntax or needs_vim then
table.insert(deferred_syntax, hunk)
end
end
end
if #deferred_syntax > 0 then
local tick = entry.tick
vim.schedule(function()
if not vim.api.nvim_buf_is_valid(bufnr) then
return
end
local cur = hunk_cache[bufnr]
if not cur or cur.tick ~= tick then
return
end
local t1 = config.debug and vim.uv.hrtime() or nil
local full_opts = {
hide_prefix = config.hide_prefix,
highlights = config.highlights,
}
for _, hunk in ipairs(deferred_syntax) do
local start_row = hunk.start_line - 1
local end_row = start_row + #hunk.lines
if hunk.header_start_line then
start_row = hunk.header_start_line - 1
end
vim.api.nvim_buf_clear_namespace(bufnr, ns, start_row, end_row)
highlight.highlight_hunk(bufnr, ns, hunk, full_opts)
if not hunk.lang and hunk.ft then
highlight.highlight_hunk_vim_syntax(bufnr, ns, hunk, full_opts)
end
end
if t1 then
dbg('deferred pass: %d hunks in %.2fms', #deferred_syntax, (vim.uv.hrtime() - t1) / 1e6)
end
end)
end
if t0 and count > 0 then
dbg(
'on_win %d: %d hunks [%d..%d] in %.2fms (viewport %d-%d)',
bufnr,
count,
first,
last,
(vim.uv.hrtime() - t0) / 1e6,
toprow,
botrow
)
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
---@param bufnr? integer
function M.attach(bufnr)
init()
bufnr = bufnr or vim.api.nvim_get_current_buf()
if attached_buffers[bufnr] then
return
end
attached_buffers[bufnr] = true
if not neogit_attached and config.neogit.enabled and vim.bo[bufnr].filetype:match('^Neogit') then
neogit_attached = true
vim.schedule(override_neogit_highlights)
end
dbg('attaching to buffer %d', bufnr)
ensure_cache(bufnr)
vim.api.nvim_create_autocmd('BufWipeout', {
buffer = bufnr,
callback = function()
attached_buffers[bufnr] = nil
hunk_cache[bufnr] = nil
end,
})
end
---@param bufnr? integer
function M.refresh(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
invalidate_cache(bufnr)
end
local DIFF_WINHIGHLIGHT = table.concat({
'DiffAdd:DiffsDiffAdd',
'DiffDelete:DiffsDiffDelete',
'DiffChange:DiffsDiffChange',
'DiffText:DiffsDiffText',
}, ',')
function M.attach_diff()
init()
local tabpage = vim.api.nvim_get_current_tabpage()
local wins = vim.api.nvim_tabpage_list_wins(tabpage)
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)
end
end
if #diff_wins == 0 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
---@return diffs.FugitiveConfig
function M.get_fugitive_config()
init()
return config.fugitive
end
---@return diffs.ConflictConfig
function M.get_conflict_config()
init()
return config.conflict
end
M._test = {
find_visible_hunks = find_visible_hunks,
hunk_cache = hunk_cache,
ensure_cache = ensure_cache,
invalidate_cache = invalidate_cache,
}
return M