From 83c17aca6708d01b07f4046056e7009ffa4df178 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 6 Mar 2026 14:11:28 -0500 Subject: [PATCH 1/3] fix(test): use valid hex hash in combined diff fixtures Problem: Combined diff test fixtures used `ghi9012` as a result hash, but `g`, `h`, `i` are not hex digits (`%x` matches `[0-9a-fA-F]`). The `%x+` pattern in `highlight.lua` correctly rejected this, so the manual `@constant.diff` extmark was never set. The existing assertion passed anyway because the diff grammar's captures on parent hashes (`abc1234`, `def5678`) satisfied the row-level check. Solution: Replace `ghi9012` with valid hex `a6b9012` in all fixtures. Tighten the result hash assertion to verify exact column range (23-30) so it cannot be satisfied by parent hash captures. --- spec/highlight_spec.lua | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/spec/highlight_spec.lua b/spec/highlight_spec.lua index 39cc23c..c6e1a2a 100644 --- a/spec/highlight_spec.lua +++ b/spec/highlight_spec.lua @@ -1654,7 +1654,7 @@ describe('highlight', function() it('applies DiffsClear to headers for combined diffs', function() local bufnr = create_buffer({ 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', '@@@ -1,2 -1,2 +1,3 @@@', @@ -1671,7 +1671,7 @@ describe('highlight', function() header_start_line = 1, header_lines = { 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', }, @@ -1755,7 +1755,7 @@ describe('highlight', function() it('applies header diff grammar at syntax priority for combined diffs', function() local bufnr = create_buffer({ 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', '@@@ -1,2 -1,2 +1,3 @@@', @@ -1772,7 +1772,7 @@ describe('highlight', function() header_start_line = 1, header_lines = { 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', }, @@ -1802,7 +1802,7 @@ describe('highlight', function() it('@diff.minus wins over @punctuation.special on combined diff headers', function() local bufnr = create_buffer({ 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', '@@@ -1,2 -1,2 +1,3 @@@', @@ -1819,7 +1819,7 @@ describe('highlight', function() header_start_line = 1, header_lines = { 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', }, @@ -1862,7 +1862,7 @@ describe('highlight', function() it('applies @keyword.diff on index word for combined diffs', function() local bufnr = create_buffer({ 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', '@@@ -1,2 -1,2 +1,3 @@@', @@ -1879,7 +1879,7 @@ describe('highlight', function() header_start_line = 1, header_lines = { 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', }, @@ -1908,7 +1908,7 @@ describe('highlight', function() it('applies @constant.diff on result hash for combined diffs', function() local bufnr = create_buffer({ 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', '@@@ -1,2 -1,2 +1,3 @@@', @@ -1925,7 +1925,7 @@ describe('highlight', function() header_start_line = 1, header_lines = { 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', + 'index abc1234,def5678..a6b9012', '--- a/lua/merge/target.lua', '+++ b/lua/merge/target.lua', }, @@ -1934,14 +1934,21 @@ describe('highlight', function() highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) - local has_constant = false + local has_result_hash = false for _, mark in ipairs(extmarks) do local d = mark[4] - if mark[2] == 1 and d and d.hl_group == '@constant.diff' and (d.priority or 0) >= 199 then - has_constant = true + if + mark[2] == 1 + and mark[3] == 23 + and d + and d.hl_group == '@constant.diff' + and d.end_col == 30 + and (d.priority or 0) >= 199 + then + has_result_hash = true end end - assert.is_true(has_constant, '@constant.diff on result hash') + assert.is_true(has_result_hash, '@constant.diff on result hash at cols 23-30') delete_buffer(bufnr) end) end) From 1b4d93314856e97f3cbbcd85eb9081395900a90f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 6 Mar 2026 14:26:40 -0500 Subject: [PATCH 2/3] refactor(config): nest integration toggles under `integrations` namespace Problem: integration keys (`fugitive`, `neogit`, `gitsigns`, `committia`, `telescope`) live at the top level of `vim.g.diffs`, cluttering the config alongside unrelated options like `highlights` and `conflict`. Solution: move them under `vim.g.diffs.integrations.*`. Old top-level keys are migrated automatically with a `vim.deprecate` warning targeting v0.3.2. `compute_filetypes` and `plugin/diffs.lua` fall back to legacy keys for pre-`init()` callers. --- lua/diffs/init.lua | 126 ++++++++++++++++++++----------- plugin/diffs.lua | 14 +++- spec/init_spec.lua | 54 ++++++++----- spec/neogit_integration_spec.lua | 2 +- 4 files changed, 129 insertions(+), 67 deletions(-) diff --git a/lua/diffs/init.lua b/lua/diffs/init.lua index 882d0c4..6a97c00 100644 --- a/lua/diffs/init.lua +++ b/lua/diffs/init.lua @@ -61,16 +61,24 @@ ---@field priority integer ---@field keymaps diffs.ConflictKeymaps ----@class diffs.Config ----@field debug boolean|string ----@field hide_prefix boolean ----@field extra_filetypes string[] ----@field highlights diffs.Highlights +---@class diffs.IntegrationsConfig ---@field fugitive diffs.FugitiveConfig|false ---@field neogit diffs.NeogitConfig|false ---@field gitsigns diffs.GitsignsConfig|false ---@field committia diffs.CommittiaConfig|false ---@field telescope diffs.TelescopeConfig|false + +---@class diffs.Config +---@field debug boolean|string +---@field hide_prefix boolean +---@field extra_filetypes string[] +---@field highlights diffs.Highlights +---@field integrations diffs.IntegrationsConfig +---@field fugitive diffs.FugitiveConfig|false deprecated: use integrations.fugitive +---@field neogit diffs.NeogitConfig|false deprecated: use integrations.neogit +---@field gitsigns diffs.GitsignsConfig|false deprecated: use integrations.gitsigns +---@field committia diffs.CommittiaConfig|false deprecated: use integrations.committia +---@field telescope diffs.TelescopeConfig|false deprecated: use integrations.telescope ---@field conflict diffs.ConflictConfig ---@class diffs @@ -148,11 +156,13 @@ local default_config = { char_bg = 201, }, }, - fugitive = false, - neogit = false, - gitsigns = false, - committia = false, - telescope = false, + integrations = { + fugitive = false, + neogit = false, + gitsigns = false, + committia = false, + telescope = false, + }, conflict = { enabled = true, disable_diagnostics = true, @@ -209,11 +219,18 @@ end ---@return string[] function M.compute_filetypes(opts) local fts = { 'git', 'gitcommit' } - local fug = opts.fugitive + local intg = opts.integrations or {} + local fug = intg.fugitive + if fug == nil then + fug = opts.fugitive + end if fug == true or type(fug) == 'table' then table.insert(fts, 'fugitive') end - local neo = opts.neogit + local neo = intg.neogit + if neo == nil then + neo = opts.neogit + end if neo == true or type(neo) == 'table' then table.insert(fts, 'NeogitStatus') table.insert(fts, 'NeogitCommitView') @@ -584,6 +601,26 @@ local function compute_highlight_groups() end end +local integration_keys = { 'fugitive', 'neogit', 'gitsigns', 'committia', 'telescope' } + +local function migrate_integrations(opts) + opts.integrations = opts.integrations or {} + for _, key in ipairs(integration_keys) do + if opts[key] ~= nil then + vim.deprecate( + 'vim.g.diffs.' .. key, + 'vim.g.diffs.integrations.' .. key, + '0.3.2', + 'diffs.nvim' + ) + if opts.integrations[key] == nil then + opts.integrations[key] = opts[key] + end + opts[key] = nil + end + end +end + local function init() if initialized then return @@ -592,48 +629,45 @@ local function init() local opts = vim.g.diffs or {} + migrate_integrations(opts) + + local intg = opts.integrations or {} local fugitive_defaults = { horizontal = 'du', vertical = 'dU' } - if opts.fugitive == true then - opts.fugitive = vim.deepcopy(fugitive_defaults) - elseif type(opts.fugitive) == 'table' then - opts.fugitive = vim.tbl_extend('keep', opts.fugitive, fugitive_defaults) + if intg.fugitive == true then + intg.fugitive = vim.deepcopy(fugitive_defaults) + elseif type(intg.fugitive) == 'table' then + intg.fugitive = vim.tbl_extend('keep', intg.fugitive, fugitive_defaults) end - if opts.neogit == true then - opts.neogit = {} + if intg.neogit == true then + intg.neogit = {} end - if opts.gitsigns == true then - opts.gitsigns = {} + if intg.gitsigns == true then + intg.gitsigns = {} end - if opts.committia == true then - opts.committia = {} + if intg.committia == true then + intg.committia = {} end - if opts.telescope == true then - opts.telescope = {} + if intg.telescope == true then + intg.telescope = {} end + opts.integrations = intg + vim.validate('debug', opts.debug, function(v) return v == nil or type(v) == 'boolean' or type(v) == 'string' end, 'boolean or string (file path)') vim.validate('hide_prefix', opts.hide_prefix, 'boolean', true) - vim.validate('fugitive', opts.fugitive, function(v) + vim.validate('integrations', opts.integrations, 'table', true) + local integration_validator = function(v) return v == nil or v == false or type(v) == 'table' - end, 'table or false') - vim.validate('neogit', opts.neogit, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') - vim.validate('gitsigns', opts.gitsigns, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') - vim.validate('committia', opts.committia, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') - vim.validate('telescope', opts.telescope, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') + end + for _, key in ipairs(integration_keys) do + vim.validate('integrations.' .. key, intg[key], integration_validator, 'table or false') + end vim.validate('extra_filetypes', opts.extra_filetypes, 'table', true) vim.validate('highlights', opts.highlights, 'table', true) @@ -704,13 +738,13 @@ local function init() end end - if type(opts.fugitive) == 'table' then + if type(intg.fugitive) == 'table' then ---@type diffs.FugitiveConfig - local fug = opts.fugitive - vim.validate('fugitive.horizontal', fug.horizontal, function(v) + local fug = intg.fugitive + vim.validate('integrations.fugitive.horizontal', fug.horizontal, function(v) return v == nil or v == false or type(v) == 'string' end, 'string or false') - vim.validate('fugitive.vertical', fug.vertical, function(v) + vim.validate('integrations.fugitive.vertical', fug.vertical, function(v) return v == nil or v == false or type(v) == 'string' end, 'string or false') end @@ -934,7 +968,7 @@ function M.attach(bufnr) attached_buffers[bufnr] = true local neogit_augroup = nil - if config.neogit and vim.bo[bufnr].filetype:match('^Neogit') then + if config.integrations.neogit and vim.bo[bufnr].filetype:match('^Neogit') then vim.b[bufnr].neogit_disable_hunk_highlight = true neogit_augroup = vim.api.nvim_create_augroup('diffs_neogit_' .. bufnr, { clear = true }) vim.api.nvim_create_autocmd('User', { @@ -1014,19 +1048,19 @@ end ---@return diffs.FugitiveConfig|false function M.get_fugitive_config() init() - return config.fugitive + return config.integrations.fugitive end ---@return diffs.CommittiaConfig|false function M.get_committia_config() init() - return config.committia + return config.integrations.committia end ---@return diffs.TelescopeConfig|false function M.get_telescope_config() init() - return config.telescope + return config.integrations.telescope end ---@return diffs.ConflictConfig diff --git a/plugin/diffs.lua b/plugin/diffs.lua index 9f59dc1..2b785ef 100644 --- a/plugin/diffs.lua +++ b/plugin/diffs.lua @@ -5,7 +5,17 @@ vim.g.loaded_diffs = 1 require('diffs.commands').setup() -local gs_cfg = (vim.g.diffs or {}).gitsigns +local function get_raw_integration(key) + local user = vim.g.diffs or {} + local intg = user.integrations or {} + local v = intg[key] + if v ~= nil then + return v + end + return user[key] +end + +local gs_cfg = get_raw_integration('gitsigns') if gs_cfg == true or type(gs_cfg) == 'table' then if not require('diffs.gitsigns').setup() then vim.api.nvim_create_autocmd('User', { @@ -18,7 +28,7 @@ if gs_cfg == true or type(gs_cfg) == 'table' then end end -local tel_cfg = (vim.g.diffs or {}).telescope +local tel_cfg = get_raw_integration('telescope') if tel_cfg == true or type(tel_cfg) == 'table' then vim.api.nvim_create_autocmd('User', { pattern = 'TelescopePreviewerLoaded', diff --git a/spec/init_spec.lua b/spec/init_spec.lua index 2e1952e..5b564ae 100644 --- a/spec/init_spec.lua +++ b/spec/init_spec.lua @@ -336,45 +336,45 @@ describe('diffs', function() assert.are.same({ 'git', 'gitcommit' }, fts) end) - it('includes fugitive when fugitive = true', function() - local fts = compute({ fugitive = true }) + it('includes fugitive when integrations.fugitive = true', function() + local fts = compute({ integrations = { fugitive = true } }) assert.is_true(vim.tbl_contains(fts, 'fugitive')) end) - it('includes fugitive when fugitive is a table', function() - local fts = compute({ fugitive = { horizontal = 'dd' } }) + it('includes fugitive when integrations.fugitive is a table', function() + local fts = compute({ integrations = { fugitive = { horizontal = 'dd' } } }) assert.is_true(vim.tbl_contains(fts, 'fugitive')) end) - it('excludes fugitive when fugitive = false', function() - local fts = compute({ fugitive = false }) + it('excludes fugitive when integrations.fugitive = false', function() + local fts = compute({ integrations = { fugitive = false } }) assert.is_false(vim.tbl_contains(fts, 'fugitive')) end) - it('excludes fugitive when fugitive is nil', function() - local fts = compute({}) + it('excludes fugitive when integrations.fugitive is nil', function() + local fts = compute({ integrations = {} }) assert.is_false(vim.tbl_contains(fts, 'fugitive')) end) - it('includes neogit filetypes when neogit = true', function() - local fts = compute({ neogit = true }) + it('includes neogit filetypes when integrations.neogit = true', function() + local fts = compute({ integrations = { neogit = true } }) assert.is_true(vim.tbl_contains(fts, 'NeogitStatus')) assert.is_true(vim.tbl_contains(fts, 'NeogitCommitView')) assert.is_true(vim.tbl_contains(fts, 'NeogitDiffView')) end) - it('includes neogit filetypes when neogit is a table', function() - local fts = compute({ neogit = {} }) + it('includes neogit filetypes when integrations.neogit is a table', function() + local fts = compute({ integrations = { neogit = {} } }) assert.is_true(vim.tbl_contains(fts, 'NeogitStatus')) end) - it('excludes neogit when neogit = false', function() - local fts = compute({ neogit = false }) + it('excludes neogit when integrations.neogit = false', function() + local fts = compute({ integrations = { neogit = false } }) assert.is_false(vim.tbl_contains(fts, 'NeogitStatus')) end) - it('excludes neogit when neogit is nil', function() - local fts = compute({}) + it('excludes neogit when integrations.neogit is nil', function() + local fts = compute({ integrations = {} }) assert.is_false(vim.tbl_contains(fts, 'NeogitStatus')) end) @@ -383,13 +383,31 @@ describe('diffs', function() assert.is_true(vim.tbl_contains(fts, 'diff')) end) - it('combines fugitive, neogit, and extra_filetypes', function() - local fts = compute({ fugitive = true, neogit = true, extra_filetypes = { 'diff' } }) + it('combines integrations and extra_filetypes', function() + local fts = compute({ + integrations = { fugitive = true, neogit = true }, + extra_filetypes = { 'diff' }, + }) assert.is_true(vim.tbl_contains(fts, 'git')) assert.is_true(vim.tbl_contains(fts, 'fugitive')) assert.is_true(vim.tbl_contains(fts, 'NeogitStatus')) assert.is_true(vim.tbl_contains(fts, 'diff')) end) + + it('falls back to legacy top-level fugitive key', function() + local fts = compute({ fugitive = true }) + assert.is_true(vim.tbl_contains(fts, 'fugitive')) + end) + + it('falls back to legacy top-level neogit key', function() + local fts = compute({ neogit = true }) + assert.is_true(vim.tbl_contains(fts, 'NeogitStatus')) + end) + + it('prefers integrations key over legacy top-level key', function() + local fts = compute({ integrations = { fugitive = false }, fugitive = true }) + assert.is_false(vim.tbl_contains(fts, 'fugitive')) + end) end) describe('diff mode', function() diff --git a/spec/neogit_integration_spec.lua b/spec/neogit_integration_spec.lua index ef33049..958df7f 100644 --- a/spec/neogit_integration_spec.lua +++ b/spec/neogit_integration_spec.lua @@ -1,6 +1,6 @@ require('spec.helpers') -vim.g.diffs = { neogit = true } +vim.g.diffs = { integrations = { neogit = true } } local diffs = require('diffs') local parser = require('diffs.parser') From 244cae33bd6ef2a981d391a63e6f1eb450bac1f4 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 6 Mar 2026 14:33:51 -0500 Subject: [PATCH 3/3] fix(config): skip deprecation warning when `integrations` key is present Problem: `migrate_integrations` warned about old top-level keys even when the user had already adopted `integrations`, which would be noisy for configs that happen to have both. Solution: only emit `vim.deprecate` and migrate values when `opts.integrations` was not explicitly provided. When it is, old top-level keys are silently stripped. --- lua/diffs/init.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lua/diffs/init.lua b/lua/diffs/init.lua index 6a97c00..ed47388 100644 --- a/lua/diffs/init.lua +++ b/lua/diffs/init.lua @@ -604,16 +604,17 @@ end local integration_keys = { 'fugitive', 'neogit', 'gitsigns', 'committia', 'telescope' } local function migrate_integrations(opts) + local has_new = opts.integrations ~= nil opts.integrations = opts.integrations or {} for _, key in ipairs(integration_keys) do if opts[key] ~= nil then - vim.deprecate( - 'vim.g.diffs.' .. key, - 'vim.g.diffs.integrations.' .. key, - '0.3.2', - 'diffs.nvim' - ) - if opts.integrations[key] == nil then + if not has_new then + vim.deprecate( + 'vim.g.diffs.' .. key, + 'vim.g.diffs.integrations.' .. key, + '0.3.2', + 'diffs.nvim' + ) opts.integrations[key] = opts[key] end opts[key] = nil