From 34a46b74551278b4e23bb26cb1b1a13772ffc25c Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 12:57:42 -0600 Subject: [PATCH 01/32] chore: announce libvscodediff removal - Warn users of the VS Code algorithm once per session before removal on 30 November 2026. - Explain how to select Neovim diff highlighting. --- doc/diffs.nvim.txt | 3 +++ lua/diffs/runtime/init.lua | 8 ++++++++ spec/plugin_spec.lua | 10 ++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 7832adf0..46e47f71 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -250,6 +250,9 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: inherited from `'diffopt'` (`algorithm` and `linematch`). `'vscode'`: use libvscodediff FFI (falls back to default if not available). + libvscodediff support will be removed on + 30 November 2026. Remove `highlights.intra.algorithm` + to use Neovim's diff. {max_lines} (integer, default: 500) Skip character-level highlighting for hunks with diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index a9f84c85..8b4b01d3 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -64,6 +64,14 @@ local function init() end log.set_enabled(config.debug) + if config.highlights.intra and config.highlights.intra.algorithm == 'vscode' then + log.notify( + 'libvscodediff support will be removed on 30 November 2026. ' + .. "Remove highlights.intra.algorithm to use Neovim's diff.", + vim.log.levels.WARN + ) + end + fast_hl_opts = highlight.hunk_opts(config, { highlights = { treesitter = false, diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 9efd1c88..5956c4d2 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -106,7 +106,7 @@ describe('plugin bootstrap', function() 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false }, conflict = { show_virtual_text = false }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { show_virtual_text = false }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -116,6 +116,9 @@ describe('plugin bootstrap', function() "print('runtime_loaded_at_startup=' .. tostring(package.loaded['diffs.runtime'] ~= nil))", "local runtime = require('diffs.runtime')", 'runtime.attach(0)', + 'runtime.attach(0)', + "print('warning=' .. _G.diffs_notifications[1].message)", + "print('warning_level=' .. _G.diffs_notifications[1].level)", "print('after_attach_notifications=' .. #(_G.diffs_notifications or {}))", 'local runtime_config = runtime._test.get_config()', 'local highlight_opts = runtime.get_highlight_opts()', @@ -155,7 +158,10 @@ describe('plugin bootstrap', function() assert.matches('loaded=1', output, 1, true) assert.matches('notifications=0', output, 1, true) - assert.matches('after_attach_notifications=0', output, 1, true) + assert.matches('after_attach_notifications=1', output, 1, true) + assert.matches('libvscodediff support will be removed on 30 November 2026.', output, 1, true) + assert.matches('Remove highlights.intra.algorithm', output, 1, true) + assert.matches('warning_level=' .. vim.log.levels.WARN, output, 1, true) assert.matches('runtime_view_prefix=false', output, 1, true) assert.matches('runtime_fugitive=true', output, 1, true) assert.matches('runtime_neogit=true', output, 1, true) From efaa0e0d8d64c6dd99bb4413e5225651b105d8d4 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 12:59:13 -0600 Subject: [PATCH 02/32] refactor!: remove libvscodediff support - Remove the shared-library loader, downloader, and FFI adapter. - Remove highlights.intra.algorithm and use Neovim diff highlighting. --- README.md | 3 - doc/diffs.nvim.txt | 22 +--- lua/diffs/config.lua | 8 -- lua/diffs/diff.lua | 125 +------------------- lua/diffs/health.lua | 7 -- lua/diffs/highlight.lua | 2 +- lua/diffs/lib.lua | 227 ------------------------------------- lua/diffs/runtime/init.lua | 8 -- lua/diffs/split.lua | 2 +- spec/config_spec.lua | 16 --- spec/diff_spec.lua | 28 ++--- spec/diffopt_spec.lua | 16 +-- spec/email_quote_spec.lua | 2 +- spec/highlight_spec.lua | 12 +- spec/plugin_spec.lua | 10 +- 15 files changed, 35 insertions(+), 453 deletions(-) delete mode 100644 lua/diffs/lib.lua diff --git a/README.md b/README.md index 12c7f00a..a53dcd51 100644 --- a/README.md +++ b/README.md @@ -110,9 +110,6 @@ own command, e.g. `:command! Review Diff review ++nountracked`. # Acknowledgements - [`vim-fugitive`](https://github.com/tpope/vim-fugitive) -- [@esmuellert](https://github.com/esmuellert) / - [`codediff.nvim`](https://github.com/esmuellert/codediff.nvim) - vscode-diff - algorithm FFI backend for word-level intra-line accuracy - [`diffview.nvim`](https://github.com/sindrets/diffview.nvim) - [`difftastic`](https://github.com/Wilfred/difftastic) - [`mini.diff`](https://github.com/echasnovski/mini.diff) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 46e47f71..357cf7b7 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -244,16 +244,6 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: *diffs.nvim.IntraConfig* Intra config fields: ~ - {algorithm} (string, default: 'default') - Diff algorithm for character-level analysis. - `'default'`: use `vim.diff()` with settings - inherited from `'diffopt'` (`algorithm` and - `linematch`). `'vscode'`: use libvscodediff FFI - (falls back to default if not available). - libvscodediff support will be removed on - 30 November 2026. Remove `highlights.intra.algorithm` - to use Neovim's diff. - {max_lines} (integer, default: 500) Skip character-level highlighting for hunks with more highlighted lines (`+`/`-`) than this @@ -485,7 +475,7 @@ Paired-window behavior: ~ *diffs.nvim-paired-windows* target language. Each changed line is painted with the same palette as generated buffers, so its background is continuous across the gutter and the text, and changed regions within a line carry the same intra-line word-diff - highlighting, honoring `highlights.intra` (including the `vscode` algorithm). + highlighting, honoring `highlights.intra`. Each pane renders its own side's real line numbers in a `statuscolumn` rail (blank on filler rows), so the rail does not depend on or conflict with a @@ -706,10 +696,6 @@ Whitespace handling: ~ *diffs.nvim-whitespace* - Native |:diffsplit| windows: whitespace is handled by Neovim itself. Limitations: ~ - - With `highlights.intra.algorithm = 'vscode'` the intra-line highlighter - ignores only leading and trailing whitespace; internal whitespace inside - a changed line is still highlighted. A warning is issued once. Use the - `default` algorithm for full whitespace handling. - 'diffopt' `algorithm` and `linematch` are honored by the generated diffs too. `linematch` has no git equivalent, so it shapes the file diffs computed with |vim.diff()| but not the git-backed surfaces (|:Diff-review| @@ -737,9 +723,7 @@ Inline granularity: ~ *diffs.nvim-inline* above, so a plain `:set`, an unimpaired-style toggle, or an autocmd all work. There is no dedicated mapping. - `highlights.intra` stays orthogonal: `inline:` chooses the granularity, - `highlights.intra.algorithm` chooses the engine that finds the differences, - and `highlights.intra = false` turns the feature off regardless of + `highlights.intra = false` disables intra-line highlighting regardless of 'diffopt'. Limitations: ~ @@ -1591,13 +1575,11 @@ Run `:checkhealth diffs` to verify your setup. Checks performed: - Neovim version >= 0.9.0 - vim-fugitive is installed (optional) -- libvscode_diff shared library is available (optional) ============================================================================== ACKNOWLEDGEMENTS *diffs.nvim-acknowledgements* - vim-fugitive (https://github.com/tpope/vim-fugitive) -- codediff.nvim (https://github.com/esmuellert/codediff.nvim) - diffview.nvim (https://github.com/sindrets/diffview.nvim) - @phanen (https://github.com/phanen) - diff header highlighting, treesitter injection support, blame_hl.nvim (gitsigns blame popup diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 5b87c7ad..276049c3 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -9,7 +9,6 @@ local integration_metadata = require('diffs.integrations') ---@field max_lines integer ---@class diffs.IntraConfig ----@field algorithm string ---@field max_lines integer ---@class diffs.ViewConfig @@ -80,7 +79,6 @@ local DEFAULTS = { max_lines = 200, }, intra = { - algorithm = 'default', max_lines = 500, }, }, @@ -144,12 +142,6 @@ function M.validate(opts) end end end - - if type(opts.highlights.intra) == 'table' then - vim.validate('highlights.intra.algorithm', opts.highlights.intra.algorithm, function(v) - return v == nil or v == 'default' or v == 'vscode' - end, "'default' or 'vscode'") - end end vim.validate('conflict', opts.conflict, function(v) diff --git a/lua/diffs/diff.lua b/lua/diffs/diff.lua index 855ec1b9..f4ab82e6 100644 --- a/lua/diffs/diff.lua +++ b/lua/diffs/diff.lua @@ -14,11 +14,8 @@ local M = {} local dbg = require('diffs.log').dbg -local notify = require('diffs.log').notify local diffopt = require('diffs.diffopt') -local warned_vscode_whitespace = false - ---@param hunk_lines string[] ---@return diffs.ChangeGroup[] function M.extract_change_groups(hunk_lines) @@ -380,90 +377,9 @@ local function diff_group_native(group, diff_opts) return all_del, all_add end ----@param group diffs.ChangeGroup ----@param handle table ----@param diff_opts? diffs.DiffOpts ----@return diffs.CharSpan[], diffs.CharSpan[] -local function diff_group_vscode(group, handle, diff_opts) - ---@type diffs.CharSpan[] - local all_del = {} - ---@type diffs.CharSpan[] - local all_add = {} - - local ffi = require('ffi') - - local old_texts = {} - for _, l in ipairs(group.del_lines) do - table.insert(old_texts, l.text) - end - local new_texts = {} - for _, l in ipairs(group.add_lines) do - table.insert(new_texts, l.text) - end - - local orig_arr = ffi.new('const char*[?]', #old_texts) - for i, t in ipairs(old_texts) do - orig_arr[i - 1] = t - end - - local mod_arr = ffi.new('const char*[?]', #new_texts) - for i, t in ipairs(new_texts) do - mod_arr[i - 1] = t - end - - local ignore_trim = false - if diff_opts then - ignore_trim = diff_opts.ignore_whitespace == true - or diff_opts.ignore_whitespace_change == true - or diff_opts.ignore_whitespace_change_at_eol == true - end - - local opts = ffi.new('DiffsDiffOptions', { - ignore_trim_whitespace = ignore_trim, - max_computation_time_ms = 1000, - compute_moves = false, - extend_to_subwords = false, - }) - - local result = handle.compute_diff(orig_arr, #old_texts, mod_arr, #new_texts, opts) - if result == nil then - return all_del, all_add - end - - for ci = 0, result.changes.count - 1 do - local mapping = result.changes.mappings[ci] - for ii = 0, mapping.inner_change_count - 1 do - local inner = mapping.inner_changes[ii] - - local orig_line = inner.original.start_line - if group.del_lines[orig_line] then - table.insert(all_del, { - line = group.del_lines[orig_line].idx, - col_start = inner.original.start_col, - col_end = inner.original.end_col, - }) - end - - local mod_line = inner.modified.start_line - if group.add_lines[mod_line] then - table.insert(all_add, { - line = group.add_lines[mod_line].idx, - col_start = inner.modified.start_col, - col_end = inner.modified.end_col, - }) - end - end - end - - handle.free_lines_diff(result) - - return all_del, all_add -end - ---@param hunk_lines string[] ----@param algorithm? string ---@return diffs.IntraChanges? -function M.compute_intra_hunks(hunk_lines, algorithm) +function M.compute_intra_hunks(hunk_lines) local inline = diffopt.inline() if inline == 'none' then dbg('intra skipped: diffopt inline:none') @@ -475,16 +391,6 @@ function M.compute_intra_hunks(hunk_lines, algorithm) return nil end - algorithm = algorithm or 'default' - - local vscode_handle = nil - if algorithm == 'vscode' then - vscode_handle = require('diffs.lib').load() - if not vscode_handle then - dbg('vscode algorithm requested but library not available, falling back to default') - end - end - local diff_opts = diffopt.resolve() if diff_opts.algorithm then dbg('diffopt algorithm: %s', diff_opts.algorithm) @@ -493,36 +399,16 @@ function M.compute_intra_hunks(hunk_lines, algorithm) dbg('diffopt linematch: %d', diff_opts.linematch) end - if vscode_handle and diff_opts.ignore_whitespace and not warned_vscode_whitespace then - warned_vscode_whitespace = true - notify( - 'the vscode intra-line algorithm ignores only leading/trailing whitespace; ' - .. 'internal whitespace differences are still highlighted. Set ' - .. 'highlights.intra.algorithm = "default" for full whitespace handling.', - vim.log.levels.WARN - ) - end - ---@type diffs.CharSpan[] local all_add = {} ---@type diffs.CharSpan[] local all_del = {} - dbg( - 'intra: %d change groups, algorithm=%s, vscode=%s', - #groups, - algorithm, - vscode_handle and 'yes' or 'no' - ) + dbg('intra: %d change groups', #groups) for gi, group in ipairs(groups) do dbg('group %d: %d del lines, %d add lines', gi, #group.del_lines, #group.add_lines) - local ds, as - if vscode_handle then - ds, as = diff_group_vscode(group, vscode_handle, diff_opts) - else - ds, as = diff_group_native(group, diff_opts) - end + local ds, as = diff_group_native(group, diff_opts) dbg('group %d result: %d del spans, %d add spans', gi, #ds, #as) for _, s in ipairs(ds) do dbg(' del span: line=%d col=%d..%d', s.line, s.col_start, s.col_end) @@ -603,9 +489,4 @@ function M.whitespace_only_lines(hunk_lines) return result end ----@return boolean -function M.has_vscode() - return require('diffs.lib').has_lib() -end - return M diff --git a/lua/diffs/health.lua b/lua/diffs/health.lua index ba2a6ef6..71d02b68 100644 --- a/lua/diffs/health.lua +++ b/lua/diffs/health.lua @@ -16,13 +16,6 @@ function M.check() vim.health.warn('vim-fugitive not detected (required for unified diff highlighting)') end - local lib = require('diffs.lib') - if lib.has_lib() then - vim.health.ok('libvscode_diff found at ' .. lib.lib_path()) - else - vim.health.info('libvscode_diff not found (optional, using native vim.diff fallback)') - end - local difftastic = require('diffs.difftastic') if not difftastic.resolve().enabled then vim.health.info('difftastic not enabled (optional, set integrations.difftastic)') diff --git a/lua/diffs/highlight.lua b/lua/diffs/highlight.lua index a3d83475..502b0214 100644 --- a/lua/diffs/highlight.lua +++ b/lua/diffs/highlight.lua @@ -625,7 +625,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) and (hunk._hl_line_count or #hunk.lines) <= intra_cfg.max_lines then dbg('computing intra for hunk %s:%d (%d lines)', hunk.filename, hunk.start_line, #hunk.lines) - intra = diff.compute_intra_hunks(hunk.lines, intra_cfg.algorithm) + intra = diff.compute_intra_hunks(hunk.lines) if intra then dbg('intra result: %d add spans, %d del spans', #intra.add_spans, #intra.del_spans) else diff --git a/lua/diffs/lib.lua b/lua/diffs/lib.lua deleted file mode 100644 index 7660da88..00000000 --- a/lua/diffs/lib.lua +++ /dev/null @@ -1,227 +0,0 @@ -local M = {} - -local log = require('diffs.log') - -local dbg = log.dbg -local notify = log.notify - ----@type table? -local cached_handle = nil - ----@type boolean -local download_in_progress = false - ----@type fun(handle: table?)[] -local pending_callbacks = {} - ----@return string -local function get_os() - local os_name = jit.os:lower() - if os_name == 'osx' then - return 'macos' - end - return os_name -end - ----@return string -local function get_arch() - return jit.arch:lower() -end - ----@return string -local function get_ext() - local os_name = jit.os:lower() - if os_name == 'windows' then - return 'dll' - elseif os_name == 'osx' then - return 'dylib' - end - return 'so' -end - ----@return string -local function lib_dir() - return vim.fn.stdpath('data') .. '/diffs/lib' -end - ----@return string -local function lib_path() - return lib_dir() .. '/libvscode_diff.' .. get_ext() -end - ----@return string -local function version_path() - return lib_dir() .. '/version' -end - -local EXPECTED_VERSION = '2.49.2' - ----@return boolean -function M.has_lib() - if cached_handle then - return true - end - return vim.fn.filereadable(lib_path()) == 1 -end - ----@return string -function M.lib_path() - return lib_path() -end - ----@return table? -function M.load() - if cached_handle then - return cached_handle - end - - local path = lib_path() - if vim.fn.filereadable(path) ~= 1 then - return nil - end - - local ffi = require('ffi') - - ffi.cdef([[ - typedef struct { - int start_line; - int end_line; - } DiffsLineRange; - - typedef struct { - int start_line; - int start_col; - int end_line; - int end_col; - } DiffsCharRange; - - typedef struct { - DiffsCharRange original; - DiffsCharRange modified; - } DiffsRangeMapping; - - typedef struct { - DiffsLineRange original; - DiffsLineRange modified; - DiffsRangeMapping* inner_changes; - int inner_change_count; - } DiffsDetailedMapping; - - typedef struct { - DiffsDetailedMapping* mappings; - int count; - int capacity; - } DiffsDetailedMappingArray; - - typedef struct { - DiffsLineRange original; - DiffsLineRange modified; - } DiffsMovedText; - - typedef struct { - DiffsMovedText* moves; - int count; - int capacity; - } DiffsMovedTextArray; - - typedef struct { - DiffsDetailedMappingArray changes; - DiffsMovedTextArray moves; - bool hit_timeout; - } DiffsLinesDiff; - - typedef struct { - bool ignore_trim_whitespace; - int max_computation_time_ms; - bool compute_moves; - bool extend_to_subwords; - } DiffsDiffOptions; - - DiffsLinesDiff* compute_diff( - const char** original_lines, - int original_count, - const char** modified_lines, - int modified_count, - const DiffsDiffOptions* options - ); - - void free_lines_diff(DiffsLinesDiff* diff); - - const char* get_version(void); - ]]) - - local ok, handle = pcall(ffi.load, path) - if not ok then - dbg('failed to load libvscode_diff: %s', handle) - return nil - end - - cached_handle = handle - return handle -end - ----@param callback fun(handle: table?) -function M.ensure(callback) - if cached_handle then - callback(cached_handle) - return - end - - if M.has_lib() then - callback(M.load()) - return - end - - table.insert(pending_callbacks, callback) - - if download_in_progress then - dbg('download already in progress, queued callback') - return - end - - download_in_progress = true - - local dir = lib_dir() - vim.fn.mkdir(dir, 'p') - - local os_name = get_os() - local arch = get_arch() - local ext = get_ext() - local filename = ('libvscode_diff_%s_%s_%s.%s'):format(os_name, arch, EXPECTED_VERSION, ext) - local url = ('https://github.com/esmuellert/codediff.nvim/releases/download/v%s/%s'):format( - EXPECTED_VERSION, - filename - ) - - local dest = lib_path() - notify('downloading libvscode_diff...', vim.log.levels.INFO) - - local cmd = { 'curl', '-fSL', '-o', dest, url } - - vim.system(cmd, {}, function(result) - download_in_progress = false - vim.schedule(function() - local handle = nil - if result.code ~= 0 then - notify('failed to download libvscode_diff', vim.log.levels.WARN) - dbg('curl failed: %s', result.stderr or '') - else - local f = io.open(version_path(), 'w') - if f then - f:write(EXPECTED_VERSION) - f:close() - end - notify('libvscode_diff downloaded', vim.log.levels.INFO) - handle = M.load() - end - - local cbs = pending_callbacks - pending_callbacks = {} - for _, cb in ipairs(cbs) do - cb(handle) - end - end) - end) -end - -return M diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index 8b4b01d3..a9f84c85 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -64,14 +64,6 @@ local function init() end log.set_enabled(config.debug) - if config.highlights.intra and config.highlights.intra.algorithm == 'vscode' then - log.notify( - 'libvscodediff support will be removed on 30 November 2026. ' - .. "Remove highlights.intra.algorithm to use Neovim's diff.", - vim.log.levels.WARN - ) - end - fast_hl_opts = highlight.hunk_opts(config, { highlights = { treesitter = false, diff --git a/lua/diffs/split.lua b/lua/diffs/split.lua index 4248999a..c5b3146c 100644 --- a/lua/diffs/split.lua +++ b/lua/diffs/split.lua @@ -319,7 +319,7 @@ local function set_split_intra(bufnr, info, hunks) end if #texts > 0 and #texts <= intra_cfg.max_lines then - local intra = diff.compute_intra_hunks(texts, intra_cfg.algorithm) + local intra = diff.compute_intra_hunks(texts) local spans = intra and intra[cfg.spans] or {} for _, span in ipairs(spans) do local ref = refs[span.line] diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 26b68ed6..b78760ac 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -30,7 +30,6 @@ describe('diffs.config', function() treesitter = { max_lines = 1000 }, vim = false, intra = { - algorithm = 'vscode', max_lines = 250, }, }, @@ -188,21 +187,6 @@ describe('diffs.config', function() end end) - it('validates intra diff algorithm names', function() - local ok, err = pcall(config.new, { - highlights = { - intra = { - algorithm = 'other', - }, - }, - }) - - assert.is_false(ok) - assert.matches('highlights.intra.algorithm', err, 1, true) - assert.matches('default', err, 1, true) - assert.matches('vscode', err, 1, true) - end) - it('validates conflict keymap values', function() local ok, err = pcall(config.new, { conflict = { diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index f470409c..d927bdeb 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -73,17 +73,17 @@ describe('diff', function() describe('compute_intra_hunks', function() it('returns nil for all-addition hunks', function() - local result = diff.compute_intra_hunks({ '+line1', '+line2' }, 'default') + local result = diff.compute_intra_hunks({ '+line1', '+line2' }) assert.is_nil(result) end) it('returns nil for all-deletion hunks', function() - local result = diff.compute_intra_hunks({ '-line1', '-line2' }, 'default') + local result = diff.compute_intra_hunks({ '-line1', '-line2' }) assert.is_nil(result) end) it('returns nil for context-only hunks', function() - local result = diff.compute_intra_hunks({ ' line1', ' line2' }, 'default') + local result = diff.compute_intra_hunks({ ' line1', ' line2' }) assert.is_nil(result) end) @@ -91,7 +91,7 @@ describe('diff', function() local result = diff.compute_intra_hunks({ '-local x = 1', '+local x = 2', - }, 'default') + }) assert.is_not_nil(result) assert.is_true(#result.del_spans > 0) assert.is_true(#result.add_spans > 0) @@ -101,7 +101,7 @@ describe('diff', function() local result = diff.compute_intra_hunks({ '-local x = 1', '+local x = 2', - }, 'default') + }) assert.is_not_nil(result) assert.are.equal(1, #result.del_spans) @@ -121,7 +121,7 @@ describe('diff', function() ' local b = 3', '-local c = 4', '+local c = 5', - }, 'default') + }) assert.is_not_nil(result) assert.is_true(#result.del_spans >= 2) assert.is_true(#result.add_spans >= 2) @@ -132,7 +132,7 @@ describe('diff', function() '-line one', '-line two', '+line combined', - }, 'default') + }) assert.is_not_nil(result) end) @@ -140,7 +140,7 @@ describe('diff', function() local result = diff.compute_intra_hunks({ '-local x = "héllo"', '+local x = "wörld"', - }, 'default') + }) assert.is_not_nil(result) assert.is_true(#result.del_spans > 0) assert.is_true(#result.add_spans > 0) @@ -150,7 +150,7 @@ describe('diff', function() local result = diff.compute_intra_hunks({ '-local x = 1', '+local x = 1', - }, 'default') + }) assert.is_nil(result) end) end) @@ -211,7 +211,7 @@ describe('diff', function() ---@param lines string[] ---@return string[] local function add_texts(lines) - local result = diff.compute_intra_hunks(lines, 'default') + local result = diff.compute_intra_hunks(lines) if not result then return {} end @@ -227,7 +227,7 @@ describe('diff', function() it('skips intra-line work entirely on inline:none', function() vim.o.diffopt = 'internal,filler,inline:none' - assert.is_nil(diff.compute_intra_hunks(changed, 'default')) + assert.is_nil(diff.compute_intra_hunks(changed)) end) it('spans first to last difference on inline:simple', function() @@ -256,10 +256,4 @@ describe('diff', function() assert.are.same({ 'z = 2' }, add_texts(changed)) end) end) - - describe('has_vscode', function() - it('returns false in test environment', function() - assert.is_false(diff.has_vscode()) - end) - end) end) diff --git a/spec/diffopt_spec.lua b/spec/diffopt_spec.lua index 4d3ed35a..8ffc9440 100644 --- a/spec/diffopt_spec.lua +++ b/spec/diffopt_spec.lua @@ -239,7 +239,7 @@ describe('line-level whitespace handling', function() end) end) -describe('intra-line whitespace handling (default algorithm)', function() +describe('intra-line whitespace handling', function() local saved before_each(function() @@ -252,39 +252,39 @@ describe('intra-line whitespace handling (default algorithm)', function() it('highlights a whitespace-only intra change by default', function() vim.o.diffopt = 'internal' - local result = diff.compute_intra_hunks({ '-foo bar', '+foo bar' }, 'default') + local result = diff.compute_intra_hunks({ '-foo bar', '+foo bar' }) assert.is_not_nil(result) assert.is_true(#result.add_spans > 0 or #result.del_spans > 0) end) it('drops whitespace-only intra spans when iwhiteall is set', function() vim.o.diffopt = 'internal,iwhiteall' - local result = diff.compute_intra_hunks({ '-foo bar', '+foo bar' }, 'default') + local result = diff.compute_intra_hunks({ '-foo bar', '+foo bar' }) assert.is_nil(result) end) it('keeps content spans while dropping whitespace spans under iwhiteall', function() vim.o.diffopt = 'internal,iwhiteall' - local result = diff.compute_intra_hunks({ '-foo', '+bar ' }, 'default') + local result = diff.compute_intra_hunks({ '-foo', '+bar ' }) assert.is_not_nil(result) assert.is_true(#result.add_spans > 0) end) it('keeps whitespace intra spans under iwhite (only iwhiteall drops them)', function() vim.o.diffopt = 'internal,iwhite' - local result = diff.compute_intra_hunks({ '-ab', '+a b' }, 'default') + local result = diff.compute_intra_hunks({ '-ab', '+a b' }) assert.is_not_nil(result) end) it('drops only end-of-line whitespace spans under iwhiteeol', function() vim.o.diffopt = 'internal,iwhiteeol' - assert.is_nil(diff.compute_intra_hunks({ '-foo', '+foo ' }, 'default')) - assert.is_not_nil(diff.compute_intra_hunks({ '-foo bar', '+foo bar' }, 'default')) + assert.is_nil(diff.compute_intra_hunks({ '-foo', '+foo ' })) + assert.is_not_nil(diff.compute_intra_hunks({ '-foo bar', '+foo bar' })) end) it('does not drop a multibyte content change under iwhiteall', function() vim.o.diffopt = 'internal,iwhiteall' - local result = diff.compute_intra_hunks({ '-café', '+cafe' }, 'default') + local result = diff.compute_intra_hunks({ '-café', '+cafe' }) assert.is_not_nil(result) end) end) diff --git a/spec/email_quote_spec.lua b/spec/email_quote_spec.lua index b767b8e4..3db3a8cf 100644 --- a/spec/email_quote_spec.lua +++ b/spec/email_quote_spec.lua @@ -408,7 +408,7 @@ describe('email-quoted diffs', function() ns, hunk, default_opts({ - highlights = { intra = { algorithm = 'default', max_lines = 500 } }, + highlights = { intra = { max_lines = 500 } }, }) ) diff --git a/spec/highlight_spec.lua b/spec/highlight_spec.lua index 44845614..944a7b09 100644 --- a/spec/highlight_spec.lua +++ b/spec/highlight_spec.lua @@ -1495,7 +1495,7 @@ describe('highlight', function() ns, hunk, default_opts({ - highlights = { intra = { algorithm = 'default', max_lines = 500 } }, + highlights = { intra = { max_lines = 500 } }, }) ) @@ -1537,7 +1537,7 @@ describe('highlight', function() ns, hunk, default_opts({ - highlights = { intra = { algorithm = 'default', max_lines = 500 } }, + highlights = { intra = { max_lines = 500 } }, }) ) @@ -1574,7 +1574,7 @@ describe('highlight', function() default_opts({ highlights = { background = true, - intra = { algorithm = 'default', max_lines = 500 }, + intra = { max_lines = 500 }, }, }) ) @@ -1828,7 +1828,7 @@ describe('highlight', function() ns, hunk, default_opts({ - highlights = { intra = { algorithm = 'default', max_lines = 500 } }, + highlights = { intra = { max_lines = 500 } }, }) ) @@ -1987,7 +1987,7 @@ describe('highlight', function() highlights = { treesitter = false, background = true, - intra = { algorithm = 'default', max_lines = 500 }, + intra = { max_lines = 500 }, }, }) @@ -1995,7 +1995,7 @@ describe('highlight', function() highlights = { treesitter = { max_lines = 500 }, background = true, - intra = { algorithm = 'default', max_lines = 500 }, + intra = { max_lines = 500 }, }, }) syntax.syntax_only = true diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 5956c4d2..9efd1c88 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -106,7 +106,7 @@ describe('plugin bootstrap', function() 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { show_virtual_text = false }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false }, conflict = { show_virtual_text = false }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -116,9 +116,6 @@ describe('plugin bootstrap', function() "print('runtime_loaded_at_startup=' .. tostring(package.loaded['diffs.runtime'] ~= nil))", "local runtime = require('diffs.runtime')", 'runtime.attach(0)', - 'runtime.attach(0)', - "print('warning=' .. _G.diffs_notifications[1].message)", - "print('warning_level=' .. _G.diffs_notifications[1].level)", "print('after_attach_notifications=' .. #(_G.diffs_notifications or {}))", 'local runtime_config = runtime._test.get_config()', 'local highlight_opts = runtime.get_highlight_opts()', @@ -158,10 +155,7 @@ describe('plugin bootstrap', function() assert.matches('loaded=1', output, 1, true) assert.matches('notifications=0', output, 1, true) - assert.matches('after_attach_notifications=1', output, 1, true) - assert.matches('libvscodediff support will be removed on 30 November 2026.', output, 1, true) - assert.matches('Remove highlights.intra.algorithm', output, 1, true) - assert.matches('warning_level=' .. vim.log.levels.WARN, output, 1, true) + assert.matches('after_attach_notifications=0', output, 1, true) assert.matches('runtime_view_prefix=false', output, 1, true) assert.matches('runtime_fugitive=true', output, 1, true) assert.matches('runtime_neogit=true', output, 1, true) From 5171f20a3eea9b2bc3fdb37bbcec15e2df005e1f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:04:15 -0600 Subject: [PATCH 03/32] refactor!: remove extra_filetypes configuration --- doc/diffs.nvim.txt | 6 +----- lua/diffs/config.lua | 3 --- lua/diffs/integrations.lua | 5 ----- plugin/diffs.lua | 10 ---------- spec/config_spec.lua | 17 ----------------- 5 files changed, 1 insertion(+), 40 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 9a5a27f7..86984bc1 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -167,9 +167,6 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Use `{ args = {...} }` to forward flags. See |diffs.nvim-difftastic|. - {extra_filetypes} (table, default: {}) - Deprecated. See |diffs.nvim-extra-filetypes|. - {highlights} (table, default: see below) Controls which highlight features are enabled. See |diffs.nvim.Highlights| for fields. @@ -1252,8 +1249,7 @@ API *diffs.nvim-api* attach({bufnr}) *diffs.nvim.attach()* Attach highlighting to a buffer. Called automatically when the buffer's - filetype matches `git`, `gitcommit`, an enabled integration filetype, - or any entry in the deprecated `extra_filetypes` option. + filetype matches `git`, `gitcommit`, or an enabled integration filetype. Direct calls work with any filetype. For automatic attachment to other filetypes, see |diffs.nvim-extra-filetypes|. diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 5b87c7ad..eb9f2fb6 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -57,7 +57,6 @@ local integration_metadata = require('diffs.integrations') ---@class diffs.Config ---@field debug boolean|string ---@field view diffs.ViewConfig ----@field extra_filetypes string[] ---@field highlights diffs.Highlights ---@field integrations diffs.IntegrationsConfig ---@field conflict diffs.ConflictConfig|false @@ -69,7 +68,6 @@ local DEFAULTS = { default_layout = 'unified', prefix = true, }, - extra_filetypes = {}, highlights = { background = true, warn_max_lines = true, @@ -122,7 +120,6 @@ function M.validate(opts) vim.validate('integrations.difftastic.args[' .. i .. ']', arg, 'string') end end - vim.validate('extra_filetypes', opts.extra_filetypes, 'table', true) vim.validate('highlights', opts.highlights, 'table', true) if opts.highlights then diff --git a/lua/diffs/integrations.lua b/lua/diffs/integrations.lua index e9ef49ee..80123993 100644 --- a/lua/diffs/integrations.lua +++ b/lua/diffs/integrations.lua @@ -55,11 +55,6 @@ function M.filetypes(opts) end end 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 diff --git a/plugin/diffs.lua b/plugin/diffs.lua index af9803f0..69dd2a5d 100644 --- a/plugin/diffs.lua +++ b/plugin/diffs.lua @@ -8,16 +8,6 @@ local user_config = config.new(vim.deepcopy(vim.g.diffs or {})) vim.g.loaded_diffs = 1 -if #user_config.extra_filetypes > 0 then - vim.schedule(function() - require('diffs.log').notify( - 'extra_filetypes is deprecated. Support ends on November 30, 2026. ' - .. 'Use the FileType autocmd recipe: :help diffs.nvim-extra-filetypes', - vim.log.levels.WARN - ) - end) -end - local function get_runtime() local runtime = require('diffs.runtime') runtime.configure(user_config) diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 26b68ed6..c5fea019 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -22,7 +22,6 @@ describe('diffs.config', function() view = { prefix = true, }, - extra_filetypes = { 'diff' }, highlights = { background = true, blend_alpha = 0.4, @@ -269,21 +268,5 @@ describe('diffs.config', function() local fts = compute({ integrations = {} }) assert.is_false(vim.tbl_contains(fts, 'NeogitStatus')) end) - - it('includes extra_filetypes', function() - local fts = compute({ extra_filetypes = { 'diff' } }) - assert.is_true(vim.tbl_contains(fts, 'diff')) - end) - - 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) end) end) From ffab0b66ae72af5dd52b2454725a018e29e5d768 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:09:00 -0600 Subject: [PATCH 04/32] refactor: deprecate highlight overrides --- doc/diffs.nvim.txt | 25 ++++--------------------- lua/diffs/runtime/init.lua | 8 ++++++++ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 9a5a27f7..baf04845 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -96,7 +96,6 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: treesitter = true, vim = true, intra = true, - overrides = {}, }, conflict = { disable_diagnostics = true, @@ -209,11 +208,7 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: See |diffs.nvim.IntraConfig| for fields. {overrides} (table, default: {}) - Map of highlight group names to highlight - definitions (see `nvim_set_hl()`). Applied - after all computed groups without `default`, - so overrides always win over both computed - defaults and colorscheme definitions. + Deprecated. See |diffs.nvim-highlights|. *diffs.nvim.TreesitterConfig* Treesitter config fields: ~ @@ -1431,11 +1426,6 @@ mis-identified, which can degrade its syntax highlighting or hunk navigation. ============================================================================== HIGHLIGHT GROUPS *diffs.nvim-highlights* -diffs.nvim defines custom highlight groups with `default = true`. Colorschemes -can override them before diffs.nvim loads, or users can override them with -`highlights.overrides`. - - Diff and generated rail highlights: ~ *DiffsClear* DiffsClear Clears underlying diff foreground colors while preserving @@ -1528,20 +1518,13 @@ Diff mode window highlights: ~ These are used for 'winhighlight' remapping in *DiffsDiffText* DiffsDiffText Background-only replacement for `DiffText`. -To customize these in your colorscheme: >lua +Customize with |nvim_set_hl()|: >lua vim.api.nvim_set_hl(0, 'DiffsAdd', { bg = '#2e4a3a' }) vim.api.nvim_set_hl(0, 'DiffsDiffDelete', { link = 'DiffDelete' }) -< Or via `highlights.overrides` in config: >lua - vim.g.diffs = { - highlights = { - overrides = { - DiffsAdd = { bg = '#2e4a3a' }, - DiffsDiffDelete = { link = 'DiffDelete' }, - }, - }, - } < +diffs.nvim recomputes these groups on |ColorScheme|. + ============================================================================== HEALTH CHECK *diffs.nvim-health* diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index 8b4b01d3..f108bd12 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -64,6 +64,14 @@ local function init() end log.set_enabled(config.debug) + if config.highlights.overrides and next(config.highlights.overrides) then + log.notify( + 'highlights.overrides is deprecated. Support ends on November 30, 2026. ' + .. 'Use vim.api.nvim_set_hl(). See :help diffs.nvim-highlights.', + vim.log.levels.WARN + ) + end + if config.highlights.intra and config.highlights.intra.algorithm == 'vscode' then log.notify( 'libvscodediff support will be removed on 30 November 2026. ' From 994104ca6a8c28bfb9202330f5f6134cf90df084 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:09:40 -0600 Subject: [PATCH 05/32] refactor!: remove highlight overrides --- doc/diffs.nvim.txt | 3 --- lua/diffs/config.lua | 2 -- lua/diffs/runtime/highlight_groups.lua | 6 ------ lua/diffs/runtime/init.lua | 8 -------- 4 files changed, 19 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index baf04845..06832993 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -207,9 +207,6 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Character-level (intra-line) diff highlighting. See |diffs.nvim.IntraConfig| for fields. - {overrides} (table, default: {}) - Deprecated. See |diffs.nvim-highlights|. - *diffs.nvim.TreesitterConfig* Treesitter config fields: ~ {max_lines} (integer, default: 500) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 5b87c7ad..6cc150fe 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -19,7 +19,6 @@ local integration_metadata = require('diffs.integrations') ---@class diffs.Highlights ---@field background boolean ---@field blend_alpha? number ----@field overrides? table ---@field warn_max_lines boolean ---@field treesitter diffs.TreesitterConfig|false ---@field vim diffs.VimConfig|false @@ -128,7 +127,6 @@ function M.validate(opts) if opts.highlights then vim.validate('highlights.background', opts.highlights.background, 'boolean', true) vim.validate('highlights.blend_alpha', opts.highlights.blend_alpha, 'number', true) - vim.validate('highlights.overrides', opts.highlights.overrides, 'table', true) vim.validate('highlights.warn_max_lines', opts.highlights.warn_max_lines, 'boolean', true) for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do diff --git a/lua/diffs/runtime/highlight_groups.lua b/lua/diffs/runtime/highlight_groups.lua index c8a3fb15..8d3407da 100644 --- a/lua/diffs/runtime/highlight_groups.lua +++ b/lua/diffs/runtime/highlight_groups.lua @@ -163,12 +163,6 @@ function M.apply(config, is_default) vim.api.nvim_set_hl(0, 'NeogitDiffDeleteInline', {}) end - if config.highlights.overrides then - for group, hl in pairs(config.highlights.overrides) do - vim.api.nvim_set_hl(0, group, hl) - end - end - return { transparent = transparent } end diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index f108bd12..8b4b01d3 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -64,14 +64,6 @@ local function init() end log.set_enabled(config.debug) - if config.highlights.overrides and next(config.highlights.overrides) then - log.notify( - 'highlights.overrides is deprecated. Support ends on November 30, 2026. ' - .. 'Use vim.api.nvim_set_hl(). See :help diffs.nvim-highlights.', - vim.log.levels.WARN - ) - end - if config.highlights.intra and config.highlights.intra.algorithm == 'vscode' then log.notify( 'libvscodediff support will be removed on 30 November 2026. ' From f6d2c6fdabda59152dfd025355bf14fd93af2ce0 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:16:46 -0600 Subject: [PATCH 06/32] chore: deprecate automatic conflict diagnostic suppression - Warn before removal on 30 November 2026 and document a diagnostics recipe. - Emit conflict lifecycle events with the working buffer number. --- doc/diffs.nvim.txt | 51 +++++++++++++++++++++++++++++++++--------- lua/diffs/conflict.lua | 42 +++++++++++++++++++++++++++------- spec/conflict_spec.lua | 17 ++++++++++++++ 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index b0842ac8..fa6dba1d 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -1115,7 +1115,8 @@ Configuration: ~ errors that clutter the diagnostic list. Diagnostics are re-enabled when all conflicts are resolved. Set `false` to leave - diagnostics alone. + diagnostics alone. This option will be removed on + 30 November 2026. See |diffs.nvim-conflict-diagnostics|. {keymaps} (table|false, default: false) Opt-in buffer-local keymaps for conflict @@ -1145,18 +1146,46 @@ Configuration: ~ Jump to previous conflict marker. Wraps around. +Diagnostic suppression: ~ *diffs.nvim-conflict-diagnostics* + +This recipe restores diagnostics only when it disabled them: >lua + vim.g.diffs = { conflict = { disable_diagnostics = false } } + + vim.api.nvim_create_autocmd('User', { + pattern = { + 'DiffsConflictDetected', + 'DiffsConflictResolved', + 'DiffsConflictDetached', + }, + callback = function(event) + local bufnr = event.data.bufnr + if event.match == 'DiffsConflictDetected' then + if vim.diagnostic.is_enabled({ bufnr = bufnr }) then + vim.b[bufnr].diffs_restore_diagnostics = true + vim.diagnostic.enable(false, { bufnr = bufnr }) + end + elseif vim.b[bufnr].diffs_restore_diagnostics then + vim.b[bufnr].diffs_restore_diagnostics = nil + vim.diagnostic.enable(true, { bufnr = bufnr }) + end + end, + }) +< + User events: ~ + +All conflict events include the working buffer number in `event.data.bufnr`. + + *DiffsConflictDetected* + DiffsConflictDetected Fired when conflicts first appear in an attached + buffer, including when undo restores markers. + *DiffsConflictResolved* - DiffsConflictResolved Fired when the last conflict in a buffer is - resolved. Useful for triggering custom actions - (e.g., auto-staging the file). >lua - vim.api.nvim_create_autocmd('User', { - pattern = 'DiffsConflictResolved', - callback = function() - print('all conflicts resolved!') - end, - }) -< + DiffsConflictResolved Fired when the last conflict in a buffer is resolved. + + *DiffsConflictDetached* + DiffsConflictDetached Fired when conflict highlighting is explicitly + detached from a buffer. ============================================================================== MERGE DIFF RESOLUTION *diffs.nvim-merge* diff --git a/lua/diffs/conflict.lua b/lua/diffs/conflict.lua index 369ca7b9..d6c96159 100644 --- a/lua/diffs/conflict.lua +++ b/lua/diffs/conflict.lua @@ -28,6 +28,27 @@ local diagnostics_suppressed = {} ---@type table> local buffer_keymaps = {} +---@param event string +---@param bufnr integer +local function emit(event, bufnr) + vim.api.nvim_exec_autocmds('User', { pattern = event, data = { bufnr = bufnr } }) +end + +---@param bufnr integer +---@param config diffs.ConflictConfig +local function suppress_diagnostics(bufnr, config) + if not config.disable_diagnostics or diagnostics_suppressed[bufnr] then + return + end + vim.notify_once( + '[diffs]: conflict.disable_diagnostics will be removed on 30 November 2026. ' + .. 'See :help diffs.nvim-conflict-diagnostics.', + vim.log.levels.WARN + ) + pcall(vim.diagnostic.enable, false, { bufnr = bufnr }) + diagnostics_suppressed[bufnr] = true +end + ---@param line string ---@param char string ---@return boolean @@ -250,6 +271,7 @@ end ---@param bufnr integer ---@param config diffs.ConflictConfig function M.refresh(bufnr, config) + local had_conflicts = #vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { limit = 1 }) > 0 local regions = parse_buffer(bufnr) if #regions == 0 then vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) @@ -259,16 +281,18 @@ function M.refresh(bufnr, config) pcall(vim.diagnostic.enable, true, { bufnr = bufnr }) diagnostics_suppressed[bufnr] = nil end - vim.api.nvim_exec_autocmds('User', { pattern = 'DiffsConflictResolved' }) + if had_conflicts then + emit('DiffsConflictResolved', bufnr) + end return end apply_highlights(bufnr, regions) if not buffer_keymaps[bufnr] then setup_keymaps(bufnr, config) end - if config.disable_diagnostics and not diagnostics_suppressed[bufnr] then - pcall(vim.diagnostic.enable, false, { bufnr = bufnr }) - diagnostics_suppressed[bufnr] = true + suppress_diagnostics(bufnr, config) + if not had_conflicts then + emit('DiffsConflictDetected', bufnr) end end @@ -394,6 +418,7 @@ end ---@param bufnr integer function M.detach(bufnr) + local attached = attached_buffers[bufnr] vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil @@ -403,6 +428,9 @@ function M.detach(bufnr) pcall(vim.diagnostic.enable, true, { bufnr = bufnr }) diagnostics_suppressed[bufnr] = nil end + if attached then + emit('DiffsConflictDetached', bufnr) + end end ---@param bufnr integer @@ -428,10 +456,7 @@ function M.attach(bufnr, config) apply_highlights(bufnr, regions) setup_keymaps(bufnr, config) - if config.disable_diagnostics then - pcall(vim.diagnostic.enable, false, { bufnr = bufnr }) - diagnostics_suppressed[bufnr] = true - end + suppress_diagnostics(bufnr, config) vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { buffer = bufnr, @@ -451,6 +476,7 @@ function M.attach(bufnr, config) diagnostics_suppressed[bufnr] = nil end, }) + emit('DiffsConflictDetected', bufnr) end ---@return integer diff --git a/spec/conflict_spec.lua b/spec/conflict_spec.lua index 91551d44..e7cc64c6 100644 --- a/spec/conflict_spec.lua +++ b/spec/conflict_spec.lua @@ -824,6 +824,14 @@ describe('conflict', function() 'local x = 2', '>>>>>>> feature', }) + local events = {} + local autocmd = vim.api.nvim_create_autocmd('User', { + pattern = { 'DiffsConflictDetected', 'DiffsConflictResolved', 'DiffsConflictDetached' }, + callback = function(event) + assert.are.equal(bufnr, event.data.bufnr) + events[#events + 1] = event.match + end, + }) vim.api.nvim_set_current_buf(bufnr) local cfg = default_config() conflict.attach(bufnr, cfg) @@ -833,6 +841,7 @@ describe('conflict', function() vim.api.nvim_win_set_cursor(0, { 2, 0 }) conflict.resolve_ours(bufnr, cfg) assert.are.equal(0, #get_extmarks(bufnr)) + conflict.refresh(bufnr, cfg) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '<<<<<<< HEAD', @@ -845,7 +854,15 @@ describe('conflict', function() assert.is_true(#get_extmarks(bufnr) > 0) + conflict.refresh(bufnr, cfg) conflict.detach(bufnr) + assert.are.same({ + 'DiffsConflictDetected', + 'DiffsConflictResolved', + 'DiffsConflictDetected', + 'DiffsConflictDetached', + }, events) + vim.api.nvim_del_autocmd(autocmd) helpers.delete_buffer(bufnr) end) From 0e8df7b48d963c81f825ba931d0649313e21b3ab Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:21:46 -0600 Subject: [PATCH 07/32] docs: clarify conflict diagnostics recipe --- doc/diffs.nvim.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index fa6dba1d..073cde7c 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -1148,7 +1148,7 @@ Configuration: ~ Diagnostic suppression: ~ *diffs.nvim-conflict-diagnostics* -This recipe restores diagnostics only when it disabled them: >lua +Hide diagnostics while a buffer contains merge conflicts: >lua vim.g.diffs = { conflict = { disable_diagnostics = false } } vim.api.nvim_create_autocmd('User', { From 82745f91a3d2474c33fbee5daba34befa298d330 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:28:53 -0600 Subject: [PATCH 08/32] refactor!: remove conflict diagnostics and keymap configuration - Remove automatic diagnostic suppression and conflict keymap settings. - Expose conflict and merge events with native configuration recipes. --- doc/diffs.nvim.txt | 122 +++++++++------------- lua/diffs/commands.lua | 3 +- lua/diffs/config.lua | 46 +-------- lua/diffs/conflict.lua | 97 +++--------------- lua/diffs/merge.lua | 48 +++------ lua/diffs/runtime/init.lua | 2 +- plugin/diffs.lua | 4 +- spec/commands_spec.lua | 8 +- spec/config_spec.lua | 39 +------ spec/conflict_spec.lua | 202 +++++-------------------------------- spec/helpers.lua | 15 --- spec/jj_conflict_spec.lua | 21 +--- spec/merge_spec.lua | 99 ++++-------------- spec/plugin_spec.lua | 6 +- spec/ux_spec.lua | 21 ---- 15 files changed, 138 insertions(+), 595 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 073cde7c..b1e13143 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -97,10 +97,7 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: vim = true, intra = true, }, - conflict = { - disable_diagnostics = true, - keymaps = false, - }, + conflict = true, } < @@ -171,9 +168,8 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Controls which highlight features are enabled. See |diffs.nvim.Highlights| for fields. - {conflict} (boolean|table, default: true) - Inline merge conflict resolution options. - See |diffs.nvim.ConflictConfig| for fields. + {conflict} (boolean, default: true) + Highlight and resolve inline merge conflicts. *diffs.nvim.Highlights* Highlights table fields: ~ @@ -1077,80 +1073,47 @@ Run |:checkhealth| diffs to confirm `difft` is detected. CONFLICT RESOLUTION *diffs.nvim-conflict* diffs.nvim detects inline merge conflict markers (`<<<<<<<`/`=======`/ -`>>>>>>>`) in working files and provides highlighting and resolution keymaps. +`>>>>>>>`) in working files and provides highlighting and resolution actions. Both standard and diff3 (`|||||||`) formats are supported. Conflict regions are detected automatically on `BufReadPost` and re-scanned on `TextChanged`. When all conflicts in a buffer are resolved, highlighting is -removed and diagnostics are re-enabled. +removed. -Configuration: ~ - *diffs.nvim.ConflictConfig* ->lua - vim.g.diffs = { - conflict = { - disable_diagnostics = true, - keymaps = false, - }, - } -< - To opt in to buffer-local mappings, provide a table: >lua - vim.g.diffs = { - conflict = { - keymaps = { - ours = 'co', - theirs = 'ct', - both = 'cb', - none = 'c0', - next = ']x', - prev = '[x', - }, +Buffer-local mappings: ~ *diffs.nvim-conflict-keymaps* + +Map the |(diffs-conflict-ours)| family directly, or use this recipe for +buffer-local mappings. It preserves existing mappings and user replacements: >lua + vim.api.nvim_create_autocmd('User', { + pattern = { + 'DiffsConflictDetected', 'DiffsConflictResolved', + 'DiffsConflictDetached', 'DiffsMergeReady', }, - } + callback = function(event) + local bufnr = event.data.bufnr + local merge = event.match == 'DiffsMergeReady' + local active = merge or event.match == 'DiffsConflictDetected' + local prefix = merge and 'diffs-merge-' or 'diffs-conflict-' + for lhs, action in pairs({ + co = 'ours', ct = 'theirs', cb = 'both', c0 = 'none', + [']x'] = 'next', ['[x'] = 'prev', + }) do + local rhs = '(' .. prefix .. action .. ')' + local current = vim.iter(vim.api.nvim_buf_get_keymap(bufnr, 'n')) + :find(function(map) return map.lhs == lhs end) + if active and not current then + vim.keymap.set('n', lhs, rhs, { buffer = bufnr }) + elseif not active and current and current.rhs == rhs then + vim.keymap.del('n', lhs, { buffer = bufnr }) + end + end + end, + }) < - Fields: ~ - {disable_diagnostics} (boolean, default: true) - Suppress LSP diagnostics on buffers with - conflict markers. Markers produce syntax - errors that clutter the diagnostic list. - Diagnostics are re-enabled when all conflicts - are resolved. Set `false` to leave - diagnostics alone. This option will be removed on - 30 November 2026. See |diffs.nvim-conflict-diagnostics|. - - {keymaps} (table|false, default: false) - Opt-in buffer-local keymaps for conflict - resolution and navigation in conflicted - working buffers and merge diff views. Pass a - table to enable plugin-defined mappings. - Unspecified fields remain disabled. - - *diffs.nvim.ConflictKeymaps* - Keymap fields: ~ - {ours} (string|false, default: false) - Accept current (ours) change. - - {theirs} (string|false, default: false) - Accept incoming (theirs) change. - - {both} (string|false, default: false) - Accept both changes (ours then theirs). - - {none} (string|false, default: false) - Reject both changes (delete entire block). - - {next} (string|false, default: false) - Jump to next conflict marker. Wraps around. - - {prev} (string|false, default: false) - Jump to previous conflict marker. Wraps - around. Diagnostic suppression: ~ *diffs.nvim-conflict-diagnostics* Hide diagnostics while a buffer contains merge conflicts: >lua - vim.g.diffs = { conflict = { disable_diagnostics = false } } - vim.api.nvim_create_autocmd('User', { pattern = { 'DiffsConflictDetected', @@ -1174,7 +1137,9 @@ Hide diagnostics while a buffer contains merge conflicts: >lua User events: ~ -All conflict events include the working buffer number in `event.data.bufnr`. +All events below include their target buffer number in `event.data.bufnr`. +Conflict events identify the working buffer. `DiffsMergeReady` identifies the +generated merge buffer. *DiffsConflictDetected* DiffsConflictDetected Fired when conflicts first appear in an attached @@ -1187,6 +1152,10 @@ All conflict events include the working buffer number in `event.data.bufnr`. DiffsConflictDetached Fired when conflict highlighting is explicitly detached from a buffer. + *DiffsMergeReady* + DiffsMergeReady Fired after a generated merge view is initialized + or refreshed. + ============================================================================== MERGE DIFF RESOLUTION *diffs.nvim-merge* @@ -1197,10 +1166,9 @@ ours (`git show :2:path`) vs theirs (`git show :3:path`) with full treesitter and intra-line highlighting. Revision comparisons such as `:Diff HEAD` keep rendering that revision against the conflicted worktree buffer. -When `conflict.keymaps` is configured, those same conflict resolution mappings -are available on the diff buffer. The corresponding |(diffs-merge-ours)| -family is documented in |diffs.nvim-mappings|. They resolve conflicts in the -working file by matching diff hunks to conflict markers: +The |(diffs-merge-ours)| family resolves conflicts in the working file +by matching diff hunks to conflict markers. The recipe in +|diffs.nvim-conflict-keymaps| uses these mappings: - `co` replaces the conflict region with ours content - `ct` replaces the conflict region with theirs content @@ -1228,8 +1196,8 @@ merged working file in Neovim: < When the file opens, diffs.nvim detects conflict markers automatically. -Configure `conflict.keymaps` to enable buffer-local resolution mappings; see -|diffs.nvim-conflict|. From a Git-unmerged file, |:Diff| can also open the +For buffer-local resolution mappings, see |diffs.nvim-conflict-keymaps|. +From a Git-unmerged file, |:Diff| can also open the generated ours-vs-theirs merge diff view documented above. Save and quit after resolving conflicts. Use `:cq` to abort. diff --git a/lua/diffs/commands.lua b/lua/diffs/commands.lua index b069d28c..248e4a18 100644 --- a/lua/diffs/commands.lua +++ b/lua/diffs/commands.lua @@ -190,7 +190,6 @@ end ---@param bufnr integer function M.setup_diff_buf(bufnr) - vim.diagnostic.enable(false, { bufnr = bufnr }) if not get_buffer_keymap(bufnr, 'n', 'q') then vim.keymap.set('n', 'q', 'close', { buffer = bufnr }) end @@ -2260,7 +2259,7 @@ function M.diff_file(filepath, opts) vim.api.nvim_buf_set_var(diff_buf, 'diffs_working_path', filepath) local conflict_config = runtime.get_conflict_config() if conflict_config then - require('diffs.merge').setup_keymaps(diff_buf, conflict_config) + require('diffs.merge').attach(diff_buf) end end diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index cf1003b4..c3f1a619 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -25,18 +25,6 @@ local integration_metadata = require('diffs.integrations') ---@field vim diffs.VimConfig|false ---@field intra diffs.IntraConfig|false ----@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 disable_diagnostics boolean ----@field keymaps diffs.ConflictKeymaps|false - ---@class diffs.DifftasticConfig ---@field args? string[] @@ -55,7 +43,7 @@ local integration_metadata = require('diffs.integrations') ---@field extra_filetypes string[] ---@field highlights diffs.Highlights ---@field integrations diffs.IntegrationsConfig ----@field conflict diffs.ConflictConfig|false +---@field conflict boolean ---@type diffs.Config local DEFAULTS = { @@ -80,10 +68,7 @@ local DEFAULTS = { }, }, integrations = {}, - conflict = { - disable_diagnostics = true, - keymaps = false, - }, + conflict = true, } ---@param opts? table @@ -145,29 +130,7 @@ function M.validate(opts) end end - vim.validate('conflict', opts.conflict, function(v) - return v == nil or type(v) == 'boolean' or type(v) == 'table' - end, 'boolean or table') - if type(opts.conflict) == 'table' then - vim.validate('conflict.disable_diagnostics', opts.conflict.disable_diagnostics, 'boolean', true) - vim.validate('conflict.keymaps', opts.conflict.keymaps, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') - - if type(opts.conflict.keymaps) == 'table' then - local keymap_validator = function(v) - return v == nil or 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 + vim.validate('conflict', opts.conflict, 'boolean', true) if opts.highlights @@ -186,9 +149,6 @@ function M.new(opts) }) M.validate(opts) local result = vim.tbl_deep_extend('force', DEFAULTS, opts) - if result.conflict == true then - result.conflict = vim.deepcopy(DEFAULTS.conflict) - end for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do if result.highlights[key] == true then result.highlights[key] = vim.deepcopy(DEFAULTS.highlights[key]) diff --git a/lua/diffs/conflict.lua b/lua/diffs/conflict.lua index d6c96159..eb0b36f1 100644 --- a/lua/diffs/conflict.lua +++ b/lua/diffs/conflict.lua @@ -13,7 +13,6 @@ local M = {} -local keymaps = require('diffs.keymaps') local notify = require('diffs.log').notify local ns = vim.api.nvim_create_namespace('diffs-conflict') @@ -22,33 +21,12 @@ local CONFLICT_PRIORITY = 200 ---@type table local attached_buffers = {} ----@type table -local diagnostics_suppressed = {} - ----@type table> -local buffer_keymaps = {} - ---@param event string ---@param bufnr integer local function emit(event, bufnr) vim.api.nvim_exec_autocmds('User', { pattern = event, data = { bufnr = bufnr } }) end ----@param bufnr integer ----@param config diffs.ConflictConfig -local function suppress_diagnostics(bufnr, config) - if not config.disable_diagnostics or diagnostics_suppressed[bufnr] then - return - end - vim.notify_once( - '[diffs]: conflict.disable_diagnostics will be removed on 30 November 2026. ' - .. 'See :help diffs.nvim-conflict-diagnostics.', - vim.log.levels.WARN - ) - pcall(vim.diagnostic.enable, false, { bufnr = bufnr }) - diagnostics_suppressed[bufnr] = true -end - ---@param line string ---@param char string ---@return boolean @@ -173,8 +151,6 @@ local function parse_buffer(bufnr) return M.parse(lines) end -local setup_keymaps - ---@param bufnr integer ---@param row integer ---@param hl_group string @@ -269,28 +245,17 @@ function M.replace_region(bufnr, region, replacement) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.refresh(bufnr, config) +function M.refresh(bufnr) local had_conflicts = #vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { limit = 1 }) > 0 local regions = parse_buffer(bufnr) if #regions == 0 then vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) - if diagnostics_suppressed[bufnr] then - pcall(vim.diagnostic.reset, nil, bufnr) - pcall(vim.diagnostic.enable, true, { bufnr = bufnr }) - diagnostics_suppressed[bufnr] = nil - end if had_conflicts then emit('DiffsConflictResolved', bufnr) end return end apply_highlights(bufnr, regions) - if not buffer_keymaps[bufnr] then - setup_keymaps(bufnr, config) - end - suppress_diagnostics(bufnr, config) if not had_conflicts then emit('DiffsConflictDetected', bufnr) end @@ -318,9 +283,8 @@ function M.replacement_lines(bufnr, region, side) end ---@param bufnr integer ----@param config diffs.ConflictConfig ---@param side 'ours'|'theirs'|'both'|'none' -local function resolve(bufnr, config, side) +local function resolve(bufnr, side) if not vim.api.nvim_get_option_value('modifiable', { buf = bufnr }) then notify('buffer is not modifiable', vim.log.levels.WARN) return @@ -331,31 +295,27 @@ local function resolve(bufnr, config, side) return end M.replace_region(bufnr, region, M.replacement_lines(bufnr, region, side)) - M.refresh(bufnr, config) + M.refresh(bufnr) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_ours(bufnr, config) - resolve(bufnr, config, 'ours') +function M.resolve_ours(bufnr) + resolve(bufnr, 'ours') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_theirs(bufnr, config) - resolve(bufnr, config, 'theirs') +function M.resolve_theirs(bufnr) + resolve(bufnr, 'theirs') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_both(bufnr, config) - resolve(bufnr, config, 'both') +function M.resolve_both(bufnr) + resolve(bufnr, 'both') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_none(bufnr, config) - resolve(bufnr, config, 'none') +function M.resolve_none(bufnr) + resolve(bufnr, 'none') end ---@param bufnr integer @@ -397,45 +357,19 @@ function M.goto_prev(bufnr) goto_conflict(bufnr, false) end ----@param bufnr integer ----@param config diffs.ConflictConfig -setup_keymaps = function(bufnr, config) - local km = config.keymaps - if km == false then - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) - return - end - - keymaps.set_buffer_keymaps(buffer_keymaps, bufnr, { - { km.ours, '(diffs-conflict-ours)' }, - { km.theirs, '(diffs-conflict-theirs)' }, - { km.both, '(diffs-conflict-both)' }, - { km.none, '(diffs-conflict-none)' }, - { km.next, '(diffs-conflict-next)' }, - { km.prev, '(diffs-conflict-prev)' }, - }) -end - ---@param bufnr integer function M.detach(bufnr) local attached = attached_buffers[bufnr] vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil - if diagnostics_suppressed[bufnr] then - pcall(vim.diagnostic.reset, nil, bufnr) - pcall(vim.diagnostic.enable, true, { bufnr = bufnr }) - diagnostics_suppressed[bufnr] = nil - end if attached then emit('DiffsConflictDetached', bufnr) end end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.attach(bufnr, config) +function M.attach(bufnr) if attached_buffers[bufnr] then return end @@ -454,9 +388,6 @@ function M.attach(bufnr, config) attached_buffers[bufnr] = true apply_highlights(bufnr, regions) - setup_keymaps(bufnr, config) - - suppress_diagnostics(bufnr, config) vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { buffer = bufnr, @@ -464,16 +395,14 @@ function M.attach(bufnr, config) if not attached_buffers[bufnr] then return true end - M.refresh(bufnr, config) + M.refresh(bufnr) end, }) vim.api.nvim_create_autocmd('BufWipeout', { buffer = bufnr, callback = function() - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil - diagnostics_suppressed[bufnr] = nil end, }) emit('DiffsConflictDetected', bufnr) diff --git a/lua/diffs/merge.lua b/lua/diffs/merge.lua index a38a582d..4e0684bc 100644 --- a/lua/diffs/merge.lua +++ b/lua/diffs/merge.lua @@ -1,16 +1,12 @@ local M = {} local conflict = require('diffs.conflict') -local keymaps = require('diffs.keymaps') local notify = require('diffs.log').notify local rails = require('diffs.rails') ---@type table> local resolved_hunks = {} ----@type table> -local buffer_keymaps = {} - ---@class diffs.MergeHunkInfo ---@field index integer ---@field start_line integer @@ -145,9 +141,8 @@ function M.is_resolved(diff_bufnr, hunk_index) end ---@param diff_bufnr integer ----@param config diffs.ConflictConfig ---@param side 'ours'|'theirs'|'both'|'none' -local function resolve(diff_bufnr, config, side) +local function resolve(diff_bufnr, side) local hunk = M.find_hunk_at_cursor(diff_bufnr) if not hunk then return @@ -170,32 +165,28 @@ local function resolve(diff_bufnr, config, side) region, conflict.replacement_lines(working_bufnr, region, side) ) - conflict.refresh(working_bufnr, config) + conflict.refresh(working_bufnr) mark_resolved(diff_bufnr, hunk.index) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_ours(bufnr, config) - resolve(bufnr, config, 'ours') +function M.resolve_ours(bufnr) + resolve(bufnr, 'ours') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_theirs(bufnr, config) - resolve(bufnr, config, 'theirs') +function M.resolve_theirs(bufnr) + resolve(bufnr, 'theirs') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_both(bufnr, config) - resolve(bufnr, config, 'both') +function M.resolve_both(bufnr) + resolve(bufnr, 'both') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_none(bufnr, config) - resolve(bufnr, config, 'none') +function M.resolve_none(bufnr) + resolve(bufnr, 'none') end ---@param bufnr integer @@ -256,31 +247,16 @@ function M.goto_prev(bufnr) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.setup_keymaps(bufnr, config) +function M.attach(bufnr) resolved_hunks[bufnr] = nil - local km = config.keymaps - if km ~= false then - keymaps.set_buffer_keymaps(buffer_keymaps, bufnr, { - { km.ours, '(diffs-merge-ours)' }, - { km.theirs, '(diffs-merge-theirs)' }, - { km.both, '(diffs-merge-both)' }, - { km.none, '(diffs-merge-none)' }, - { km.next, '(diffs-merge-next)' }, - { km.prev, '(diffs-merge-prev)' }, - }) - else - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) - end - vim.api.nvim_create_autocmd('BufWipeout', { buffer = bufnr, callback = function() - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) resolved_hunks[bufnr] = nil end, }) + vim.api.nvim_exec_autocmds('User', { pattern = 'DiffsMergeReady', data = { bufnr = bufnr } }) end return M diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index f108bd12..e277c32a 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -191,7 +191,7 @@ function M.get_telescope_config() return config.integrations.telescope == true end ----@return diffs.ConflictConfig|false +---@return boolean function M.get_conflict_config() init() return config.conflict diff --git a/plugin/diffs.lua b/plugin/diffs.lua index af9803f0..d273a5fa 100644 --- a/plugin/diffs.lua +++ b/plugin/diffs.lua @@ -105,7 +105,7 @@ vim.api.nvim_create_autocmd('BufReadPost', { local runtime = get_runtime() local conflict_config = runtime.get_conflict_config() if conflict_config then - require('diffs.conflict').attach(args.buf, conflict_config) + require('diffs.conflict').attach(args.buf) end end, }) @@ -174,7 +174,7 @@ local function conflict_action(fn) local runtime = get_runtime() local conflict_config = runtime.get_conflict_config() if conflict_config then - fn(bufnr, conflict_config) + fn(bufnr) end end diff --git a/spec/commands_spec.lua b/spec/commands_spec.lua index fb8724dc..7e548f6f 100644 --- a/spec/commands_spec.lua +++ b/spec/commands_spec.lua @@ -2182,9 +2182,7 @@ describe('commands', function() edit_file(filepath) local source_win = vim.api.nvim_get_current_win() mock_runtime_attach(function() end) - mock_conflict_config({ - keymaps = helpers.default_conflict_keymaps(), - }) + mock_conflict_config(true) assert.is_true(git.is_unmerged(filepath)) @@ -2216,8 +2214,8 @@ describe('commands', function() assert.is_true(text:find('--- /dev/null', 1, true) == nil) assert.is_true(text:find('new file mode', 1, true) == nil) assert.is_true(text:find('<<<<<<<', 1, true) == nil) - assert.is_true(helpers.has_keymap(bufnr, 'go')) - assert.is_true(helpers.has_keymap(bufnr, 'gt')) + assert.is_false(helpers.has_keymap(bufnr, 'go')) + assert.is_false(helpers.has_keymap(bufnr, 'gt')) if expected_rail_style then assert.are.equal(expected_rail_style, vim.api.nvim_buf_get_var(bufnr, 'diffs_rail_style')) end diff --git a/spec/config_spec.lua b/spec/config_spec.lua index e4921af0..fcadd7cc 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -42,17 +42,7 @@ describe('diffs.config', function() committia = true, telescope = true, }, - conflict = { - disable_diagnostics = false, - keymaps = { - ours = 'co', - theirs = 'ct', - both = 'cb', - none = 'c0', - next = ']x', - prev = '[x', - }, - }, + conflict = false, }) end) end) @@ -99,7 +89,6 @@ describe('diffs.config', function() assert.is_nil(opts.hide_prefix) assert.is_nil(opts.highlights.gutter) assert.is_nil(opts.highlights.priorities) - assert.is_nil(opts.conflict.priority) end) it('keeps supported highlights config without removed priorities', function() @@ -110,16 +99,10 @@ describe('diffs.config', function() assert.is_nil(opts.highlights.priorities) end) - it('accepts boolean and table conflict settings', function() - local defaults = config.new().conflict - for _, value in ipairs({ true, {} }) do - assert.are.same(defaults, config.new({ conflict = value }).conflict) - end + it('accepts boolean conflict settings', function() + assert.is_true(config.new().conflict) + assert.is_true(config.new({ conflict = true }).conflict) assert.is_false(config.new({ conflict = false }).conflict) - assert.are.same( - vim.tbl_extend('force', defaults, { keymaps = { ours = 'co' } }), - config.new({ conflict = { keymaps = { ours = 'co' } } }).conflict - ) end) it('accepts integration lists and boolean maps', function() @@ -197,20 +180,6 @@ describe('diffs.config', function() assert.matches('default', err, 1, true) assert.matches('vscode', err, 1, true) end) - - it('validates conflict keymap values', function() - local ok, err = pcall(config.new, { - conflict = { - keymaps = { - ours = true, - }, - }, - }) - - assert.is_false(ok) - assert.matches('conflict.keymaps.ours', err, 1, true) - assert.matches('string or false', err, 1, true) - end) end) describe('compute_filetypes', function() diff --git a/spec/conflict_spec.lua b/spec/conflict_spec.lua index e7cc64c6..4d0a9a88 100644 --- a/spec/conflict_spec.lua +++ b/spec/conflict_spec.lua @@ -1,17 +1,6 @@ local conflict = require('diffs.conflict') local helpers = require('spec.helpers') -local function default_config(overrides) - local cfg = { - disable_diagnostics = false, - keymaps = false, - } - if overrides then - cfg = vim.tbl_deep_extend('force', cfg, overrides) - end - return cfg -end - local function create_file_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, false) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) @@ -175,7 +164,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) @@ -211,7 +200,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local checked = 0 for _, mark in ipairs(get_extmarks(bufnr)) do @@ -235,7 +224,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) @@ -256,7 +245,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) local has_ours_nr = false @@ -287,7 +276,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) local has_base = false @@ -311,7 +300,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.is_true(#get_extmarks(bufnr) > 0) conflict.detach(bufnr) @@ -321,147 +310,6 @@ describe('conflict', function() end) end) - describe('keymaps', function() - it('does not set buffer-local keymaps when disabled', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - - conflict.attach(bufnr, default_config()) - - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('removes buffer-local keymaps on detach', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - assert.is_true(helpers.has_keymap(bufnr, 'gt')) - - conflict.detach(bufnr) - - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('removes buffer-local keymaps when all conflicts are resolved', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - assert.is_true(helpers.has_keymap(bufnr, 'gt')) - - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local x = 2' }) - conflict.refresh(bufnr, cfg) - - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('reinstalls buffer-local keymaps when conflicts return', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local x = 2' }) - conflict.refresh(bufnr, cfg) - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - conflict.refresh(bufnr, cfg) - - assert.is_true(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('does not replace pre-existing buffer-local keymaps', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - vim.keymap.set('n', 'gt', '', { buffer = bufnr, desc = 'user theirs' }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - local theirs = helpers.get_keymap(bufnr, 'gt') - local ours = helpers.get_keymap(bufnr, 'go') - assert.are.equal('user theirs', theirs.desc) - assert.are.equal('(diffs-conflict-ours)', ours.rhs) - - conflict.detach(bufnr) - - theirs = helpers.get_keymap(bufnr, 'gt') - assert.are.equal('user theirs', theirs.desc) - assert.is_false(helpers.has_keymap(bufnr, 'go')) - - helpers.delete_buffer(bufnr) - end) - - it('does not remove a user replacement during cleanup', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - assert.are.equal('(diffs-conflict-ours)', helpers.get_keymap(bufnr, 'go').rhs) - - vim.keymap.set('n', 'go', '', { buffer = bufnr, desc = 'user ours' }) - conflict.detach(bufnr) - - local ours = helpers.get_keymap(bufnr, 'go') - assert.are.equal('user ours', ours.desc) - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - end) - describe('resolution', function() local function make_conflict_buffer() local bufnr = create_file_buffer({ @@ -479,7 +327,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -492,7 +340,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_theirs(bufnr, default_config()) + conflict.resolve_theirs(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -505,7 +353,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_both(bufnr, default_config()) + conflict.resolve_both(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(2, #lines) @@ -519,7 +367,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_none(bufnr, default_config()) + conflict.resolve_none(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -540,7 +388,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(6, #lines) @@ -565,7 +413,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal('a', lines[1]) @@ -585,7 +433,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -607,7 +455,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_theirs(bufnr, default_config()) + conflict.resolve_theirs(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -781,10 +629,9 @@ describe('conflict', function() 'b', '>>>>>>> feat', }) - local cfg = default_config() - conflict.attach(bufnr, cfg) + conflict.attach(bufnr) local count1 = #get_extmarks(bufnr) - conflict.attach(bufnr, cfg) + conflict.attach(bufnr) local count2 = #get_extmarks(bufnr) assert.are.equal(count1, count2) conflict.detach(bufnr) @@ -801,7 +648,7 @@ describe('conflict', function() }) vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) helpers.delete_buffer(bufnr) @@ -810,7 +657,7 @@ describe('conflict', function() it('skips buffers without conflict markers', function() local bufnr = create_file_buffer({ 'local x = 1', 'local y = 2' }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) helpers.delete_buffer(bufnr) @@ -833,15 +680,14 @@ describe('conflict', function() end, }) vim.api.nvim_set_current_buf(bufnr) - local cfg = default_config() - conflict.attach(bufnr, cfg) + conflict.attach(bufnr) assert.is_true(#get_extmarks(bufnr) > 0) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, cfg) + conflict.resolve_ours(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) - conflict.refresh(bufnr, cfg) + conflict.refresh(bufnr) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '<<<<<<< HEAD', @@ -854,7 +700,7 @@ describe('conflict', function() assert.is_true(#get_extmarks(bufnr) > 0) - conflict.refresh(bufnr, cfg) + conflict.refresh(bufnr) conflict.detach(bufnr) assert.are.same({ 'DiffsConflictDetected', @@ -875,12 +721,12 @@ describe('conflict', function() '>>>>>>> feature', }) vim.api.nvim_set_current_buf(bufnr) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.is_true(#get_extmarks(bufnr) > 0) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) diff --git a/spec/helpers.lua b/spec/helpers.lua index 297d2335..64d78982 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -16,21 +16,6 @@ ensure_parser('vim') local M = {} -function M.default_conflict_keymaps(overrides) - local keymaps = { - ours = 'go', - theirs = 'gt', - both = 'gb', - none = 'g0', - next = ']x', - prev = '[x', - } - if overrides then - keymaps = vim.tbl_extend('force', keymaps, overrides) - end - return keymaps -end - function M.create_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) diff --git a/spec/jj_conflict_spec.lua b/spec/jj_conflict_spec.lua index 36d65649..b1b60007 100644 --- a/spec/jj_conflict_spec.lua +++ b/spec/jj_conflict_spec.lua @@ -1,16 +1,5 @@ local conflict = require('diffs.conflict') -local function default_config(overrides) - local cfg = { - disable_diagnostics = false, - keymaps = false, - } - if overrides then - cfg = vim.tbl_deep_extend('force', cfg, overrides) - end - return cfg -end - local function create_file_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, false) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) @@ -89,7 +78,7 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) assert.are.same({ 'line1', 'AAA', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) @@ -98,7 +87,7 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - conflict.resolve_theirs(bufnr, default_config()) + conflict.resolve_theirs(bufnr) assert.are.same({ 'line1', 'BBB', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) @@ -107,14 +96,14 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_none(bufnr, default_config()) + conflict.resolve_none(bufnr) assert.are.same({ 'line1', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) it('highlights the opening marker', function() local bufnr = create_file_buffer(snapshot_lines()) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local rows = {} for _, mark in ipairs( @@ -145,7 +134,7 @@ describe('jj conflict markers', function() it('does not attach when nothing parses', function() local bufnr = create_file_buffer(diff_lines()) - conflict.attach(bufnr, default_config({ keymaps = { ours = 'gto' } })) + conflict.attach(bufnr) assert.are.equal( 0, #vim.api.nvim_buf_get_extmarks(bufnr, conflict.get_namespace(), 0, -1, {}) diff --git a/spec/merge_spec.lua b/spec/merge_spec.lua index 0f0ca4c6..12a5e8d6 100644 --- a/spec/merge_spec.lua +++ b/spec/merge_spec.lua @@ -2,17 +2,6 @@ local helpers = require('spec.helpers') local merge = require('diffs.merge') local rails = require('diffs.rails') -local function default_config(overrides) - local cfg = { - disable_diagnostics = false, - keymaps = false, - } - if overrides then - cfg = vim.tbl_deep_extend('force', cfg, overrides) - end - return cfg -end - local function create_diff_buffer(lines, working_path) local bufnr = helpers.create_buffer(lines) if working_path then @@ -316,7 +305,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -329,7 +318,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_theirs(diff_bufnr, default_config()) + merge.resolve_theirs(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -342,7 +331,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_both(diff_bufnr, default_config()) + merge.resolve_both(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(2, #lines) @@ -356,7 +345,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_none(diff_bufnr, default_config()) + merge.resolve_none(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -370,7 +359,7 @@ describe('merge', function() vim.api.nvim_win_set_cursor(0, { 5, 0 }) assert.is_false(merge.is_resolved(diff_bufnr, 1)) - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) assert.is_true(merge.is_resolved(diff_bufnr, 1)) cleanup() @@ -406,7 +395,7 @@ describe('merge', function() assert.is_not_nil(merge.match_hunk_to_conflict(hunks[1], w_bufnr)) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_theirs(d_bufnr, default_config()) + merge.resolve_theirs(d_bufnr) local lines = vim.api.nvim_buf_get_lines(w_bufnr, 0, -1, false) assert.are.same({ 'local x = 2' }, lines) @@ -419,7 +408,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) local notified = false local orig_notify = vim.notify @@ -429,7 +418,7 @@ describe('merge', function() end end - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) vim.notify = orig_notify assert.is_true(notified) @@ -462,7 +451,7 @@ describe('merge', function() end end - merge.resolve_ours(d_bufnr, default_config()) + merge.resolve_ours(d_bufnr) vim.notify = orig_notify assert.is_true(notified) @@ -715,7 +704,7 @@ describe('merge', function() vim.api.nvim_set_current_buf(d_bufnr) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(d_bufnr, default_config()) + merge.resolve_ours(d_bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) merge.goto_next(d_bufnr) @@ -726,7 +715,7 @@ describe('merge', function() end) end) - describe('setup_keymaps', function() + describe('attach', function() it('clears resolved state on re-init', function() local working_path = '/tmp/diffs_test_reinit.lua' local w_bufnr = create_working_buffer({ @@ -748,68 +737,24 @@ describe('merge', function() vim.api.nvim_set_current_buf(d_bufnr) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - local cfg = default_config() - merge.resolve_ours(d_bufnr, cfg) + merge.resolve_ours(d_bufnr) assert.is_true(merge.is_resolved(d_bufnr, 1)) - merge.setup_keymaps(d_bufnr, cfg) + local ready_bufnr + vim.api.nvim_create_autocmd('User', { + pattern = 'DiffsMergeReady', + once = true, + callback = function(event) + ready_bufnr = event.data.bufnr + end, + }) + merge.attach(d_bufnr) + assert.are.equal(d_bufnr, ready_bufnr) assert.is_false(merge.is_resolved(d_bufnr, 1)) helpers.delete_buffer(d_bufnr) helpers.delete_buffer(w_bufnr) end) - - it('does not replace pre-existing buffer-local keymaps', function() - local d_bufnr = create_diff_buffer({ - 'diff --git a/file.lua b/file.lua', - '--- a/file.lua', - '+++ b/file.lua', - '@@ -1,1 +1,1 @@', - '-local x = 1', - '+local x = 2', - }) - vim.keymap.set('n', 'gt', '', { buffer = d_bufnr, desc = 'user theirs' }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - merge.setup_keymaps(d_bufnr, cfg) - - local theirs = helpers.get_keymap(d_bufnr, 'gt') - local ours = helpers.get_keymap(d_bufnr, 'go') - assert.are.equal('user theirs', theirs.desc) - assert.are.equal('(diffs-merge-ours)', ours.rhs) - - merge.setup_keymaps(d_bufnr, default_config()) - - theirs = helpers.get_keymap(d_bufnr, 'gt') - assert.are.equal('user theirs', theirs.desc) - assert.is_false(helpers.has_keymap(d_bufnr, 'go')) - - helpers.delete_buffer(d_bufnr) - end) - - it('does not remove a user replacement during cleanup', function() - local d_bufnr = create_diff_buffer({ - 'diff --git a/file.lua b/file.lua', - '--- a/file.lua', - '+++ b/file.lua', - '@@ -1,1 +1,1 @@', - '-local x = 1', - '+local x = 2', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - merge.setup_keymaps(d_bufnr, cfg) - assert.are.equal('(diffs-merge-ours)', helpers.get_keymap(d_bufnr, 'go').rhs) - - vim.keymap.set('n', 'go', '', { buffer = d_bufnr, desc = 'user ours' }) - merge.setup_keymaps(d_bufnr, default_config()) - - local ours = helpers.get_keymap(d_bufnr, 'go') - assert.are.equal('user ours', ours.desc) - assert.is_false(helpers.has_keymap(d_bufnr, 'gt')) - - helpers.delete_buffer(d_bufnr) - end) end) describe('fugitive integration', function() diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 6b77fb47..85303f0c 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -106,7 +106,7 @@ describe('plugin bootstrap', function() 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { keymaps = false }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = false, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -130,7 +130,7 @@ describe('plugin bootstrap', function() "print('runtime_committia=' .. tostring(runtime_config.integrations.committia))", "print('runtime_telescope=' .. tostring(runtime_config.integrations.telescope))", "print('runtime_background=' .. tostring(runtime_config.highlights.background))", - "print('runtime_conflict_keymaps=' .. tostring(runtime_config.conflict.keymaps))", + "print('runtime_conflict=' .. tostring(runtime_config.conflict))", "print('runtime_config_priorities=' .. tostring(runtime_config.highlights.priorities))", "print('highlight_priority_clear=' .. tostring(highlight_opts.highlights.priorities.clear))", "print('highlight_priority_syntax=' .. tostring(highlight_opts.highlights.priorities.syntax))", @@ -170,7 +170,7 @@ describe('plugin bootstrap', function() assert.matches('runtime_committia=true', output, 1, true) assert.matches('runtime_telescope=true', output, 1, true) assert.matches('runtime_background=false', output, 1, true) - assert.matches('runtime_conflict_keymaps=false', output, 1, true) + assert.matches('runtime_conflict=false', output, 1, true) assert.matches('runtime_config_priorities=nil', output, 1, true) assert.matches('highlight_priority_clear=198', output, 1, true) assert.matches('highlight_priority_syntax=199', output, 1, true) diff --git a/spec/ux_spec.lua b/spec/ux_spec.lua index b4307643..e1e8aa17 100644 --- a/spec/ux_spec.lua +++ b/spec/ux_spec.lua @@ -46,27 +46,6 @@ local function keymap_desc(bufnr, lhs, mode) end describe('ux', function() - describe('diagnostics', function() - it('disables diagnostics on diff buffers', function() - local bufnr = create_diffs_buffer() - commands.setup_diff_buf(bufnr) - - assert.is_false(vim.diagnostic.is_enabled({ bufnr = bufnr })) - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) - - it('does not affect other buffers', function() - local diff_buf = create_diffs_buffer() - local normal_buf = helpers.create_buffer({ 'hello' }) - - commands.setup_diff_buf(diff_buf) - - assert.is_true(vim.diagnostic.is_enabled({ bufnr = normal_buf })) - vim.api.nvim_buf_delete(diff_buf, { force = true }) - helpers.delete_buffer(normal_buf) - end) - end) - describe('q keymap', function() it('sets q keymap on diff buffer', function() local bufnr = create_diffs_buffer() From 03e7771a5d19d3b948b24410bd2d86fbc49aa902 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:32:23 -0600 Subject: [PATCH 09/32] docs: simplify conflict mapping recipes --- doc/diffs.nvim.txt | 56 +++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index b1e13143..622c65af 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -1082,31 +1082,17 @@ removed. Buffer-local mappings: ~ *diffs.nvim-conflict-keymaps* -Map the |(diffs-conflict-ours)| family directly, or use this recipe for -buffer-local mappings. It preserves existing mappings and user replacements: >lua +Set buffer-local mappings when conflicts are detected: >lua vim.api.nvim_create_autocmd('User', { - pattern = { - 'DiffsConflictDetected', 'DiffsConflictResolved', - 'DiffsConflictDetached', 'DiffsMergeReady', - }, + pattern = 'DiffsConflictDetected', callback = function(event) - local bufnr = event.data.bufnr - local merge = event.match == 'DiffsMergeReady' - local active = merge or event.match == 'DiffsConflictDetected' - local prefix = merge and 'diffs-merge-' or 'diffs-conflict-' - for lhs, action in pairs({ - co = 'ours', ct = 'theirs', cb = 'both', c0 = 'none', - [']x'] = 'next', ['[x'] = 'prev', - }) do - local rhs = '(' .. prefix .. action .. ')' - local current = vim.iter(vim.api.nvim_buf_get_keymap(bufnr, 'n')) - :find(function(map) return map.lhs == lhs end) - if active and not current then - vim.keymap.set('n', lhs, rhs, { buffer = bufnr }) - elseif not active and current and current.rhs == rhs then - vim.keymap.del('n', lhs, { buffer = bufnr }) - end - end + local opts = { buffer = event.data.bufnr } + vim.keymap.set('n', 'co', '(diffs-conflict-ours)', opts) + vim.keymap.set('n', 'ct', '(diffs-conflict-theirs)', opts) + vim.keymap.set('n', 'cb', '(diffs-conflict-both)', opts) + vim.keymap.set('n', 'c0', '(diffs-conflict-none)', opts) + vim.keymap.set('n', ']x', '(diffs-conflict-next)', opts) + vim.keymap.set('n', '[x', '(diffs-conflict-prev)', opts) end, }) < @@ -1167,14 +1153,22 @@ and intra-line highlighting. Revision comparisons such as `:Diff HEAD` keep rendering that revision against the conflicted worktree buffer. The |(diffs-merge-ours)| family resolves conflicts in the working file -by matching diff hunks to conflict markers. The recipe in -|diffs.nvim-conflict-keymaps| uses these mappings: - -- `co` replaces the conflict region with ours content -- `ct` replaces the conflict region with theirs content -- `cb` replaces with both (ours then theirs) -- `c0` removes the conflict region entirely -- `]c`/`[c` navigate between unresolved conflict hunks +by matching diff hunks to conflict markers. + +Set buffer-local mappings for merge views: >lua + vim.api.nvim_create_autocmd('User', { + pattern = 'DiffsMergeReady', + callback = function(event) + local opts = { buffer = event.data.bufnr } + vim.keymap.set('n', 'co', '(diffs-merge-ours)', opts) + vim.keymap.set('n', 'ct', '(diffs-merge-theirs)', opts) + vim.keymap.set('n', 'cb', '(diffs-merge-both)', opts) + vim.keymap.set('n', 'c0', '(diffs-merge-none)', opts) + vim.keymap.set('n', ']x', '(diffs-merge-next)', opts) + vim.keymap.set('n', '[x', '(diffs-merge-prev)', opts) + end, + }) +< Hunks that correspond to auto-merged content (no conflict markers) show an informational notification and are left unchanged. From e800dde12c39e276fbacac49a78674c2162af5d8 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:42:18 -0600 Subject: [PATCH 10/32] chore: deprecate conflict keymaps - Preserve configured conflict mappings and their lifecycle behavior. - Warn that conflict.keymaps support ends on 30 November 2026. --- doc/diffs.nvim.txt | 5 +- lua/diffs/commands.lua | 2 +- lua/diffs/config.lua | 43 +++++++- lua/diffs/conflict.lua | 65 +++++++++--- lua/diffs/merge.lua | 47 +++++++-- lua/diffs/runtime/init.lua | 10 +- plugin/diffs.lua | 4 +- spec/commands_spec.lua | 8 +- spec/config_spec.lua | 38 ++++++- spec/conflict_spec.lua | 201 ++++++++++++++++++++++++++++++++----- spec/helpers.lua | 15 +++ spec/jj_conflict_spec.lua | 20 +++- spec/merge_spec.lua | 87 +++++++++++++--- spec/plugin_spec.lua | 13 ++- 14 files changed, 473 insertions(+), 85 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 622c65af..5ef7e349 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -168,7 +168,7 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Controls which highlight features are enabled. See |diffs.nvim.Highlights| for fields. - {conflict} (boolean, default: true) + {conflict} (boolean|table, default: true) Highlight and resolve inline merge conflicts. *diffs.nvim.Highlights* @@ -1082,7 +1082,8 @@ removed. Buffer-local mappings: ~ *diffs.nvim-conflict-keymaps* -Set buffer-local mappings when conflicts are detected: >lua +`conflict.keymaps` is deprecated and will be removed on 30 November 2026. +Replace it with buffer-local mappings: >lua vim.api.nvim_create_autocmd('User', { pattern = 'DiffsConflictDetected', callback = function(event) diff --git a/lua/diffs/commands.lua b/lua/diffs/commands.lua index 248e4a18..b54b9ab8 100644 --- a/lua/diffs/commands.lua +++ b/lua/diffs/commands.lua @@ -2259,7 +2259,7 @@ function M.diff_file(filepath, opts) vim.api.nvim_buf_set_var(diff_buf, 'diffs_working_path', filepath) local conflict_config = runtime.get_conflict_config() if conflict_config then - require('diffs.merge').attach(diff_buf) + require('diffs.merge').attach(diff_buf, conflict_config) end end diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index c3f1a619..325e5bf3 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -25,6 +25,17 @@ local integration_metadata = require('diffs.integrations') ---@field vim diffs.VimConfig|false ---@field intra diffs.IntraConfig|false +---@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 keymaps diffs.ConflictKeymaps|false + ---@class diffs.DifftasticConfig ---@field args? string[] @@ -43,7 +54,7 @@ local integration_metadata = require('diffs.integrations') ---@field extra_filetypes string[] ---@field highlights diffs.Highlights ---@field integrations diffs.IntegrationsConfig ----@field conflict boolean +---@field conflict diffs.ConflictConfig|false ---@type diffs.Config local DEFAULTS = { @@ -68,7 +79,9 @@ local DEFAULTS = { }, }, integrations = {}, - conflict = true, + conflict = { + keymaps = false, + }, } ---@param opts? table @@ -130,7 +143,28 @@ function M.validate(opts) end end - vim.validate('conflict', opts.conflict, 'boolean', true) + vim.validate('conflict', opts.conflict, function(v) + return v == nil or type(v) == 'boolean' or type(v) == 'table' + end, 'boolean or table') + if type(opts.conflict) == 'table' then + vim.validate('conflict.keymaps', opts.conflict.keymaps, function(v) + return v == nil or v == false or type(v) == 'table' + end, 'table or false') + + if type(opts.conflict.keymaps) == 'table' then + local keymap_validator = function(v) + return v == nil or 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 @@ -149,6 +183,9 @@ function M.new(opts) }) M.validate(opts) local result = vim.tbl_deep_extend('force', DEFAULTS, opts) + if result.conflict == true then + result.conflict = vim.deepcopy(DEFAULTS.conflict) + end for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do if result.highlights[key] == true then result.highlights[key] = vim.deepcopy(DEFAULTS.highlights[key]) diff --git a/lua/diffs/conflict.lua b/lua/diffs/conflict.lua index eb0b36f1..1eb0fbbb 100644 --- a/lua/diffs/conflict.lua +++ b/lua/diffs/conflict.lua @@ -13,6 +13,7 @@ local M = {} +local keymaps = require('diffs.keymaps') local notify = require('diffs.log').notify local ns = vim.api.nvim_create_namespace('diffs-conflict') @@ -21,6 +22,9 @@ local CONFLICT_PRIORITY = 200 ---@type table local attached_buffers = {} +---@type table> +local buffer_keymaps = {} + ---@param event string ---@param bufnr integer local function emit(event, bufnr) @@ -151,6 +155,8 @@ local function parse_buffer(bufnr) return M.parse(lines) end +local setup_keymaps + ---@param bufnr integer ---@param row integer ---@param hl_group string @@ -245,17 +251,22 @@ function M.replace_region(bufnr, region, replacement) end ---@param bufnr integer -function M.refresh(bufnr) +---@param config diffs.ConflictConfig +function M.refresh(bufnr, config) local had_conflicts = #vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { limit = 1 }) > 0 local regions = parse_buffer(bufnr) if #regions == 0 then vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) if had_conflicts then emit('DiffsConflictResolved', bufnr) end return end apply_highlights(bufnr, regions) + if not buffer_keymaps[bufnr] then + setup_keymaps(bufnr, config) + end if not had_conflicts then emit('DiffsConflictDetected', bufnr) end @@ -283,8 +294,9 @@ function M.replacement_lines(bufnr, region, side) end ---@param bufnr integer +---@param config diffs.ConflictConfig ---@param side 'ours'|'theirs'|'both'|'none' -local function resolve(bufnr, side) +local function resolve(bufnr, config, side) if not vim.api.nvim_get_option_value('modifiable', { buf = bufnr }) then notify('buffer is not modifiable', vim.log.levels.WARN) return @@ -295,27 +307,31 @@ local function resolve(bufnr, side) return end M.replace_region(bufnr, region, M.replacement_lines(bufnr, region, side)) - M.refresh(bufnr) + M.refresh(bufnr, config) end ---@param bufnr integer -function M.resolve_ours(bufnr) - resolve(bufnr, 'ours') +---@param config diffs.ConflictConfig +function M.resolve_ours(bufnr, config) + resolve(bufnr, config, 'ours') end ---@param bufnr integer -function M.resolve_theirs(bufnr) - resolve(bufnr, 'theirs') +---@param config diffs.ConflictConfig +function M.resolve_theirs(bufnr, config) + resolve(bufnr, config, 'theirs') end ---@param bufnr integer -function M.resolve_both(bufnr) - resolve(bufnr, 'both') +---@param config diffs.ConflictConfig +function M.resolve_both(bufnr, config) + resolve(bufnr, config, 'both') end ---@param bufnr integer -function M.resolve_none(bufnr) - resolve(bufnr, 'none') +---@param config diffs.ConflictConfig +function M.resolve_none(bufnr, config) + resolve(bufnr, config, 'none') end ---@param bufnr integer @@ -357,10 +373,30 @@ function M.goto_prev(bufnr) goto_conflict(bufnr, false) end +---@param bufnr integer +---@param config diffs.ConflictConfig +setup_keymaps = function(bufnr, config) + local km = config.keymaps + if km == false then + keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) + return + end + + keymaps.set_buffer_keymaps(buffer_keymaps, bufnr, { + { km.ours, '(diffs-conflict-ours)' }, + { km.theirs, '(diffs-conflict-theirs)' }, + { km.both, '(diffs-conflict-both)' }, + { km.none, '(diffs-conflict-none)' }, + { km.next, '(diffs-conflict-next)' }, + { km.prev, '(diffs-conflict-prev)' }, + }) +end + ---@param bufnr integer function M.detach(bufnr) local attached = attached_buffers[bufnr] vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil if attached then @@ -369,7 +405,8 @@ function M.detach(bufnr) end ---@param bufnr integer -function M.attach(bufnr) +---@param config diffs.ConflictConfig +function M.attach(bufnr, config) if attached_buffers[bufnr] then return end @@ -388,6 +425,7 @@ function M.attach(bufnr) attached_buffers[bufnr] = true apply_highlights(bufnr, regions) + setup_keymaps(bufnr, config) vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { buffer = bufnr, @@ -395,13 +433,14 @@ function M.attach(bufnr) if not attached_buffers[bufnr] then return true end - M.refresh(bufnr) + M.refresh(bufnr, config) end, }) vim.api.nvim_create_autocmd('BufWipeout', { buffer = bufnr, callback = function() + keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil end, }) diff --git a/lua/diffs/merge.lua b/lua/diffs/merge.lua index 4e0684bc..5ee4ead5 100644 --- a/lua/diffs/merge.lua +++ b/lua/diffs/merge.lua @@ -1,12 +1,16 @@ local M = {} local conflict = require('diffs.conflict') +local keymaps = require('diffs.keymaps') local notify = require('diffs.log').notify local rails = require('diffs.rails') ---@type table> local resolved_hunks = {} +---@type table> +local buffer_keymaps = {} + ---@class diffs.MergeHunkInfo ---@field index integer ---@field start_line integer @@ -141,8 +145,9 @@ function M.is_resolved(diff_bufnr, hunk_index) end ---@param diff_bufnr integer +---@param config diffs.ConflictConfig ---@param side 'ours'|'theirs'|'both'|'none' -local function resolve(diff_bufnr, side) +local function resolve(diff_bufnr, config, side) local hunk = M.find_hunk_at_cursor(diff_bufnr) if not hunk then return @@ -165,28 +170,32 @@ local function resolve(diff_bufnr, side) region, conflict.replacement_lines(working_bufnr, region, side) ) - conflict.refresh(working_bufnr) + conflict.refresh(working_bufnr, config) mark_resolved(diff_bufnr, hunk.index) end ---@param bufnr integer -function M.resolve_ours(bufnr) - resolve(bufnr, 'ours') +---@param config diffs.ConflictConfig +function M.resolve_ours(bufnr, config) + resolve(bufnr, config, 'ours') end ---@param bufnr integer -function M.resolve_theirs(bufnr) - resolve(bufnr, 'theirs') +---@param config diffs.ConflictConfig +function M.resolve_theirs(bufnr, config) + resolve(bufnr, config, 'theirs') end ---@param bufnr integer -function M.resolve_both(bufnr) - resolve(bufnr, 'both') +---@param config diffs.ConflictConfig +function M.resolve_both(bufnr, config) + resolve(bufnr, config, 'both') end ---@param bufnr integer -function M.resolve_none(bufnr) - resolve(bufnr, 'none') +---@param config diffs.ConflictConfig +function M.resolve_none(bufnr, config) + resolve(bufnr, config, 'none') end ---@param bufnr integer @@ -247,12 +256,28 @@ function M.goto_prev(bufnr) end ---@param bufnr integer -function M.attach(bufnr) +---@param config diffs.ConflictConfig +function M.attach(bufnr, config) resolved_hunks[bufnr] = nil + local km = config.keymaps + if km ~= false then + keymaps.set_buffer_keymaps(buffer_keymaps, bufnr, { + { km.ours, '(diffs-merge-ours)' }, + { km.theirs, '(diffs-merge-theirs)' }, + { km.both, '(diffs-merge-both)' }, + { km.none, '(diffs-merge-none)' }, + { km.next, '(diffs-merge-next)' }, + { km.prev, '(diffs-merge-prev)' }, + }) + else + keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) + end + vim.api.nvim_create_autocmd('BufWipeout', { buffer = bufnr, callback = function() + keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) resolved_hunks[bufnr] = nil end, }) diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index e277c32a..2634b8e2 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -80,6 +80,14 @@ local function init() ) end + if config.conflict and config.conflict.keymaps then + log.notify( + 'conflict.keymaps will be removed on 30 November 2026. ' + .. 'Use vim.keymap.set(). See :help diffs.nvim-conflict-keymaps.', + vim.log.levels.WARN + ) + end + fast_hl_opts = highlight.hunk_opts(config, { highlights = { treesitter = false, @@ -191,7 +199,7 @@ function M.get_telescope_config() return config.integrations.telescope == true end ----@return boolean +---@return diffs.ConflictConfig|false function M.get_conflict_config() init() return config.conflict diff --git a/plugin/diffs.lua b/plugin/diffs.lua index d273a5fa..af9803f0 100644 --- a/plugin/diffs.lua +++ b/plugin/diffs.lua @@ -105,7 +105,7 @@ vim.api.nvim_create_autocmd('BufReadPost', { local runtime = get_runtime() local conflict_config = runtime.get_conflict_config() if conflict_config then - require('diffs.conflict').attach(args.buf) + require('diffs.conflict').attach(args.buf, conflict_config) end end, }) @@ -174,7 +174,7 @@ local function conflict_action(fn) local runtime = get_runtime() local conflict_config = runtime.get_conflict_config() if conflict_config then - fn(bufnr) + fn(bufnr, conflict_config) end end diff --git a/spec/commands_spec.lua b/spec/commands_spec.lua index 7e548f6f..fb8724dc 100644 --- a/spec/commands_spec.lua +++ b/spec/commands_spec.lua @@ -2182,7 +2182,9 @@ describe('commands', function() edit_file(filepath) local source_win = vim.api.nvim_get_current_win() mock_runtime_attach(function() end) - mock_conflict_config(true) + mock_conflict_config({ + keymaps = helpers.default_conflict_keymaps(), + }) assert.is_true(git.is_unmerged(filepath)) @@ -2214,8 +2216,8 @@ describe('commands', function() assert.is_true(text:find('--- /dev/null', 1, true) == nil) assert.is_true(text:find('new file mode', 1, true) == nil) assert.is_true(text:find('<<<<<<<', 1, true) == nil) - assert.is_false(helpers.has_keymap(bufnr, 'go')) - assert.is_false(helpers.has_keymap(bufnr, 'gt')) + assert.is_true(helpers.has_keymap(bufnr, 'go')) + assert.is_true(helpers.has_keymap(bufnr, 'gt')) if expected_rail_style then assert.are.equal(expected_rail_style, vim.api.nvim_buf_get_var(bufnr, 'diffs_rail_style')) end diff --git a/spec/config_spec.lua b/spec/config_spec.lua index fcadd7cc..21e1febe 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -42,7 +42,16 @@ describe('diffs.config', function() committia = true, telescope = true, }, - conflict = false, + conflict = { + keymaps = { + ours = 'co', + theirs = 'ct', + both = 'cb', + none = 'c0', + next = ']x', + prev = '[x', + }, + }, }) end) end) @@ -89,6 +98,7 @@ describe('diffs.config', function() assert.is_nil(opts.hide_prefix) assert.is_nil(opts.highlights.gutter) assert.is_nil(opts.highlights.priorities) + assert.is_nil(opts.conflict.priority) end) it('keeps supported highlights config without removed priorities', function() @@ -99,10 +109,16 @@ describe('diffs.config', function() assert.is_nil(opts.highlights.priorities) end) - it('accepts boolean conflict settings', function() - assert.is_true(config.new().conflict) - assert.is_true(config.new({ conflict = true }).conflict) + it('accepts boolean and table conflict settings', function() + local defaults = config.new().conflict + for _, value in ipairs({ true, {} }) do + assert.are.same(defaults, config.new({ conflict = value }).conflict) + end assert.is_false(config.new({ conflict = false }).conflict) + assert.are.same( + vim.tbl_extend('force', defaults, { keymaps = { ours = 'co' } }), + config.new({ conflict = { keymaps = { ours = 'co' } } }).conflict + ) end) it('accepts integration lists and boolean maps', function() @@ -180,6 +196,20 @@ describe('diffs.config', function() assert.matches('default', err, 1, true) assert.matches('vscode', err, 1, true) end) + + it('validates conflict keymap values', function() + local ok, err = pcall(config.new, { + conflict = { + keymaps = { + ours = true, + }, + }, + }) + + assert.is_false(ok) + assert.matches('conflict.keymaps.ours', err, 1, true) + assert.matches('string or false', err, 1, true) + end) end) describe('compute_filetypes', function() diff --git a/spec/conflict_spec.lua b/spec/conflict_spec.lua index 4d0a9a88..614c3ac8 100644 --- a/spec/conflict_spec.lua +++ b/spec/conflict_spec.lua @@ -1,6 +1,16 @@ local conflict = require('diffs.conflict') local helpers = require('spec.helpers') +local function default_config(overrides) + local cfg = { + keymaps = false, + } + if overrides then + cfg = vim.tbl_deep_extend('force', cfg, overrides) + end + return cfg +end + local function create_file_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, false) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) @@ -164,7 +174,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) @@ -200,7 +210,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) local checked = 0 for _, mark in ipairs(get_extmarks(bufnr)) do @@ -224,7 +234,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) @@ -245,7 +255,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) local extmarks = get_extmarks(bufnr) local has_ours_nr = false @@ -276,7 +286,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) local extmarks = get_extmarks(bufnr) local has_base = false @@ -300,7 +310,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) assert.is_true(#get_extmarks(bufnr) > 0) conflict.detach(bufnr) @@ -310,6 +320,147 @@ describe('conflict', function() end) end) + describe('keymaps', function() + it('does not set buffer-local keymaps when disabled', function() + local bufnr = create_file_buffer({ + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + + conflict.attach(bufnr, default_config()) + + assert.is_false(helpers.has_keymap(bufnr, 'gt')) + + helpers.delete_buffer(bufnr) + end) + + it('removes buffer-local keymaps on detach', function() + local bufnr = create_file_buffer({ + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + conflict.attach(bufnr, cfg) + + assert.is_true(helpers.has_keymap(bufnr, 'gt')) + + conflict.detach(bufnr) + + assert.is_false(helpers.has_keymap(bufnr, 'gt')) + + helpers.delete_buffer(bufnr) + end) + + it('removes buffer-local keymaps when all conflicts are resolved', function() + local bufnr = create_file_buffer({ + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + conflict.attach(bufnr, cfg) + + assert.is_true(helpers.has_keymap(bufnr, 'gt')) + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local x = 2' }) + conflict.refresh(bufnr, cfg) + + assert.is_false(helpers.has_keymap(bufnr, 'gt')) + + helpers.delete_buffer(bufnr) + end) + + it('reinstalls buffer-local keymaps when conflicts return', function() + local bufnr = create_file_buffer({ + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + conflict.attach(bufnr, cfg) + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local x = 2' }) + conflict.refresh(bufnr, cfg) + assert.is_false(helpers.has_keymap(bufnr, 'gt')) + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + conflict.refresh(bufnr, cfg) + + assert.is_true(helpers.has_keymap(bufnr, 'gt')) + + helpers.delete_buffer(bufnr) + end) + + it('does not replace pre-existing buffer-local keymaps', function() + local bufnr = create_file_buffer({ + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + vim.keymap.set('n', 'gt', '', { buffer = bufnr, desc = 'user theirs' }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + conflict.attach(bufnr, cfg) + + local theirs = helpers.get_keymap(bufnr, 'gt') + local ours = helpers.get_keymap(bufnr, 'go') + assert.are.equal('user theirs', theirs.desc) + assert.are.equal('(diffs-conflict-ours)', ours.rhs) + + conflict.detach(bufnr) + + theirs = helpers.get_keymap(bufnr, 'gt') + assert.are.equal('user theirs', theirs.desc) + assert.is_false(helpers.has_keymap(bufnr, 'go')) + + helpers.delete_buffer(bufnr) + end) + + it('does not remove a user replacement during cleanup', function() + local bufnr = create_file_buffer({ + '<<<<<<< HEAD', + 'local x = 1', + '=======', + 'local x = 2', + '>>>>>>> feature', + }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + conflict.attach(bufnr, cfg) + assert.are.equal('(diffs-conflict-ours)', helpers.get_keymap(bufnr, 'go').rhs) + + vim.keymap.set('n', 'go', '', { buffer = bufnr, desc = 'user ours' }) + conflict.detach(bufnr) + + local ours = helpers.get_keymap(bufnr, 'go') + assert.are.equal('user ours', ours.desc) + assert.is_false(helpers.has_keymap(bufnr, 'gt')) + + helpers.delete_buffer(bufnr) + end) + end) + describe('resolution', function() local function make_conflict_buffer() local bufnr = create_file_buffer({ @@ -327,7 +478,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -340,7 +491,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_theirs(bufnr) + conflict.resolve_theirs(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -353,7 +504,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_both(bufnr) + conflict.resolve_both(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(2, #lines) @@ -367,7 +518,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_none(bufnr) + conflict.resolve_none(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -388,7 +539,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(6, #lines) @@ -413,7 +564,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal('a', lines[1]) @@ -433,7 +584,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -455,7 +606,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_theirs(bufnr) + conflict.resolve_theirs(bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -629,9 +780,10 @@ describe('conflict', function() 'b', '>>>>>>> feat', }) - conflict.attach(bufnr) + local cfg = default_config() + conflict.attach(bufnr, cfg) local count1 = #get_extmarks(bufnr) - conflict.attach(bufnr) + conflict.attach(bufnr, cfg) local count2 = #get_extmarks(bufnr) assert.are.equal(count1, count2) conflict.detach(bufnr) @@ -648,7 +800,7 @@ describe('conflict', function() }) vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) assert.are.equal(0, #get_extmarks(bufnr)) helpers.delete_buffer(bufnr) @@ -657,7 +809,7 @@ describe('conflict', function() it('skips buffers without conflict markers', function() local bufnr = create_file_buffer({ 'local x = 1', 'local y = 2' }) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) assert.are.equal(0, #get_extmarks(bufnr)) helpers.delete_buffer(bufnr) @@ -671,6 +823,7 @@ describe('conflict', function() 'local x = 2', '>>>>>>> feature', }) + local cfg = default_config() local events = {} local autocmd = vim.api.nvim_create_autocmd('User', { pattern = { 'DiffsConflictDetected', 'DiffsConflictResolved', 'DiffsConflictDetached' }, @@ -680,14 +833,14 @@ describe('conflict', function() end, }) vim.api.nvim_set_current_buf(bufnr) - conflict.attach(bufnr) + conflict.attach(bufnr, cfg) assert.is_true(#get_extmarks(bufnr) > 0) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, cfg) assert.are.equal(0, #get_extmarks(bufnr)) - conflict.refresh(bufnr) + conflict.refresh(bufnr, cfg) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '<<<<<<< HEAD', @@ -700,7 +853,7 @@ describe('conflict', function() assert.is_true(#get_extmarks(bufnr) > 0) - conflict.refresh(bufnr) + conflict.refresh(bufnr, cfg) conflict.detach(bufnr) assert.are.same({ 'DiffsConflictDetected', @@ -721,12 +874,12 @@ describe('conflict', function() '>>>>>>> feature', }) vim.api.nvim_set_current_buf(bufnr) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) assert.is_true(#get_extmarks(bufnr) > 0) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, default_config()) assert.are.equal(0, #get_extmarks(bufnr)) diff --git a/spec/helpers.lua b/spec/helpers.lua index 64d78982..297d2335 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -16,6 +16,21 @@ ensure_parser('vim') local M = {} +function M.default_conflict_keymaps(overrides) + local keymaps = { + ours = 'go', + theirs = 'gt', + both = 'gb', + none = 'g0', + next = ']x', + prev = '[x', + } + if overrides then + keymaps = vim.tbl_extend('force', keymaps, overrides) + end + return keymaps +end + function M.create_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) diff --git a/spec/jj_conflict_spec.lua b/spec/jj_conflict_spec.lua index b1b60007..e35a78cd 100644 --- a/spec/jj_conflict_spec.lua +++ b/spec/jj_conflict_spec.lua @@ -1,5 +1,15 @@ local conflict = require('diffs.conflict') +local function default_config(overrides) + local cfg = { + keymaps = false, + } + if overrides then + cfg = vim.tbl_deep_extend('force', cfg, overrides) + end + return cfg +end + local function create_file_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, false) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) @@ -78,7 +88,7 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - conflict.resolve_ours(bufnr) + conflict.resolve_ours(bufnr, default_config()) assert.are.same({ 'line1', 'AAA', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) @@ -87,7 +97,7 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - conflict.resolve_theirs(bufnr) + conflict.resolve_theirs(bufnr, default_config()) assert.are.same({ 'line1', 'BBB', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) @@ -96,14 +106,14 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_none(bufnr) + conflict.resolve_none(bufnr, default_config()) assert.are.same({ 'line1', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) it('highlights the opening marker', function() local bufnr = create_file_buffer(snapshot_lines()) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config()) local rows = {} for _, mark in ipairs( @@ -134,7 +144,7 @@ describe('jj conflict markers', function() it('does not attach when nothing parses', function() local bufnr = create_file_buffer(diff_lines()) - conflict.attach(bufnr) + conflict.attach(bufnr, default_config({ keymaps = { ours = 'gto' } })) assert.are.equal( 0, #vim.api.nvim_buf_get_extmarks(bufnr, conflict.get_namespace(), 0, -1, {}) diff --git a/spec/merge_spec.lua b/spec/merge_spec.lua index 12a5e8d6..d43f488e 100644 --- a/spec/merge_spec.lua +++ b/spec/merge_spec.lua @@ -2,6 +2,16 @@ local helpers = require('spec.helpers') local merge = require('diffs.merge') local rails = require('diffs.rails') +local function default_config(overrides) + local cfg = { + keymaps = false, + } + if overrides then + cfg = vim.tbl_deep_extend('force', cfg, overrides) + end + return cfg +end + local function create_diff_buffer(lines, working_path) local bufnr = helpers.create_buffer(lines) if working_path then @@ -305,7 +315,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(diff_bufnr) + merge.resolve_ours(diff_bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -318,7 +328,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_theirs(diff_bufnr) + merge.resolve_theirs(diff_bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -331,7 +341,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_both(diff_bufnr) + merge.resolve_both(diff_bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(2, #lines) @@ -345,7 +355,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_none(diff_bufnr) + merge.resolve_none(diff_bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -359,7 +369,7 @@ describe('merge', function() vim.api.nvim_win_set_cursor(0, { 5, 0 }) assert.is_false(merge.is_resolved(diff_bufnr, 1)) - merge.resolve_ours(diff_bufnr) + merge.resolve_ours(diff_bufnr, default_config()) assert.is_true(merge.is_resolved(diff_bufnr, 1)) cleanup() @@ -395,7 +405,7 @@ describe('merge', function() assert.is_not_nil(merge.match_hunk_to_conflict(hunks[1], w_bufnr)) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_theirs(d_bufnr) + merge.resolve_theirs(d_bufnr, default_config()) local lines = vim.api.nvim_buf_get_lines(w_bufnr, 0, -1, false) assert.are.same({ 'local x = 2' }, lines) @@ -408,7 +418,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(diff_bufnr) + merge.resolve_ours(diff_bufnr, default_config()) local notified = false local orig_notify = vim.notify @@ -418,7 +428,7 @@ describe('merge', function() end end - merge.resolve_ours(diff_bufnr) + merge.resolve_ours(diff_bufnr, default_config()) vim.notify = orig_notify assert.is_true(notified) @@ -451,7 +461,7 @@ describe('merge', function() end end - merge.resolve_ours(d_bufnr) + merge.resolve_ours(d_bufnr, default_config()) vim.notify = orig_notify assert.is_true(notified) @@ -704,7 +714,7 @@ describe('merge', function() vim.api.nvim_set_current_buf(d_bufnr) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(d_bufnr) + merge.resolve_ours(d_bufnr, default_config()) vim.api.nvim_win_set_cursor(0, { 1, 0 }) merge.goto_next(d_bufnr) @@ -737,7 +747,8 @@ describe('merge', function() vim.api.nvim_set_current_buf(d_bufnr) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(d_bufnr) + local cfg = default_config() + merge.resolve_ours(d_bufnr, cfg) assert.is_true(merge.is_resolved(d_bufnr, 1)) local ready_bufnr @@ -748,13 +759,65 @@ describe('merge', function() ready_bufnr = event.data.bufnr end, }) - merge.attach(d_bufnr) + merge.attach(d_bufnr, cfg) assert.are.equal(d_bufnr, ready_bufnr) assert.is_false(merge.is_resolved(d_bufnr, 1)) helpers.delete_buffer(d_bufnr) helpers.delete_buffer(w_bufnr) end) + + it('does not replace pre-existing buffer-local keymaps', function() + local d_bufnr = create_diff_buffer({ + 'diff --git a/file.lua b/file.lua', + '--- a/file.lua', + '+++ b/file.lua', + '@@ -1,1 +1,1 @@', + '-local x = 1', + '+local x = 2', + }) + vim.keymap.set('n', 'gt', '', { buffer = d_bufnr, desc = 'user theirs' }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + merge.attach(d_bufnr, cfg) + + local theirs = helpers.get_keymap(d_bufnr, 'gt') + local ours = helpers.get_keymap(d_bufnr, 'go') + assert.are.equal('user theirs', theirs.desc) + assert.are.equal('(diffs-merge-ours)', ours.rhs) + + merge.attach(d_bufnr, default_config()) + + theirs = helpers.get_keymap(d_bufnr, 'gt') + assert.are.equal('user theirs', theirs.desc) + assert.is_false(helpers.has_keymap(d_bufnr, 'go')) + + helpers.delete_buffer(d_bufnr) + end) + + it('does not remove a user replacement during cleanup', function() + local d_bufnr = create_diff_buffer({ + 'diff --git a/file.lua b/file.lua', + '--- a/file.lua', + '+++ b/file.lua', + '@@ -1,1 +1,1 @@', + '-local x = 1', + '+local x = 2', + }) + local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) + + merge.attach(d_bufnr, cfg) + assert.are.equal('(diffs-merge-ours)', helpers.get_keymap(d_bufnr, 'go').rhs) + + vim.keymap.set('n', 'go', '', { buffer = d_bufnr, desc = 'user ours' }) + merge.attach(d_bufnr, default_config()) + + local ours = helpers.get_keymap(d_bufnr, 'go') + assert.are.equal('user ours', ours.desc) + assert.is_false(helpers.has_keymap(d_bufnr, 'gt')) + + helpers.delete_buffer(d_bufnr) + end) end) describe('fugitive integration', function() diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 85303f0c..0a7b5001 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -106,7 +106,7 @@ describe('plugin bootstrap', function() 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = false, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { keymaps = { ours = "co" } }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -119,6 +119,8 @@ describe('plugin bootstrap', function() 'runtime.attach(0)', "print('warning=' .. _G.diffs_notifications[1].message)", "print('warning_level=' .. _G.diffs_notifications[1].level)", + "print('keymap_warning=' .. _G.diffs_notifications[2].message)", + "print('keymap_warning_level=' .. _G.diffs_notifications[2].level)", "print('after_attach_notifications=' .. #(_G.diffs_notifications or {}))", 'local runtime_config = runtime._test.get_config()', 'local highlight_opts = runtime.get_highlight_opts()', @@ -130,7 +132,7 @@ describe('plugin bootstrap', function() "print('runtime_committia=' .. tostring(runtime_config.integrations.committia))", "print('runtime_telescope=' .. tostring(runtime_config.integrations.telescope))", "print('runtime_background=' .. tostring(runtime_config.highlights.background))", - "print('runtime_conflict=' .. tostring(runtime_config.conflict))", + "print('runtime_conflict_keymaps=' .. tostring(runtime_config.conflict.keymaps.ours))", "print('runtime_config_priorities=' .. tostring(runtime_config.highlights.priorities))", "print('highlight_priority_clear=' .. tostring(highlight_opts.highlights.priorities.clear))", "print('highlight_priority_syntax=' .. tostring(highlight_opts.highlights.priorities.syntax))", @@ -158,9 +160,12 @@ describe('plugin bootstrap', function() assert.matches('loaded=1', output, 1, true) assert.matches('notifications=0', output, 1, true) - assert.matches('after_attach_notifications=1', output, 1, true) + assert.matches('after_attach_notifications=2', output, 1, true) assert.matches('libvscodediff support will be removed on 30 November 2026.', output, 1, true) assert.matches('Remove highlights.intra.algorithm', output, 1, true) + assert.matches('conflict.keymaps will be removed on 30 November 2026.', output, 1, true) + assert.matches(':help diffs.nvim-conflict-keymaps', output, 1, true) + assert.matches('keymap_warning_level=' .. vim.log.levels.WARN, output, 1, true) assert.matches('warning_level=' .. vim.log.levels.WARN, output, 1, true) assert.matches('runtime_view_prefix=false', output, 1, true) assert.matches('runtime_fugitive=true', output, 1, true) @@ -170,7 +175,7 @@ describe('plugin bootstrap', function() assert.matches('runtime_committia=true', output, 1, true) assert.matches('runtime_telescope=true', output, 1, true) assert.matches('runtime_background=false', output, 1, true) - assert.matches('runtime_conflict=false', output, 1, true) + assert.matches('runtime_conflict_keymaps=co', output, 1, true) assert.matches('runtime_config_priorities=nil', output, 1, true) assert.matches('highlight_priority_clear=198', output, 1, true) assert.matches('highlight_priority_syntax=199', output, 1, true) From fbe61be450aff7cfe5a21b0e3ef8c5f434d26e0d Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 13:43:24 -0600 Subject: [PATCH 11/32] refactor!: remove conflict keymap configuration - Remove conflict keymap configuration, installation, cleanup, and the deprecation warning. - Reduce conflict configuration to a boolean and remove unused config arguments. --- doc/diffs.nvim.txt | 5 +- lua/diffs/commands.lua | 2 +- lua/diffs/config.lua | 43 +------- lua/diffs/conflict.lua | 65 +++--------- lua/diffs/merge.lua | 47 ++------- lua/diffs/runtime/init.lua | 10 +- plugin/diffs.lua | 4 +- spec/commands_spec.lua | 8 +- spec/config_spec.lua | 38 +------ spec/conflict_spec.lua | 201 +++++-------------------------------- spec/helpers.lua | 15 --- spec/jj_conflict_spec.lua | 20 +--- spec/merge_spec.lua | 87 +++------------- spec/plugin_spec.lua | 13 +-- 14 files changed, 85 insertions(+), 473 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 5ef7e349..622c65af 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -168,7 +168,7 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Controls which highlight features are enabled. See |diffs.nvim.Highlights| for fields. - {conflict} (boolean|table, default: true) + {conflict} (boolean, default: true) Highlight and resolve inline merge conflicts. *diffs.nvim.Highlights* @@ -1082,8 +1082,7 @@ removed. Buffer-local mappings: ~ *diffs.nvim-conflict-keymaps* -`conflict.keymaps` is deprecated and will be removed on 30 November 2026. -Replace it with buffer-local mappings: >lua +Set buffer-local mappings when conflicts are detected: >lua vim.api.nvim_create_autocmd('User', { pattern = 'DiffsConflictDetected', callback = function(event) diff --git a/lua/diffs/commands.lua b/lua/diffs/commands.lua index b54b9ab8..248e4a18 100644 --- a/lua/diffs/commands.lua +++ b/lua/diffs/commands.lua @@ -2259,7 +2259,7 @@ function M.diff_file(filepath, opts) vim.api.nvim_buf_set_var(diff_buf, 'diffs_working_path', filepath) local conflict_config = runtime.get_conflict_config() if conflict_config then - require('diffs.merge').attach(diff_buf, conflict_config) + require('diffs.merge').attach(diff_buf) end end diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 325e5bf3..c3f1a619 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -25,17 +25,6 @@ local integration_metadata = require('diffs.integrations') ---@field vim diffs.VimConfig|false ---@field intra diffs.IntraConfig|false ----@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 keymaps diffs.ConflictKeymaps|false - ---@class diffs.DifftasticConfig ---@field args? string[] @@ -54,7 +43,7 @@ local integration_metadata = require('diffs.integrations') ---@field extra_filetypes string[] ---@field highlights diffs.Highlights ---@field integrations diffs.IntegrationsConfig ----@field conflict diffs.ConflictConfig|false +---@field conflict boolean ---@type diffs.Config local DEFAULTS = { @@ -79,9 +68,7 @@ local DEFAULTS = { }, }, integrations = {}, - conflict = { - keymaps = false, - }, + conflict = true, } ---@param opts? table @@ -143,28 +130,7 @@ function M.validate(opts) end end - vim.validate('conflict', opts.conflict, function(v) - return v == nil or type(v) == 'boolean' or type(v) == 'table' - end, 'boolean or table') - if type(opts.conflict) == 'table' then - vim.validate('conflict.keymaps', opts.conflict.keymaps, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') - - if type(opts.conflict.keymaps) == 'table' then - local keymap_validator = function(v) - return v == nil or 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 + vim.validate('conflict', opts.conflict, 'boolean', true) if opts.highlights @@ -183,9 +149,6 @@ function M.new(opts) }) M.validate(opts) local result = vim.tbl_deep_extend('force', DEFAULTS, opts) - if result.conflict == true then - result.conflict = vim.deepcopy(DEFAULTS.conflict) - end for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do if result.highlights[key] == true then result.highlights[key] = vim.deepcopy(DEFAULTS.highlights[key]) diff --git a/lua/diffs/conflict.lua b/lua/diffs/conflict.lua index 1eb0fbbb..eb0b36f1 100644 --- a/lua/diffs/conflict.lua +++ b/lua/diffs/conflict.lua @@ -13,7 +13,6 @@ local M = {} -local keymaps = require('diffs.keymaps') local notify = require('diffs.log').notify local ns = vim.api.nvim_create_namespace('diffs-conflict') @@ -22,9 +21,6 @@ local CONFLICT_PRIORITY = 200 ---@type table local attached_buffers = {} ----@type table> -local buffer_keymaps = {} - ---@param event string ---@param bufnr integer local function emit(event, bufnr) @@ -155,8 +151,6 @@ local function parse_buffer(bufnr) return M.parse(lines) end -local setup_keymaps - ---@param bufnr integer ---@param row integer ---@param hl_group string @@ -251,22 +245,17 @@ function M.replace_region(bufnr, region, replacement) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.refresh(bufnr, config) +function M.refresh(bufnr) local had_conflicts = #vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { limit = 1 }) > 0 local regions = parse_buffer(bufnr) if #regions == 0 then vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) if had_conflicts then emit('DiffsConflictResolved', bufnr) end return end apply_highlights(bufnr, regions) - if not buffer_keymaps[bufnr] then - setup_keymaps(bufnr, config) - end if not had_conflicts then emit('DiffsConflictDetected', bufnr) end @@ -294,9 +283,8 @@ function M.replacement_lines(bufnr, region, side) end ---@param bufnr integer ----@param config diffs.ConflictConfig ---@param side 'ours'|'theirs'|'both'|'none' -local function resolve(bufnr, config, side) +local function resolve(bufnr, side) if not vim.api.nvim_get_option_value('modifiable', { buf = bufnr }) then notify('buffer is not modifiable', vim.log.levels.WARN) return @@ -307,31 +295,27 @@ local function resolve(bufnr, config, side) return end M.replace_region(bufnr, region, M.replacement_lines(bufnr, region, side)) - M.refresh(bufnr, config) + M.refresh(bufnr) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_ours(bufnr, config) - resolve(bufnr, config, 'ours') +function M.resolve_ours(bufnr) + resolve(bufnr, 'ours') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_theirs(bufnr, config) - resolve(bufnr, config, 'theirs') +function M.resolve_theirs(bufnr) + resolve(bufnr, 'theirs') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_both(bufnr, config) - resolve(bufnr, config, 'both') +function M.resolve_both(bufnr) + resolve(bufnr, 'both') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_none(bufnr, config) - resolve(bufnr, config, 'none') +function M.resolve_none(bufnr) + resolve(bufnr, 'none') end ---@param bufnr integer @@ -373,30 +357,10 @@ function M.goto_prev(bufnr) goto_conflict(bufnr, false) end ----@param bufnr integer ----@param config diffs.ConflictConfig -setup_keymaps = function(bufnr, config) - local km = config.keymaps - if km == false then - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) - return - end - - keymaps.set_buffer_keymaps(buffer_keymaps, bufnr, { - { km.ours, '(diffs-conflict-ours)' }, - { km.theirs, '(diffs-conflict-theirs)' }, - { km.both, '(diffs-conflict-both)' }, - { km.none, '(diffs-conflict-none)' }, - { km.next, '(diffs-conflict-next)' }, - { km.prev, '(diffs-conflict-prev)' }, - }) -end - ---@param bufnr integer function M.detach(bufnr) local attached = attached_buffers[bufnr] vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil if attached then @@ -405,8 +369,7 @@ function M.detach(bufnr) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.attach(bufnr, config) +function M.attach(bufnr) if attached_buffers[bufnr] then return end @@ -425,7 +388,6 @@ function M.attach(bufnr, config) attached_buffers[bufnr] = true apply_highlights(bufnr, regions) - setup_keymaps(bufnr, config) vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { buffer = bufnr, @@ -433,14 +395,13 @@ function M.attach(bufnr, config) if not attached_buffers[bufnr] then return true end - M.refresh(bufnr, config) + M.refresh(bufnr) end, }) vim.api.nvim_create_autocmd('BufWipeout', { buffer = bufnr, callback = function() - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) attached_buffers[bufnr] = nil end, }) diff --git a/lua/diffs/merge.lua b/lua/diffs/merge.lua index 5ee4ead5..4e0684bc 100644 --- a/lua/diffs/merge.lua +++ b/lua/diffs/merge.lua @@ -1,16 +1,12 @@ local M = {} local conflict = require('diffs.conflict') -local keymaps = require('diffs.keymaps') local notify = require('diffs.log').notify local rails = require('diffs.rails') ---@type table> local resolved_hunks = {} ----@type table> -local buffer_keymaps = {} - ---@class diffs.MergeHunkInfo ---@field index integer ---@field start_line integer @@ -145,9 +141,8 @@ function M.is_resolved(diff_bufnr, hunk_index) end ---@param diff_bufnr integer ----@param config diffs.ConflictConfig ---@param side 'ours'|'theirs'|'both'|'none' -local function resolve(diff_bufnr, config, side) +local function resolve(diff_bufnr, side) local hunk = M.find_hunk_at_cursor(diff_bufnr) if not hunk then return @@ -170,32 +165,28 @@ local function resolve(diff_bufnr, config, side) region, conflict.replacement_lines(working_bufnr, region, side) ) - conflict.refresh(working_bufnr, config) + conflict.refresh(working_bufnr) mark_resolved(diff_bufnr, hunk.index) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_ours(bufnr, config) - resolve(bufnr, config, 'ours') +function M.resolve_ours(bufnr) + resolve(bufnr, 'ours') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_theirs(bufnr, config) - resolve(bufnr, config, 'theirs') +function M.resolve_theirs(bufnr) + resolve(bufnr, 'theirs') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_both(bufnr, config) - resolve(bufnr, config, 'both') +function M.resolve_both(bufnr) + resolve(bufnr, 'both') end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.resolve_none(bufnr, config) - resolve(bufnr, config, 'none') +function M.resolve_none(bufnr) + resolve(bufnr, 'none') end ---@param bufnr integer @@ -256,28 +247,12 @@ function M.goto_prev(bufnr) end ---@param bufnr integer ----@param config diffs.ConflictConfig -function M.attach(bufnr, config) +function M.attach(bufnr) resolved_hunks[bufnr] = nil - local km = config.keymaps - if km ~= false then - keymaps.set_buffer_keymaps(buffer_keymaps, bufnr, { - { km.ours, '(diffs-merge-ours)' }, - { km.theirs, '(diffs-merge-theirs)' }, - { km.both, '(diffs-merge-both)' }, - { km.none, '(diffs-merge-none)' }, - { km.next, '(diffs-merge-next)' }, - { km.prev, '(diffs-merge-prev)' }, - }) - else - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) - end - vim.api.nvim_create_autocmd('BufWipeout', { buffer = bufnr, callback = function() - keymaps.clear_buffer_keymaps(buffer_keymaps, bufnr) resolved_hunks[bufnr] = nil end, }) diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index 2634b8e2..e277c32a 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -80,14 +80,6 @@ local function init() ) end - if config.conflict and config.conflict.keymaps then - log.notify( - 'conflict.keymaps will be removed on 30 November 2026. ' - .. 'Use vim.keymap.set(). See :help diffs.nvim-conflict-keymaps.', - vim.log.levels.WARN - ) - end - fast_hl_opts = highlight.hunk_opts(config, { highlights = { treesitter = false, @@ -199,7 +191,7 @@ function M.get_telescope_config() return config.integrations.telescope == true end ----@return diffs.ConflictConfig|false +---@return boolean function M.get_conflict_config() init() return config.conflict diff --git a/plugin/diffs.lua b/plugin/diffs.lua index af9803f0..d273a5fa 100644 --- a/plugin/diffs.lua +++ b/plugin/diffs.lua @@ -105,7 +105,7 @@ vim.api.nvim_create_autocmd('BufReadPost', { local runtime = get_runtime() local conflict_config = runtime.get_conflict_config() if conflict_config then - require('diffs.conflict').attach(args.buf, conflict_config) + require('diffs.conflict').attach(args.buf) end end, }) @@ -174,7 +174,7 @@ local function conflict_action(fn) local runtime = get_runtime() local conflict_config = runtime.get_conflict_config() if conflict_config then - fn(bufnr, conflict_config) + fn(bufnr) end end diff --git a/spec/commands_spec.lua b/spec/commands_spec.lua index fb8724dc..7e548f6f 100644 --- a/spec/commands_spec.lua +++ b/spec/commands_spec.lua @@ -2182,9 +2182,7 @@ describe('commands', function() edit_file(filepath) local source_win = vim.api.nvim_get_current_win() mock_runtime_attach(function() end) - mock_conflict_config({ - keymaps = helpers.default_conflict_keymaps(), - }) + mock_conflict_config(true) assert.is_true(git.is_unmerged(filepath)) @@ -2216,8 +2214,8 @@ describe('commands', function() assert.is_true(text:find('--- /dev/null', 1, true) == nil) assert.is_true(text:find('new file mode', 1, true) == nil) assert.is_true(text:find('<<<<<<<', 1, true) == nil) - assert.is_true(helpers.has_keymap(bufnr, 'go')) - assert.is_true(helpers.has_keymap(bufnr, 'gt')) + assert.is_false(helpers.has_keymap(bufnr, 'go')) + assert.is_false(helpers.has_keymap(bufnr, 'gt')) if expected_rail_style then assert.are.equal(expected_rail_style, vim.api.nvim_buf_get_var(bufnr, 'diffs_rail_style')) end diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 21e1febe..fcadd7cc 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -42,16 +42,7 @@ describe('diffs.config', function() committia = true, telescope = true, }, - conflict = { - keymaps = { - ours = 'co', - theirs = 'ct', - both = 'cb', - none = 'c0', - next = ']x', - prev = '[x', - }, - }, + conflict = false, }) end) end) @@ -98,7 +89,6 @@ describe('diffs.config', function() assert.is_nil(opts.hide_prefix) assert.is_nil(opts.highlights.gutter) assert.is_nil(opts.highlights.priorities) - assert.is_nil(opts.conflict.priority) end) it('keeps supported highlights config without removed priorities', function() @@ -109,16 +99,10 @@ describe('diffs.config', function() assert.is_nil(opts.highlights.priorities) end) - it('accepts boolean and table conflict settings', function() - local defaults = config.new().conflict - for _, value in ipairs({ true, {} }) do - assert.are.same(defaults, config.new({ conflict = value }).conflict) - end + it('accepts boolean conflict settings', function() + assert.is_true(config.new().conflict) + assert.is_true(config.new({ conflict = true }).conflict) assert.is_false(config.new({ conflict = false }).conflict) - assert.are.same( - vim.tbl_extend('force', defaults, { keymaps = { ours = 'co' } }), - config.new({ conflict = { keymaps = { ours = 'co' } } }).conflict - ) end) it('accepts integration lists and boolean maps', function() @@ -196,20 +180,6 @@ describe('diffs.config', function() assert.matches('default', err, 1, true) assert.matches('vscode', err, 1, true) end) - - it('validates conflict keymap values', function() - local ok, err = pcall(config.new, { - conflict = { - keymaps = { - ours = true, - }, - }, - }) - - assert.is_false(ok) - assert.matches('conflict.keymaps.ours', err, 1, true) - assert.matches('string or false', err, 1, true) - end) end) describe('compute_filetypes', function() diff --git a/spec/conflict_spec.lua b/spec/conflict_spec.lua index 614c3ac8..4d0a9a88 100644 --- a/spec/conflict_spec.lua +++ b/spec/conflict_spec.lua @@ -1,16 +1,6 @@ local conflict = require('diffs.conflict') local helpers = require('spec.helpers') -local function default_config(overrides) - local cfg = { - keymaps = false, - } - if overrides then - cfg = vim.tbl_deep_extend('force', cfg, overrides) - end - return cfg -end - local function create_file_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, false) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) @@ -174,7 +164,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) @@ -210,7 +200,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local checked = 0 for _, mark in ipairs(get_extmarks(bufnr)) do @@ -234,7 +224,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) assert.is_true(#extmarks > 0) @@ -255,7 +245,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) local has_ours_nr = false @@ -286,7 +276,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local extmarks = get_extmarks(bufnr) local has_base = false @@ -310,7 +300,7 @@ describe('conflict', function() '>>>>>>> feature', }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.is_true(#get_extmarks(bufnr) > 0) conflict.detach(bufnr) @@ -320,147 +310,6 @@ describe('conflict', function() end) end) - describe('keymaps', function() - it('does not set buffer-local keymaps when disabled', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - - conflict.attach(bufnr, default_config()) - - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('removes buffer-local keymaps on detach', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - assert.is_true(helpers.has_keymap(bufnr, 'gt')) - - conflict.detach(bufnr) - - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('removes buffer-local keymaps when all conflicts are resolved', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - assert.is_true(helpers.has_keymap(bufnr, 'gt')) - - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local x = 2' }) - conflict.refresh(bufnr, cfg) - - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('reinstalls buffer-local keymaps when conflicts return', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local x = 2' }) - conflict.refresh(bufnr, cfg) - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - conflict.refresh(bufnr, cfg) - - assert.is_true(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - - it('does not replace pre-existing buffer-local keymaps', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - vim.keymap.set('n', 'gt', '', { buffer = bufnr, desc = 'user theirs' }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - - local theirs = helpers.get_keymap(bufnr, 'gt') - local ours = helpers.get_keymap(bufnr, 'go') - assert.are.equal('user theirs', theirs.desc) - assert.are.equal('(diffs-conflict-ours)', ours.rhs) - - conflict.detach(bufnr) - - theirs = helpers.get_keymap(bufnr, 'gt') - assert.are.equal('user theirs', theirs.desc) - assert.is_false(helpers.has_keymap(bufnr, 'go')) - - helpers.delete_buffer(bufnr) - end) - - it('does not remove a user replacement during cleanup', function() - local bufnr = create_file_buffer({ - '<<<<<<< HEAD', - 'local x = 1', - '=======', - 'local x = 2', - '>>>>>>> feature', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - conflict.attach(bufnr, cfg) - assert.are.equal('(diffs-conflict-ours)', helpers.get_keymap(bufnr, 'go').rhs) - - vim.keymap.set('n', 'go', '', { buffer = bufnr, desc = 'user ours' }) - conflict.detach(bufnr) - - local ours = helpers.get_keymap(bufnr, 'go') - assert.are.equal('user ours', ours.desc) - assert.is_false(helpers.has_keymap(bufnr, 'gt')) - - helpers.delete_buffer(bufnr) - end) - end) - describe('resolution', function() local function make_conflict_buffer() local bufnr = create_file_buffer({ @@ -478,7 +327,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -491,7 +340,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_theirs(bufnr, default_config()) + conflict.resolve_theirs(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -504,7 +353,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_both(bufnr, default_config()) + conflict.resolve_both(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(2, #lines) @@ -518,7 +367,7 @@ describe('conflict', function() local bufnr = make_conflict_buffer() vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_none(bufnr, default_config()) + conflict.resolve_none(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -539,7 +388,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(6, #lines) @@ -564,7 +413,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal('a', lines[1]) @@ -584,7 +433,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -606,7 +455,7 @@ describe('conflict', function() vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_theirs(bufnr, default_config()) + conflict.resolve_theirs(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -780,10 +629,9 @@ describe('conflict', function() 'b', '>>>>>>> feat', }) - local cfg = default_config() - conflict.attach(bufnr, cfg) + conflict.attach(bufnr) local count1 = #get_extmarks(bufnr) - conflict.attach(bufnr, cfg) + conflict.attach(bufnr) local count2 = #get_extmarks(bufnr) assert.are.equal(count1, count2) conflict.detach(bufnr) @@ -800,7 +648,7 @@ describe('conflict', function() }) vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) helpers.delete_buffer(bufnr) @@ -809,7 +657,7 @@ describe('conflict', function() it('skips buffers without conflict markers', function() local bufnr = create_file_buffer({ 'local x = 1', 'local y = 2' }) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) helpers.delete_buffer(bufnr) @@ -823,7 +671,6 @@ describe('conflict', function() 'local x = 2', '>>>>>>> feature', }) - local cfg = default_config() local events = {} local autocmd = vim.api.nvim_create_autocmd('User', { pattern = { 'DiffsConflictDetected', 'DiffsConflictResolved', 'DiffsConflictDetached' }, @@ -833,14 +680,14 @@ describe('conflict', function() end, }) vim.api.nvim_set_current_buf(bufnr) - conflict.attach(bufnr, cfg) + conflict.attach(bufnr) assert.is_true(#get_extmarks(bufnr) > 0) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, cfg) + conflict.resolve_ours(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) - conflict.refresh(bufnr, cfg) + conflict.refresh(bufnr) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '<<<<<<< HEAD', @@ -853,7 +700,7 @@ describe('conflict', function() assert.is_true(#get_extmarks(bufnr) > 0) - conflict.refresh(bufnr, cfg) + conflict.refresh(bufnr) conflict.detach(bufnr) assert.are.same({ 'DiffsConflictDetected', @@ -874,12 +721,12 @@ describe('conflict', function() '>>>>>>> feature', }) vim.api.nvim_set_current_buf(bufnr) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) assert.is_true(#get_extmarks(bufnr) > 0) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) assert.are.equal(0, #get_extmarks(bufnr)) diff --git a/spec/helpers.lua b/spec/helpers.lua index 297d2335..64d78982 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -16,21 +16,6 @@ ensure_parser('vim') local M = {} -function M.default_conflict_keymaps(overrides) - local keymaps = { - ours = 'go', - theirs = 'gt', - both = 'gb', - none = 'g0', - next = ']x', - prev = '[x', - } - if overrides then - keymaps = vim.tbl_extend('force', keymaps, overrides) - end - return keymaps -end - function M.create_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) diff --git a/spec/jj_conflict_spec.lua b/spec/jj_conflict_spec.lua index e35a78cd..b1b60007 100644 --- a/spec/jj_conflict_spec.lua +++ b/spec/jj_conflict_spec.lua @@ -1,15 +1,5 @@ local conflict = require('diffs.conflict') -local function default_config(overrides) - local cfg = { - keymaps = false, - } - if overrides then - cfg = vim.tbl_deep_extend('force', cfg, overrides) - end - return cfg -end - local function create_file_buffer(lines) local bufnr = vim.api.nvim_create_buf(false, false) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) @@ -88,7 +78,7 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - conflict.resolve_ours(bufnr, default_config()) + conflict.resolve_ours(bufnr) assert.are.same({ 'line1', 'AAA', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) @@ -97,7 +87,7 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - conflict.resolve_theirs(bufnr, default_config()) + conflict.resolve_theirs(bufnr) assert.are.same({ 'line1', 'BBB', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) @@ -106,14 +96,14 @@ describe('jj conflict markers', function() local bufnr = create_file_buffer(snapshot_lines()) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 2, 0 }) - conflict.resolve_none(bufnr, default_config()) + conflict.resolve_none(bufnr) assert.are.same({ 'line1', 'line3' }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)) vim.api.nvim_buf_delete(bufnr, { force = true }) end) it('highlights the opening marker', function() local bufnr = create_file_buffer(snapshot_lines()) - conflict.attach(bufnr, default_config()) + conflict.attach(bufnr) local rows = {} for _, mark in ipairs( @@ -144,7 +134,7 @@ describe('jj conflict markers', function() it('does not attach when nothing parses', function() local bufnr = create_file_buffer(diff_lines()) - conflict.attach(bufnr, default_config({ keymaps = { ours = 'gto' } })) + conflict.attach(bufnr) assert.are.equal( 0, #vim.api.nvim_buf_get_extmarks(bufnr, conflict.get_namespace(), 0, -1, {}) diff --git a/spec/merge_spec.lua b/spec/merge_spec.lua index d43f488e..12a5e8d6 100644 --- a/spec/merge_spec.lua +++ b/spec/merge_spec.lua @@ -2,16 +2,6 @@ local helpers = require('spec.helpers') local merge = require('diffs.merge') local rails = require('diffs.rails') -local function default_config(overrides) - local cfg = { - keymaps = false, - } - if overrides then - cfg = vim.tbl_deep_extend('force', cfg, overrides) - end - return cfg -end - local function create_diff_buffer(lines, working_path) local bufnr = helpers.create_buffer(lines) if working_path then @@ -315,7 +305,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -328,7 +318,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_theirs(diff_bufnr, default_config()) + merge.resolve_theirs(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -341,7 +331,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_both(diff_bufnr, default_config()) + merge.resolve_both(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(2, #lines) @@ -355,7 +345,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_none(diff_bufnr, default_config()) + merge.resolve_none(diff_bufnr) local lines = vim.api.nvim_buf_get_lines(working_bufnr, 0, -1, false) assert.are.equal(1, #lines) @@ -369,7 +359,7 @@ describe('merge', function() vim.api.nvim_win_set_cursor(0, { 5, 0 }) assert.is_false(merge.is_resolved(diff_bufnr, 1)) - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) assert.is_true(merge.is_resolved(diff_bufnr, 1)) cleanup() @@ -405,7 +395,7 @@ describe('merge', function() assert.is_not_nil(merge.match_hunk_to_conflict(hunks[1], w_bufnr)) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_theirs(d_bufnr, default_config()) + merge.resolve_theirs(d_bufnr) local lines = vim.api.nvim_buf_get_lines(w_bufnr, 0, -1, false) assert.are.same({ 'local x = 2' }, lines) @@ -418,7 +408,7 @@ describe('merge', function() setup_buffers() vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) local notified = false local orig_notify = vim.notify @@ -428,7 +418,7 @@ describe('merge', function() end end - merge.resolve_ours(diff_bufnr, default_config()) + merge.resolve_ours(diff_bufnr) vim.notify = orig_notify assert.is_true(notified) @@ -461,7 +451,7 @@ describe('merge', function() end end - merge.resolve_ours(d_bufnr, default_config()) + merge.resolve_ours(d_bufnr) vim.notify = orig_notify assert.is_true(notified) @@ -714,7 +704,7 @@ describe('merge', function() vim.api.nvim_set_current_buf(d_bufnr) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - merge.resolve_ours(d_bufnr, default_config()) + merge.resolve_ours(d_bufnr) vim.api.nvim_win_set_cursor(0, { 1, 0 }) merge.goto_next(d_bufnr) @@ -747,8 +737,7 @@ describe('merge', function() vim.api.nvim_set_current_buf(d_bufnr) vim.api.nvim_win_set_cursor(0, { 5, 0 }) - local cfg = default_config() - merge.resolve_ours(d_bufnr, cfg) + merge.resolve_ours(d_bufnr) assert.is_true(merge.is_resolved(d_bufnr, 1)) local ready_bufnr @@ -759,65 +748,13 @@ describe('merge', function() ready_bufnr = event.data.bufnr end, }) - merge.attach(d_bufnr, cfg) + merge.attach(d_bufnr) assert.are.equal(d_bufnr, ready_bufnr) assert.is_false(merge.is_resolved(d_bufnr, 1)) helpers.delete_buffer(d_bufnr) helpers.delete_buffer(w_bufnr) end) - - it('does not replace pre-existing buffer-local keymaps', function() - local d_bufnr = create_diff_buffer({ - 'diff --git a/file.lua b/file.lua', - '--- a/file.lua', - '+++ b/file.lua', - '@@ -1,1 +1,1 @@', - '-local x = 1', - '+local x = 2', - }) - vim.keymap.set('n', 'gt', '', { buffer = d_bufnr, desc = 'user theirs' }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - merge.attach(d_bufnr, cfg) - - local theirs = helpers.get_keymap(d_bufnr, 'gt') - local ours = helpers.get_keymap(d_bufnr, 'go') - assert.are.equal('user theirs', theirs.desc) - assert.are.equal('(diffs-merge-ours)', ours.rhs) - - merge.attach(d_bufnr, default_config()) - - theirs = helpers.get_keymap(d_bufnr, 'gt') - assert.are.equal('user theirs', theirs.desc) - assert.is_false(helpers.has_keymap(d_bufnr, 'go')) - - helpers.delete_buffer(d_bufnr) - end) - - it('does not remove a user replacement during cleanup', function() - local d_bufnr = create_diff_buffer({ - 'diff --git a/file.lua b/file.lua', - '--- a/file.lua', - '+++ b/file.lua', - '@@ -1,1 +1,1 @@', - '-local x = 1', - '+local x = 2', - }) - local cfg = default_config({ keymaps = helpers.default_conflict_keymaps() }) - - merge.attach(d_bufnr, cfg) - assert.are.equal('(diffs-merge-ours)', helpers.get_keymap(d_bufnr, 'go').rhs) - - vim.keymap.set('n', 'go', '', { buffer = d_bufnr, desc = 'user ours' }) - merge.attach(d_bufnr, default_config()) - - local ours = helpers.get_keymap(d_bufnr, 'go') - assert.are.equal('user ours', ours.desc) - assert.is_false(helpers.has_keymap(d_bufnr, 'gt')) - - helpers.delete_buffer(d_bufnr) - end) end) describe('fugitive integration', function() diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 0a7b5001..85303f0c 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -106,7 +106,7 @@ describe('plugin bootstrap', function() 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { keymaps = { ours = "co" } }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = false, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -119,8 +119,6 @@ describe('plugin bootstrap', function() 'runtime.attach(0)', "print('warning=' .. _G.diffs_notifications[1].message)", "print('warning_level=' .. _G.diffs_notifications[1].level)", - "print('keymap_warning=' .. _G.diffs_notifications[2].message)", - "print('keymap_warning_level=' .. _G.diffs_notifications[2].level)", "print('after_attach_notifications=' .. #(_G.diffs_notifications or {}))", 'local runtime_config = runtime._test.get_config()', 'local highlight_opts = runtime.get_highlight_opts()', @@ -132,7 +130,7 @@ describe('plugin bootstrap', function() "print('runtime_committia=' .. tostring(runtime_config.integrations.committia))", "print('runtime_telescope=' .. tostring(runtime_config.integrations.telescope))", "print('runtime_background=' .. tostring(runtime_config.highlights.background))", - "print('runtime_conflict_keymaps=' .. tostring(runtime_config.conflict.keymaps.ours))", + "print('runtime_conflict=' .. tostring(runtime_config.conflict))", "print('runtime_config_priorities=' .. tostring(runtime_config.highlights.priorities))", "print('highlight_priority_clear=' .. tostring(highlight_opts.highlights.priorities.clear))", "print('highlight_priority_syntax=' .. tostring(highlight_opts.highlights.priorities.syntax))", @@ -160,12 +158,9 @@ describe('plugin bootstrap', function() assert.matches('loaded=1', output, 1, true) assert.matches('notifications=0', output, 1, true) - assert.matches('after_attach_notifications=2', output, 1, true) + assert.matches('after_attach_notifications=1', output, 1, true) assert.matches('libvscodediff support will be removed on 30 November 2026.', output, 1, true) assert.matches('Remove highlights.intra.algorithm', output, 1, true) - assert.matches('conflict.keymaps will be removed on 30 November 2026.', output, 1, true) - assert.matches(':help diffs.nvim-conflict-keymaps', output, 1, true) - assert.matches('keymap_warning_level=' .. vim.log.levels.WARN, output, 1, true) assert.matches('warning_level=' .. vim.log.levels.WARN, output, 1, true) assert.matches('runtime_view_prefix=false', output, 1, true) assert.matches('runtime_fugitive=true', output, 1, true) @@ -175,7 +170,7 @@ describe('plugin bootstrap', function() assert.matches('runtime_committia=true', output, 1, true) assert.matches('runtime_telescope=true', output, 1, true) assert.matches('runtime_background=false', output, 1, true) - assert.matches('runtime_conflict_keymaps=co', output, 1, true) + assert.matches('runtime_conflict=false', output, 1, true) assert.matches('runtime_config_priorities=nil', output, 1, true) assert.matches('highlight_priority_clear=198', output, 1, true) assert.matches('highlight_priority_syntax=199', output, 1, true) From adc51ad3754d96f618591743050a129a930309c5 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:34:26 -0600 Subject: [PATCH 12/32] refactor: deprecate the intra-line highlighting toggle --- doc/diffs.nvim.txt | 17 +++++++++++++++-- lua/diffs/config.lua | 7 +++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 79c3fd8d..142530c0 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -94,7 +94,6 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: blend_alpha = 0.6, warn_max_lines = true, vim = true, - intra = true, }, conflict = true, } @@ -198,7 +197,8 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: See |diffs.nvim.VimConfig| for fields. {intra} (boolean|table, default: true) - Character-level (intra-line) diff highlighting. + Boolean values are deprecated. + See |diffs.nvim-intra-toggle|. See |diffs.nvim.IntraConfig| for fields. {overrides} (table, default: {}) @@ -719,6 +719,19 @@ Inline granularity: ~ *diffs.nvim-inline* and `highlights.intra = false` turns the feature off regardless of 'diffopt'. + *diffs.nvim-intra-toggle* + To replace `highlights.intra = false`, remove that setting and disable + intra-line highlighting with |'diffopt'|: >lua + vim.opt.diffopt:remove({ 'inline:simple', 'inline:char', 'inline:word' }) + vim.opt.diffopt:append('inline:none') +< + To enable word-level highlighting: >lua + vim.opt.diffopt:remove({ 'inline:none', 'inline:simple', 'inline:char' }) + vim.opt.diffopt:append('inline:word') +< + Remove `highlights.intra = true` to use the defaults. The table form + remains available for `max_lines` and `algorithm`. + Limitations: ~ - `inline:word` is produced by growing character-level spans to the word boundaries they touch, not by re-diffing the line word by word. The diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 6e233ee3..76c4e87c 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -181,6 +181,13 @@ function M.new(opts) integrations = integration_metadata.normalize(opts and opts.integrations), }) M.validate(opts) + if opts.highlights and type(opts.highlights.intra) == 'boolean' then + vim.notify_once( + '[diffs]: Boolean highlights.intra is deprecated. Support ends on November 30, 2026. ' + .. 'See :help diffs.nvim-intra-toggle.', + vim.log.levels.WARN + ) + end local result = vim.tbl_deep_extend('force', DEFAULTS, opts) if result.conflict == true then result.conflict = vim.deepcopy(DEFAULTS.conflict) From c574861c9a793b97054bf7925aca16447a767a05 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:34:41 -0600 Subject: [PATCH 13/32] fix: respect inline:none for difftastic token highlights --- lua/diffs/difftastic.lua | 4 +++- lua/diffs/split.lua | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lua/diffs/difftastic.lua b/lua/diffs/difftastic.lua index 3d22480a..aea4c4a2 100644 --- a/lua/diffs/difftastic.lua +++ b/lua/diffs/difftastic.lua @@ -1,5 +1,7 @@ local M = {} +local diffopt = require('diffs.diffopt') + local hunk_model = require('diffs.hunks') local log = require('diffs.log') local runtime = require('diffs.runtime') @@ -353,7 +355,7 @@ function M.apply_unified(bufnr, lhs, rhs, diff_lines, spec, rail_width) vim.api.nvim_buf_clear_namespace(bufnr, unified_ns, 0, -1) M.mark_active(bufnr) - if not intra_cfg then + if not intra_cfg or diffopt.inline() == 'none' then return 0 end diff --git a/lua/diffs/split.lua b/lua/diffs/split.lua index 4248999a..995104dd 100644 --- a/lua/diffs/split.lua +++ b/lua/diffs/split.lua @@ -1,5 +1,7 @@ local M = {} +local diffopt = require('diffs.diffopt') + local diff = require('diffs.diff') local diffspec = require('diffs.spec') local difftastic = require('diffs.difftastic') @@ -256,7 +258,7 @@ end local function set_split_intra_difft(bufnr, info) local opts = require('diffs.runtime').get_highlight_opts() local intra_cfg = opts.highlights.intra - if not intra_cfg then + if not intra_cfg or diffopt.inline() == 'none' then return end local cfg = side_config[info.side] @@ -289,7 +291,7 @@ local function set_split_intra(bufnr, info, hunks) local opts = require('diffs.runtime').get_highlight_opts() local intra_cfg = opts.highlights.intra - if not intra_cfg then + if not intra_cfg or diffopt.inline() == 'none' then return end From 0ce6ec770f3a539d100ec92b55f54410387050b8 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:35:21 -0600 Subject: [PATCH 14/32] docs: describe inline settings for difftastic views --- doc/diffs.nvim.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 142530c0..dcf469e0 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -737,8 +737,9 @@ Inline granularity: ~ *diffs.nvim-inline* boundaries they touch, not by re-diffing the line word by word. The result matches Neovim's own `inline:word` rendering on ordinary source lines, but the two are not guaranteed identical in every case. - - difftastic-rendered diffs keep their structural token spans; `inline:` - does not apply to them (see |diffs.nvim-difftastic|). + - `inline:none` hides difftastic token highlights. Other `inline:` modes + preserve its structural spans. Run `:Diff refresh` after changing this + setting in an open difftastic view (see |diffs.nvim-difftastic|). ============================================================================== MAPPINGS *diffs.nvim-mappings* From 50a15bde983bbf04c3a91ce8e7e76f2f71c5425a Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:37:14 -0600 Subject: [PATCH 15/32] refactor!: remove the intra-line configuration toggle --- doc/diffs.nvim.txt | 16 +++------------- lua/diffs/config.lua | 17 ++++------------- lua/diffs/difftastic.lua | 3 +-- lua/diffs/highlight.lua | 1 + lua/diffs/split.lua | 5 ++--- spec/commands_spec.lua | 37 ------------------------------------- spec/config_spec.lua | 2 +- 7 files changed, 12 insertions(+), 69 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index dcf469e0..ed410763 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -196,9 +196,8 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Vim syntax fallback highlighting options. See |diffs.nvim.VimConfig| for fields. - {intra} (boolean|table, default: true) - Boolean values are deprecated. - See |diffs.nvim-intra-toggle|. + {intra} (table, default: see below) + Intra-line diff highlighting options. See |diffs.nvim.IntraConfig| for fields. {overrides} (table, default: {}) @@ -714,14 +713,8 @@ Inline granularity: ~ *diffs.nvim-inline* above, so a plain `:set`, an unimpaired-style toggle, or an autocmd all work. There is no dedicated mapping. - `highlights.intra` stays orthogonal: `inline:` chooses the granularity, - `highlights.intra.algorithm` chooses the engine that finds the differences, - and `highlights.intra = false` turns the feature off regardless of - 'diffopt'. - *diffs.nvim-intra-toggle* - To replace `highlights.intra = false`, remove that setting and disable - intra-line highlighting with |'diffopt'|: >lua + Disable intra-line highlighting with |'diffopt'|: >lua vim.opt.diffopt:remove({ 'inline:simple', 'inline:char', 'inline:word' }) vim.opt.diffopt:append('inline:none') < @@ -729,9 +722,6 @@ Inline granularity: ~ *diffs.nvim-inline* vim.opt.diffopt:remove({ 'inline:none', 'inline:simple', 'inline:char' }) vim.opt.diffopt:append('inline:word') < - Remove `highlights.intra = true` to use the defaults. The table form - remains available for `max_lines` and `algorithm`. - Limitations: ~ - `inline:word` is produced by growing character-level spans to the word boundaries they touch, not by re-diffing the line word by word. The diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 76c4e87c..5a7d4632 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -23,7 +23,7 @@ local integration_metadata = require('diffs.integrations') ---@field warn_max_lines boolean ---@field treesitter diffs.TreesitterConfig ---@field vim diffs.VimConfig|false ----@field intra diffs.IntraConfig|false +---@field intra diffs.IntraConfig ---@class diffs.ConflictKeymaps ---@field ours string|false @@ -125,7 +125,7 @@ function M.validate(opts) for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do local path = 'highlights.' .. key local value = opts.highlights[key] - local types = key == 'treesitter' and 'table' or { 'boolean', 'table' } + local types = key == 'vim' and { 'boolean', 'table' } or 'table' vim.validate(path, value, types, true) if type(value) == 'table' then vim.validate(path .. '.max_lines', value.max_lines, 'number', true) @@ -181,21 +181,12 @@ function M.new(opts) integrations = integration_metadata.normalize(opts and opts.integrations), }) M.validate(opts) - if opts.highlights and type(opts.highlights.intra) == 'boolean' then - vim.notify_once( - '[diffs]: Boolean highlights.intra is deprecated. Support ends on November 30, 2026. ' - .. 'See :help diffs.nvim-intra-toggle.', - vim.log.levels.WARN - ) - end local result = vim.tbl_deep_extend('force', DEFAULTS, opts) if result.conflict == true then result.conflict = vim.deepcopy(DEFAULTS.conflict) end - for _, key in ipairs({ 'vim', 'intra' }) do - if result.highlights[key] == true then - result.highlights[key] = vim.deepcopy(DEFAULTS.highlights[key]) - end + if result.highlights.vim == true then + result.highlights.vim = vim.deepcopy(DEFAULTS.highlights.vim) end return result end diff --git a/lua/diffs/difftastic.lua b/lua/diffs/difftastic.lua index aea4c4a2..5570ac61 100644 --- a/lua/diffs/difftastic.lua +++ b/lua/diffs/difftastic.lua @@ -348,14 +348,13 @@ end ---@return integer applied span count function M.apply_unified(bufnr, lhs, rhs, diff_lines, spec, rail_width) local opts = runtime.get_highlight_opts() - local intra_cfg = opts.highlights.intra local priority = opts.highlights.priorities.char_bg local base = rail_width + 1 vim.api.nvim_buf_clear_namespace(bufnr, unified_ns, 0, -1) M.mark_active(bufnr) - if not intra_cfg or diffopt.inline() == 'none' then + if diffopt.inline() == 'none' then return 0 end diff --git a/lua/diffs/highlight.lua b/lua/diffs/highlight.lua index f424972f..03da0a24 100644 --- a/lua/diffs/highlight.lua +++ b/lua/diffs/highlight.lua @@ -37,6 +37,7 @@ local vim_syntax_cache_limit = 128 ---@class diffs.HunkHighlights: diffs.Highlights ---@field treesitter diffs.TreesitterConfig|false +---@field intra diffs.IntraConfig|false ---@field priorities diffs.HighlightPriorities ---@type diffs.HighlightPriorities diff --git a/lua/diffs/split.lua b/lua/diffs/split.lua index 995104dd..e3021f1b 100644 --- a/lua/diffs/split.lua +++ b/lua/diffs/split.lua @@ -257,8 +257,7 @@ end ---@param info diffs.SplitPaneInfo local function set_split_intra_difft(bufnr, info) local opts = require('diffs.runtime').get_highlight_opts() - local intra_cfg = opts.highlights.intra - if not intra_cfg or diffopt.inline() == 'none' then + if diffopt.inline() == 'none' then return end local cfg = side_config[info.side] @@ -291,7 +290,7 @@ local function set_split_intra(bufnr, info, hunks) local opts = require('diffs.runtime').get_highlight_opts() local intra_cfg = opts.highlights.intra - if not intra_cfg or diffopt.inline() == 'none' then + if diffopt.inline() == 'none' then return end diff --git a/spec/commands_spec.lua b/spec/commands_spec.lua index fb8724dc..e26990a4 100644 --- a/spec/commands_spec.lua +++ b/spec/commands_spec.lua @@ -12,7 +12,6 @@ local saved_git = {} local saved_runtime_attach local saved_runtime_get_conflict_config local saved_runtime_get_view_config -local saved_runtime_get_highlight_opts local saved_schedule local saved_systemlist local saved_jobstart @@ -84,16 +83,6 @@ local function mock_view_config(config) end end -local function mock_highlight_opts(mutate) - saved_runtime_get_highlight_opts = runtime.get_highlight_opts - local base = saved_runtime_get_highlight_opts() - runtime.get_highlight_opts = function() - local opts = vim.deepcopy(base) - mutate(opts) - return opts - end -end - local function capture_notifications() local notifications = {} saved_notify = vim.notify @@ -403,10 +392,6 @@ local function restore_mocks() runtime.get_view_config = saved_runtime_get_view_config saved_runtime_get_view_config = nil end - if saved_runtime_get_highlight_opts then - runtime.get_highlight_opts = saved_runtime_get_highlight_opts - saved_runtime_get_highlight_opts = nil - end if saved_schedule then vim.schedule = saved_schedule saved_schedule = nil @@ -1223,28 +1208,6 @@ describe('commands', function() ) end) - it('skips the intra overlay when highlights.intra is disabled', function() - mock_view_config({ prefix = true }) - mock_highlight_opts(function(opts) - opts.highlights.intra = false - end) - create_split_source({ - index_lines = { 'function f()', ' return 111', ' log()', 'end' }, - worktree_lines = { 'function f()', ' return 222', ' log()', 'end' }, - }) - commands.diff('++layout=split', false) - - local right_buf = vim.api.nvim_get_current_buf() - local left_buf = vim.api.nvim_buf_get_var(right_buf, 'diffs_split_peer') - table.insert(test_buffers, left_buf) - table.insert(test_buffers, right_buf) - - local intra_ns = vim.api.nvim_get_namespaces().diffs_split_intra - assert.is_not_nil(intra_ns) - assert.are.equal(0, #vim.api.nvim_buf_get_extmarks(left_buf, intra_ns, 0, -1, {})) - assert.are.equal(0, #vim.api.nvim_buf_get_extmarks(right_buf, intra_ns, 0, -1, {})) - end) - it('leaves split panes untouched when attach_diff re-runs', function() mock_view_config({ prefix = true }) create_split_source() diff --git a/spec/config_spec.lua b/spec/config_spec.lua index cada9d2c..2e839d48 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -72,7 +72,7 @@ describe('diffs.config', function() end) it('accepts boolean and table highlight settings', function() - for _, key in ipairs({ 'vim', 'intra' }) do + for _, key in ipairs({ 'vim' }) do local defaults = config.new().highlights[key] for _, value in ipairs({ true, {} }) do assert.are.same(defaults, config.new({ highlights = { [key] = value } }).highlights[key]) From a98219c291fcd7bc1fc59c8fd46298f256f6c762 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:39:46 -0600 Subject: [PATCH 16/32] docs: add a FileType recipe for intra-line highlighting --- doc/diffs.nvim.txt | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index dcf469e0..62ccc66a 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -725,22 +725,21 @@ Inline granularity: ~ *diffs.nvim-inline* vim.opt.diffopt:remove({ 'inline:simple', 'inline:char', 'inline:word' }) vim.opt.diffopt:append('inline:none') < - To enable word-level highlighting: >lua - vim.opt.diffopt:remove({ 'inline:none', 'inline:simple', 'inline:char' }) - vim.opt.diffopt:append('inline:word') + To enable word-level highlighting when opening a diff buffer: >lua + vim.api.nvim_create_autocmd('FileType', { + pattern = { 'diff', 'git', 'gitcommit' }, + callback = function() + vim.opt.diffopt:remove({ 'inline:none', 'inline:simple', 'inline:char' }) + vim.opt.diffopt:append('inline:word') + end, + }) < + 'diffopt' is global: this changes all diff views. For attachment to `diff` + buffers, see |diffs.nvim-extra-filetypes|. + Remove `highlights.intra = true` to use the defaults. The table form remains available for `max_lines` and `algorithm`. - Limitations: ~ - - `inline:word` is produced by growing character-level spans to the word - boundaries they touch, not by re-diffing the line word by word. The - result matches Neovim's own `inline:word` rendering on ordinary source - lines, but the two are not guaranteed identical in every case. - - `inline:none` hides difftastic token highlights. Other `inline:` modes - preserve its structural spans. Run `:Diff refresh` after changing this - setting in an open difftastic view (see |diffs.nvim-difftastic|). - ============================================================================== MAPPINGS *diffs.nvim-mappings* @@ -1081,6 +1080,8 @@ emits an informational message and renders no token highlights. The 'diffopt' whitespace handling does not apply to a structural diff; such buffers are left unchanged. +Run `:Diff refresh` after changing `inline:` in an open difftastic view. + Run |:checkhealth| diffs to confirm `difft` is detected. ============================================================================== From a5a4b5dbcb15be7e74534d807544072ab86f140e Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:43:21 -0600 Subject: [PATCH 17/32] docs: correct the intra-line migration instructions --- doc/diffs.nvim.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 62ccc66a..58cb2b7b 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -737,9 +737,6 @@ Inline granularity: ~ *diffs.nvim-inline* 'diffopt' is global: this changes all diff views. For attachment to `diff` buffers, see |diffs.nvim-extra-filetypes|. - Remove `highlights.intra = true` to use the defaults. The table form - remains available for `max_lines` and `algorithm`. - ============================================================================== MAPPINGS *diffs.nvim-mappings* @@ -1080,7 +1077,7 @@ emits an informational message and renders no token highlights. The 'diffopt' whitespace handling does not apply to a structural diff; such buffers are left unchanged. -Run `:Diff refresh` after changing `inline:` in an open difftastic view. +Use |(diffs-refresh)| after changing `inline:` in an open difftastic view. Run |:checkhealth| diffs to confirm `difft` is detected. From 6663da9b83fa81d9e4ae6301e23c8e048801d505 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:44:07 -0600 Subject: [PATCH 18/32] docs: describe intra-line highlighting through diffopt --- doc/diffs.nvim.txt | 12 ++++-------- lua/diffs/config.lua | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 58cb2b7b..6f2f9756 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -198,7 +198,7 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: {intra} (boolean|table, default: true) Boolean values are deprecated. - See |diffs.nvim-intra-toggle|. + See |diffs.nvim-diffopt|. See |diffs.nvim.IntraConfig| for fields. {overrides} (table, default: {}) @@ -714,14 +714,10 @@ Inline granularity: ~ *diffs.nvim-inline* above, so a plain `:set`, an unimpaired-style toggle, or an autocmd all work. There is no dedicated mapping. - `highlights.intra` stays orthogonal: `inline:` chooses the granularity, - `highlights.intra.algorithm` chooses the engine that finds the differences, - and `highlights.intra = false` turns the feature off regardless of - 'diffopt'. + *diffs.nvim-diffopt* + Intra-line highlighting is controlled by |'diffopt'|. - *diffs.nvim-intra-toggle* - To replace `highlights.intra = false`, remove that setting and disable - intra-line highlighting with |'diffopt'|: >lua + Disable intra-line highlighting: >lua vim.opt.diffopt:remove({ 'inline:simple', 'inline:char', 'inline:word' }) vim.opt.diffopt:append('inline:none') < diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 76c4e87c..a1ad9fd2 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -184,7 +184,7 @@ function M.new(opts) if opts.highlights and type(opts.highlights.intra) == 'boolean' then vim.notify_once( '[diffs]: Boolean highlights.intra is deprecated. Support ends on November 30, 2026. ' - .. 'See :help diffs.nvim-intra-toggle.', + .. 'See :help diffs.nvim-diffopt.', vim.log.levels.WARN ) end From 6b53c89f489f9b047d9f664232eedeff628c5bd0 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:44:42 -0600 Subject: [PATCH 19/32] fix: resolve the diffopt documentation merge --- doc/diffs.nvim.txt | 11 ----------- lua/diffs/config.lua | 10 ---------- 2 files changed, 21 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index eb597de9..5734b6d9 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -196,14 +196,8 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Vim syntax fallback highlighting options. See |diffs.nvim.VimConfig| for fields. -<<<<<<< HEAD {intra} (table, default: see below) Intra-line diff highlighting options. -======= - {intra} (boolean|table, default: true) - Boolean values are deprecated. - See |diffs.nvim-diffopt|. ->>>>>>> refactor/deprecate-intra-toggle See |diffs.nvim.IntraConfig| for fields. {overrides} (table, default: {}) @@ -719,15 +713,10 @@ Inline granularity: ~ *diffs.nvim-inline* above, so a plain `:set`, an unimpaired-style toggle, or an autocmd all work. There is no dedicated mapping. -<<<<<<< HEAD - *diffs.nvim-intra-toggle* - Disable intra-line highlighting with |'diffopt'|: >lua -======= *diffs.nvim-diffopt* Intra-line highlighting is controlled by |'diffopt'|. Disable intra-line highlighting: >lua ->>>>>>> refactor/deprecate-intra-toggle vim.opt.diffopt:remove({ 'inline:simple', 'inline:char', 'inline:word' }) vim.opt.diffopt:append('inline:none') < diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 8c97e5ed..5a7d4632 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -181,16 +181,6 @@ function M.new(opts) integrations = integration_metadata.normalize(opts and opts.integrations), }) M.validate(opts) -<<<<<<< HEAD -======= - if opts.highlights and type(opts.highlights.intra) == 'boolean' then - vim.notify_once( - '[diffs]: Boolean highlights.intra is deprecated. Support ends on November 30, 2026. ' - .. 'See :help diffs.nvim-diffopt.', - vim.log.levels.WARN - ) - end ->>>>>>> refactor/deprecate-intra-toggle local result = vim.tbl_deep_extend('force', DEFAULTS, opts) if result.conflict == true then result.conflict = vim.deepcopy(DEFAULTS.conflict) From c0d5e4f77539950b343fc403f4b2c9314d11e39f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 14:51:38 -0600 Subject: [PATCH 20/32] fix: refresh Difftastic highlights when diffopt changes --- doc/diffs.nvim.txt | 2 -- lua/diffs/commands.lua | 34 +++++++++++++++++++++++----------- lua/diffs/render.lua | 4 ++-- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 6f2f9756..b8079283 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -1073,8 +1073,6 @@ emits an informational message and renders no token highlights. The 'diffopt' whitespace handling does not apply to a structural diff; such buffers are left unchanged. -Use |(diffs-refresh)| after changing `inline:` in an open difftastic view. - Run |:checkhealth| diffs to confirm `difft` is detected. ============================================================================== diff --git a/lua/diffs/commands.lua b/lua/diffs/commands.lua index b54b9ab8..79f6d440 100644 --- a/lua/diffs/commands.lua +++ b/lua/diffs/commands.lua @@ -604,16 +604,16 @@ local function files_source_new_lines(source) end ---@param source diffs.GeneratedBufferSource ----@return string[]?, diffs.DiffSpec?, string?, table? +---@return string[]?, diffs.DiffSpec?, string?, table?, diffs.ContentLines|string[]?, diffs.ContentLines|string[]? local function render_source(source) if source.kind == 'file' then - local diff_lines, read_err = render.file(source.spec, source.repo_root, { + local diff_lines, read_err, old_lines, new_lines = render.file(source.spec, source.repo_root, { empty_on_missing = true, }) if not diff_lines then return nil, nil, read_err, nil end - return diff_lines, source.spec, diffspec.label(source.spec), nil + return diff_lines, source.spec, diffspec.label(source.spec), nil, old_lines, new_lines end if source.kind == 'files' then @@ -628,7 +628,9 @@ local function render_source(source) return render.unified_lines(old_lines, new_lines, source.left_name, source.right_name), nil, 'files:' .. source.left_name .. ' -> ' .. files_side_display(source.right_name, buffered), - nil + nil, + old_lines, + new_lines end if source.kind == 'file_pair' then @@ -648,7 +650,9 @@ local function render_source(source) return render.unified_lines(old_lines, new_lines, source.old_path, source.path), nil, source.edge .. ':' .. source.path, - nil + nil, + old_lines, + new_lines end if source.kind == 'section' then @@ -672,7 +676,9 @@ local function render_source(source) return render.unified_lines(old_lines, new_lines, source.path, source.path), nil, 'unmerged:' .. source.path, - nil + nil, + old_lines, + new_lines end ---@param diff_label string @@ -2347,6 +2353,7 @@ function M.read_buffer(bufnr, opts) end local diff_lines + local old_lines, new_lines local stored_spec local debug_label local list_opts @@ -2354,7 +2361,7 @@ function M.read_buffer(bufnr, opts) if source then local render_err - diff_lines, stored_spec, render_err, list_opts = render_source(source) + diff_lines, stored_spec, render_err, list_opts, old_lines, new_lines = render_source(source) if not diff_lines then notify(render_err or 'cannot reload diffs:// buffer', vim.log.levels.WARN) return @@ -2375,7 +2382,8 @@ function M.read_buffer(bufnr, opts) if stored_spec then local read_err - diff_lines, read_err = render.file(stored_spec, repo_root, { empty_on_missing = true }) + diff_lines, read_err, old_lines, new_lines = + render.file(stored_spec, repo_root, { empty_on_missing = true }) if not diff_lines then notify(read_err, vim.log.levels.WARN) return @@ -2406,8 +2414,6 @@ function M.read_buffer(bufnr, opts) or abs_path local old_name = old_ok and old_rel_path or path - local old_lines, new_lines - if label == 'unmerged' then old_lines = git.get_file_content(':2', abs_path) or {} new_lines = git.get_file_content(':3', abs_path) or {} @@ -2457,6 +2463,12 @@ function M.read_buffer(bufnr, opts) dbg('reloaded diff buffer %d (%s)', bufnr, debug_label) runtime.attach(bufnr) + local relpath = stored_spec and stored_spec.scope.path + or (source and (source.right_name or source.path)) + or path + if relpath then + apply_difft_unified(bufnr, diff_lines, stored_spec, old_lines, new_lines, relpath) + end end --- Re-render every visible diffs:// buffer in place, preserving each window's @@ -2476,7 +2488,7 @@ function M.refresh_visible() local done = {} for _, entry in ipairs(views) do local buf = vim.api.nvim_win_is_valid(entry.win) and vim.api.nvim_win_get_buf(entry.win) - if buf and not done[buf] and not difftastic.is_active(buf) then + if buf and not done[buf] then done[buf] = true local ok, peer = pcall(vim.api.nvim_buf_get_var, buf, 'diffs_split_peer') if ok and type(peer) == 'number' then diff --git a/lua/diffs/render.lua b/lua/diffs/render.lua index 34a26810..67931a38 100644 --- a/lua/diffs/render.lua +++ b/lua/diffs/render.lua @@ -265,7 +265,7 @@ end ---@param diff_spec diffs.DiffSpec ---@param repo_root string ---@param opts? diffs.RenderFileOpts ----@return string[]?, string? +---@return string[]?, string?, diffs.ContentLines|string[]?, diffs.ContentLines|string[]? function M.file(diff_spec, repo_root, opts) diff_spec = diffspec.new(diff_spec) opts = opts or {} @@ -330,7 +330,7 @@ function M.file(diff_spec, repo_root, opts) return nil, 'diff does not support mode-only changes' end - return diff_lines, nil + return diff_lines, nil, old_lines, new_lines end return M From f4d19d5d880d9fd627e8ffdd23e8385e88f4bac5 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:07:43 -0600 Subject: [PATCH 21/32] fix: respect deprecated conflict.enabled configuration --- lua/diffs/config.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index a1ad9fd2..374d1e64 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -34,6 +34,7 @@ local integration_metadata = require('diffs.integrations') ---@field prev string|false ---@class diffs.ConflictConfig +---@field enabled? boolean Deprecated. ---@field keymaps diffs.ConflictKeymaps|false ---@class diffs.DifftasticConfig @@ -146,6 +147,7 @@ function M.validate(opts) return v == nil or type(v) == 'boolean' or type(v) == 'table' end, 'boolean or table') if type(opts.conflict) == 'table' then + vim.validate('conflict.enabled', opts.conflict.enabled, 'boolean', true) vim.validate('conflict.keymaps', opts.conflict.keymaps, function(v) return v == nil or v == false or type(v) == 'table' end, 'table or false') @@ -189,6 +191,18 @@ function M.new(opts) ) end local result = vim.tbl_deep_extend('force', DEFAULTS, opts) + if type(result.conflict) == 'table' and result.conflict.enabled ~= nil then + vim.notify_once( + '[diffs]: conflict.enabled is deprecated. Support ends on November 30, 2026. ' + .. 'Use conflict = false to disable conflict handling. Otherwise remove enabled.', + vim.log.levels.WARN + ) + if result.conflict.enabled == false then + result.conflict = false + else + result.conflict.enabled = nil + end + end if result.conflict == true then result.conflict = vim.deepcopy(DEFAULTS.conflict) end From 01ea25cbbc80baa24be7991f42a9f77c0b06b3e3 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:08:32 -0600 Subject: [PATCH 22/32] refactor!: remove deprecated conflict.enabled support --- lua/diffs/config.lua | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 374d1e64..34f5810a 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -34,7 +34,6 @@ local integration_metadata = require('diffs.integrations') ---@field prev string|false ---@class diffs.ConflictConfig ----@field enabled? boolean Deprecated. ---@field keymaps diffs.ConflictKeymaps|false ---@class diffs.DifftasticConfig @@ -147,7 +146,7 @@ function M.validate(opts) return v == nil or type(v) == 'boolean' or type(v) == 'table' end, 'boolean or table') if type(opts.conflict) == 'table' then - vim.validate('conflict.enabled', opts.conflict.enabled, 'boolean', true) + vim.validate('conflict.enabled', opts.conflict.enabled, 'nil') vim.validate('conflict.keymaps', opts.conflict.keymaps, function(v) return v == nil or v == false or type(v) == 'table' end, 'table or false') @@ -191,18 +190,6 @@ function M.new(opts) ) end local result = vim.tbl_deep_extend('force', DEFAULTS, opts) - if type(result.conflict) == 'table' and result.conflict.enabled ~= nil then - vim.notify_once( - '[diffs]: conflict.enabled is deprecated. Support ends on November 30, 2026. ' - .. 'Use conflict = false to disable conflict handling. Otherwise remove enabled.', - vim.log.levels.WARN - ) - if result.conflict.enabled == false then - result.conflict = false - else - result.conflict.enabled = nil - end - end if result.conflict == true then result.conflict = vim.deepcopy(DEFAULTS.conflict) end From 63264255cdb7d200de39d0aaf26939d084ba460e Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:15:07 -0600 Subject: [PATCH 23/32] fix: migrate legacy highlight settings --- lua/diffs/config.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 374d1e64..0ac2b6a4 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -129,6 +129,7 @@ function M.validate(opts) local types = key == 'treesitter' and 'table' or { 'boolean', 'table' } vim.validate(path, value, types, true) if type(value) == 'table' then + vim.validate(path .. '.enabled', value.enabled, 'boolean', true) vim.validate(path .. '.max_lines', value.max_lines, 'number', true) if value.max_lines and value.max_lines < 1 then error('diffs: ' .. path .. '.max_lines must be >= 1') @@ -190,7 +191,34 @@ function M.new(opts) vim.log.levels.WARN ) end + if opts.highlights and opts.highlights.context ~= nil then + vim.notify_once( + '[diffs]: highlights.context was removed and is ignored. Remove it from the configuration.', + vim.log.levels.WARN + ) + end local result = vim.tbl_deep_extend('force', DEFAULTS, opts) + for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do + local value = result.highlights[key] + if type(value) == 'table' and value.enabled ~= nil then + local migration = key == 'treesitter' + and 'The enabled field is ignored. Tree-sitter highlighting is always enabled. Remove enabled.' + or key == 'intra' and 'Remove enabled and use diffopt. See :help diffs.nvim-diffopt.' + or 'Use highlights.vim = false to disable Vim syntax. Otherwise remove enabled.' + vim.notify_once( + '[diffs]: highlights.' + .. key + .. '.enabled is deprecated. Support ends on November 30, 2026. ' + .. migration, + vim.log.levels.WARN + ) + if key ~= 'treesitter' and value.enabled == false then + result.highlights[key] = false + else + value.enabled = nil + end + end + end if type(result.conflict) == 'table' and result.conflict.enabled ~= nil then vim.notify_once( '[diffs]: conflict.enabled is deprecated. Support ends on November 30, 2026. ' From d3def3ec78cdd22da433675b27746f7fd0cf00a5 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:15:51 -0600 Subject: [PATCH 24/32] refactor!: remove legacy highlight migration support --- lua/diffs/config.lua | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 0ac2b6a4..bfc43d6e 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -129,7 +129,7 @@ function M.validate(opts) local types = key == 'treesitter' and 'table' or { 'boolean', 'table' } vim.validate(path, value, types, true) if type(value) == 'table' then - vim.validate(path .. '.enabled', value.enabled, 'boolean', true) + vim.validate(path .. '.enabled', value.enabled, 'nil') vim.validate(path .. '.max_lines', value.max_lines, 'number', true) if value.max_lines and value.max_lines < 1 then error('diffs: ' .. path .. '.max_lines must be >= 1') @@ -191,34 +191,7 @@ function M.new(opts) vim.log.levels.WARN ) end - if opts.highlights and opts.highlights.context ~= nil then - vim.notify_once( - '[diffs]: highlights.context was removed and is ignored. Remove it from the configuration.', - vim.log.levels.WARN - ) - end local result = vim.tbl_deep_extend('force', DEFAULTS, opts) - for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do - local value = result.highlights[key] - if type(value) == 'table' and value.enabled ~= nil then - local migration = key == 'treesitter' - and 'The enabled field is ignored. Tree-sitter highlighting is always enabled. Remove enabled.' - or key == 'intra' and 'Remove enabled and use diffopt. See :help diffs.nvim-diffopt.' - or 'Use highlights.vim = false to disable Vim syntax. Otherwise remove enabled.' - vim.notify_once( - '[diffs]: highlights.' - .. key - .. '.enabled is deprecated. Support ends on November 30, 2026. ' - .. migration, - vim.log.levels.WARN - ) - if key ~= 'treesitter' and value.enabled == false then - result.highlights[key] = false - else - value.enabled = nil - end - end - end if type(result.conflict) == 'table' and result.conflict.enabled ~= nil then vim.notify_once( '[diffs]: conflict.enabled is deprecated. Support ends on November 30, 2026. ' From 788f5207c44cc858a3ff06450b950cf7d6fd2ee0 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:21:02 -0600 Subject: [PATCH 25/32] fix: warn about ignored conflict and view settings --- lua/diffs/config.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 0ac2b6a4..6cb57737 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -184,6 +184,25 @@ function M.new(opts) integrations = integration_metadata.normalize(opts and opts.integrations), }) M.validate(opts) + for group, keys in pairs({ + conflict = { 'disable_diagnostics', 'show_virtual_text', 'format_virtual_text', 'show_actions' }, + view = { 'change_bar', 'rail_separator' }, + }) do + if type(opts[group]) == 'table' then + for _, key in ipairs(keys) do + if opts[group][key] ~= nil then + vim.notify_once( + '[diffs]: ' + .. group + .. '.' + .. key + .. ' was removed and is ignored. Remove it from the configuration.', + vim.log.levels.WARN + ) + end + end + end + end if opts.highlights and type(opts.highlights.intra) == 'boolean' then vim.notify_once( '[diffs]: Boolean highlights.intra is deprecated. Support ends on November 30, 2026. ' From 21c35867eb407d263da063a66d0b506b7c7f6e4a Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:21:52 -0600 Subject: [PATCH 26/32] refactor: remove obsolete conflict and view option warnings --- lua/diffs/config.lua | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 6cb57737..0ac2b6a4 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -184,25 +184,6 @@ function M.new(opts) integrations = integration_metadata.normalize(opts and opts.integrations), }) M.validate(opts) - for group, keys in pairs({ - conflict = { 'disable_diagnostics', 'show_virtual_text', 'format_virtual_text', 'show_actions' }, - view = { 'change_bar', 'rail_separator' }, - }) do - if type(opts[group]) == 'table' then - for _, key in ipairs(keys) do - if opts[group][key] ~= nil then - vim.notify_once( - '[diffs]: ' - .. group - .. '.' - .. key - .. ' was removed and is ignored. Remove it from the configuration.', - vim.log.levels.WARN - ) - end - end - end - end if opts.highlights and type(opts.highlights.intra) == 'boolean' then vim.notify_once( '[diffs]: Boolean highlights.intra is deprecated. Support ends on November 30, 2026. ' From e1a155b054544a016eeb327739a05a0ed7afc515 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 15:41:18 -0600 Subject: [PATCH 27/32] fix: reconcile runtime configuration after removal merges --- lua/diffs/conflict.lua | 1 - lua/diffs/runtime/init.lua | 24 ------------------------ spec/config_spec.lua | 3 --- spec/plugin_spec.lua | 14 ++------------ 4 files changed, 2 insertions(+), 40 deletions(-) diff --git a/lua/diffs/conflict.lua b/lua/diffs/conflict.lua index d03a2ff9..eb0b36f1 100644 --- a/lua/diffs/conflict.lua +++ b/lua/diffs/conflict.lua @@ -21,7 +21,6 @@ local CONFLICT_PRIORITY = 200 ---@type table local attached_buffers = {} - ---@param event string ---@param bufnr integer local function emit(event, bufnr) diff --git a/lua/diffs/runtime/init.lua b/lua/diffs/runtime/init.lua index 5f4d0276..8b96048c 100644 --- a/lua/diffs/runtime/init.lua +++ b/lua/diffs/runtime/init.lua @@ -61,30 +61,6 @@ local function init() end log.set_enabled(config.debug) - if config.highlights.overrides and next(config.highlights.overrides) then - log.notify( - 'highlights.overrides is deprecated. Support ends on November 30, 2026. ' - .. 'Use vim.api.nvim_set_hl(). See :help diffs.nvim-highlights.', - vim.log.levels.WARN - ) - end - - if config.highlights.intra and config.highlights.intra.algorithm == 'vscode' then - log.notify( - 'libvscodediff support will be removed on 30 November 2026. ' - .. "Remove highlights.intra.algorithm to use Neovim's diff.", - vim.log.levels.WARN - ) - end - - if config.conflict and config.conflict.keymaps then - log.notify( - 'conflict.keymaps will be removed on 30 November 2026. ' - .. 'Use vim.keymap.set(). See :help diffs.nvim-conflict-keymaps.', - vim.log.levels.WARN - ) - end - fast_hl_opts = highlight.hunk_opts(config, { highlights = { treesitter = false, diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 6bfa141c..0b8ac7b5 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -146,8 +146,6 @@ describe('diffs.config', function() assert.matches(case.message, err, 1, true) end end) - - end) describe('compute_filetypes', function() @@ -181,6 +179,5 @@ describe('diffs.config', function() local fts = compute({ integrations = { neogit = false } }) assert.is_false(vim.tbl_contains(fts, 'NeogitStatus')) end) - end) end) diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 26ce35dc..b77a64cf 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -129,7 +129,7 @@ vim.fn.delete(path) 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = false, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false }, conflict = false, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -140,10 +140,6 @@ vim.fn.delete(path) "local runtime = require('diffs.runtime')", 'runtime.attach(0)', 'runtime.attach(0)', - "print('warning=' .. _G.diffs_notifications[1].message)", - "print('warning_level=' .. _G.diffs_notifications[1].level)", - "print('keymap_warning=' .. _G.diffs_notifications[2].message)", - "print('keymap_warning_level=' .. _G.diffs_notifications[2].level)", "print('after_attach_notifications=' .. #(_G.diffs_notifications or {}))", 'local runtime_config = runtime._test.get_config()', 'local highlight_opts = runtime.get_highlight_opts()', @@ -183,13 +179,7 @@ vim.fn.delete(path) assert.matches('loaded=1', output, 1, true) assert.matches('notifications=0', output, 1, true) - assert.matches('after_attach_notifications=2', output, 1, true) - assert.matches('libvscodediff support will be removed on 30 November 2026.', output, 1, true) - assert.matches('Remove highlights.intra.algorithm', output, 1, true) - assert.matches('conflict.keymaps will be removed on 30 November 2026.', output, 1, true) - assert.matches(':help diffs.nvim-conflict-keymaps', output, 1, true) - assert.matches('keymap_warning_level=' .. vim.log.levels.WARN, output, 1, true) - assert.matches('warning_level=' .. vim.log.levels.WARN, output, 1, true) + assert.matches('after_attach_notifications=0', output, 1, true) assert.matches('runtime_view_prefix=false', output, 1, true) assert.matches('runtime_fugitive=true', output, 1, true) assert.matches('runtime_neogit=true', output, 1, true) From c80a3240b2675a3fc0c5c5cb8f21c3228cc1c34f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 16:11:18 -0600 Subject: [PATCH 28/32] docs: simplify integration guidance and health checks --- doc/diffs.nvim.txt | 70 ++++---------------------------------------- lua/diffs/health.lua | 23 --------------- 2 files changed, 5 insertions(+), 88 deletions(-) diff --git a/doc/diffs.nvim.txt b/doc/diffs.nvim.txt index 0dfef0b7..ed9a2139 100644 --- a/doc/diffs.nvim.txt +++ b/doc/diffs.nvim.txt @@ -145,10 +145,6 @@ Configuration is done via `vim.g.diffs`. Set options before the plugin loads: Enable gitsigns.nvim popup support. See |diffs.nvim-gitsigns|. - {committia} - Enable committia.vim diff-pane support. See - |diffs.nvim-committia|. - {telescope} Enable telescope.nvim preview support. See |diffs.nvim-telescope|. @@ -884,13 +880,10 @@ Diff buffer mappings: ~ ============================================================================== INTEGRATIONS *diffs.nvim-integrations* -diffs.nvim integrates with several plugins. There are two attachment patterns: - -Automatic: ~ List integrations in the config. The plugin registers `FileType` -autocmds for each integration's filetypes and attaches automatically. +List the integrations to enable: >lua vim.g.diffs = { - integrations = { 'fugitive', 'neogit', 'neojj', 'gitsigns', 'committia' }, + integrations = { 'fugitive', 'neogit', 'neojj', 'gitsigns' }, } < @@ -956,15 +949,6 @@ This status-row behavior is intentionally narrower than Fugitive `:Gdiffsplit`. It preserves the useful edge selection model while keeping `diffs://` buffers read-only, explicit, and owned by diffs.nvim. -Configuration: ~ ->lua - vim.g.diffs = { - integrations = { 'fugitive' }, - } -< - Remove `'fugitive'` from the list to disable it. Status keymaps are fixed - at `du` and `dU`. - ------------------------------------------------------------------------------ NEOGIT *diffs.nvim-neogit* @@ -975,9 +959,6 @@ Enable Neogit (https://github.com/NeogitOrg/neogit) support: >lua Expanding a diff in a Neogit buffer (e.g., TAB on a file in the status view) applies treesitter syntax highlighting and intra-line diffs to the hunk lines. -Configuration: ~ - Use `neogit = false` to disable. - ------------------------------------------------------------------------------ NEOJJ *diffs.nvim-neojj* @@ -988,9 +969,6 @@ Enable neojj (https://github.com/NicholasZolton/neojj) support: >lua Expanding a diff in a neojj buffer (e.g., TAB on a file in the status view) applies treesitter syntax highlighting and intra-line diffs to the hunk lines. -Configuration: ~ - Use `neojj = false` to disable. - ------------------------------------------------------------------------------ GITSIGNS *diffs.nvim-gitsigns* @@ -999,9 +977,6 @@ integration for blame hunk previews: >lua vim.g.diffs = { integrations = { 'gitsigns' } } < -Configuration: ~ - Use `gitsigns = false` to disable. - `:Gitsigns blame_line full=true` popups receive treesitter syntax, line backgrounds, and intra-line diffs. @@ -1011,12 +986,8 @@ interfere with the main decoration provider used for diff buffers. ------------------------------------------------------------------------------ COMMITTIA *diffs.nvim-committia* -Enable committia.vim (https://github.com/rhysd/committia.vim) diff-pane -support: >lua - vim.g.diffs = { integrations = { 'committia' } } -< - -Use `committia = false` to disable. +Committia (https://github.com/rhysd/committia.vim) diff panes receive +highlighting automatically. ------------------------------------------------------------------------------ TELESCOPE *diffs.nvim-telescope* @@ -1026,11 +997,6 @@ preview highlighting: >lua vim.g.diffs = { integrations = { 'telescope' } } < -Telescope does not set `filetype=diff` on preview buffers — it calls -`vim.treesitter.start(bufnr, "diff")` directly, so diffs.nvim's `FileType` -autocmd never fires. This integration listens for the `User -TelescopePreviewerLoaded` event and attaches to the preview buffer. - Pickers that show diff content (e.g. `git_bcommits`, `git_status`) will receive treesitter syntax, line backgrounds, and intra-line diffs in the preview pane. @@ -1077,8 +1043,6 @@ emits an informational message and renders no token highlights. The 'diffopt' whitespace handling does not apply to a structural diff; such buffers are left unchanged. -Run |:checkhealth| diffs to confirm `difft` is detected. - ============================================================================== CONFLICT RESOLUTION *diffs.nvim-conflict* @@ -1369,25 +1333,6 @@ When opening a fugitive buffer, there is an unavoidable visual "flash" where the buffer briefly shows fugitive's default diff highlighting before diffs.nvim applies its own highlights. -Conflicting Diff Plugins ~ - *diffs.nvim-plugin-conflicts* -Plugins that also own diff highlighting, sign columns, or conflict resolution -can visually conflict with diffs.nvim. Common overlap points include -diffview.nvim, mini.diff, gitsigns.nvim line highlights, and -git-conflict.nvim. If you see visual conflicts, disable one plugin's -overlapping diff features. - -Prefixless Diff Path Ambiguity ~ - *diffs.nvim-prefix-ambiguity* -Diffs that diffs.nvim generates pin git's `a/` and `b/` path prefixes, so they -are unaffected by `diff.mnemonicPrefix` or `diff.noprefix`. Diffs from other -sources that diffs.nvim only highlights (fugitive, neogit, an opened patch) -are parsed as they arrive. When such a diff was produced with `diff.noprefix`, -a header like `diff --git path path` carries no prefix, and a real top-level -directory named with a single letter (for example `a` or `i`) cannot be -distinguished from a stripped prefix. In that narrow case the file may be -mis-identified, which can degrade its syntax highlighting or hunk navigation. - ============================================================================== HIGHLIGHT GROUPS *diffs.nvim-highlights* @@ -1498,12 +1443,7 @@ After diffs.nvim initializes, customize with |nvim_set_hl()| and ============================================================================== HEALTH CHECK *diffs.nvim-health* -Run `:checkhealth diffs` to verify your setup. - -Checks performed: -- Neovim version >= 0.12.0 -- vim-fugitive is installed (optional) -- libvscode_diff shared library is available (optional) +Run `:checkhealth diffs` to check the required Neovim version (0.12.0+). ============================================================================== ACKNOWLEDGEMENTS *diffs.nvim-acknowledgements* diff --git a/lua/diffs/health.lua b/lua/diffs/health.lua index 34b2c225..ccd16e70 100644 --- a/lua/diffs/health.lua +++ b/lua/diffs/health.lua @@ -8,29 +8,6 @@ function M.check() else vim.health.error('diffs.nvim requires Neovim 0.12.0+') end - - local fugitive_loaded = vim.fn.exists(':Git') == 2 - if fugitive_loaded then - vim.health.ok('vim-fugitive detected') - else - vim.health.warn('vim-fugitive not detected (required for unified diff highlighting)') - end - - local lib = require('diffs.lib') - if lib.has_lib() then - vim.health.ok('libvscode_diff found at ' .. lib.lib_path()) - else - vim.health.info('libvscode_diff not found (optional, using native vim.diff fallback)') - end - - local difftastic = require('diffs.difftastic') - if not difftastic.resolve().enabled then - vim.health.info('difftastic not enabled (optional, set integrations.difftastic)') - elseif difftastic.available() then - vim.health.ok('difftastic (difft) found on PATH') - else - vim.health.warn('integrations.difftastic is enabled but difft was not found on PATH') - end end return M From 2e1d124e497c250d6a1a3b0601f70904599c9afa Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 16:11:36 -0600 Subject: [PATCH 29/32] refactor: consolidate integration attachment and resolve highlights with Neovim --- lua/diffs/runtime/attach.lua | 61 ++++++++++---------------- lua/diffs/runtime/highlight_groups.lua | 18 ++------ 2 files changed, 26 insertions(+), 53 deletions(-) diff --git a/lua/diffs/runtime/attach.lua b/lua/diffs/runtime/attach.lua index 2c86f4cd..d596c5fb 100644 --- a/lua/diffs/runtime/attach.lua +++ b/lua/diffs/runtime/attach.lua @@ -24,40 +24,26 @@ function M.attach(opts, bufnr) opts.attached_buffers[bufnr] = true local config = opts.get_config() - local neogit_augroup = nil - if - integrations.is_enabled(config.integrations.neogit) - and integrations.matches_filetype('neogit', vim.bo[bufnr].filetype) - 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', { - pattern = 'NeogitDiffLoaded', - group = neogit_augroup, - callback = function() - if vim.api.nvim_buf_is_valid(bufnr) and opts.attached_buffers[bufnr] then - opts.refresh(bufnr) - end - end, - }) - end - - local neojj_augroup = nil - if - integrations.is_enabled(config.integrations.neojj) - and integrations.matches_filetype('neojj', vim.bo[bufnr].filetype) - then - vim.b[bufnr].neojj_disable_hunk_highlight = true - neojj_augroup = vim.api.nvim_create_augroup('diffs_neojj_' .. bufnr, { clear = true }) - vim.api.nvim_create_autocmd('User', { - pattern = 'NeojjDiffLoaded', - group = neojj_augroup, - callback = function() - if vim.api.nvim_buf_is_valid(bufnr) and opts.attached_buffers[bufnr] then - opts.refresh(bufnr) - end - end, - }) + local integration_augroup + for key, event in pairs({ neogit = 'NeogitDiffLoaded', neojj = 'NeojjDiffLoaded' }) do + if + integrations.is_enabled(config.integrations[key]) + and integrations.matches_filetype(key, vim.bo[bufnr].filetype) + then + vim.b[bufnr][key .. '_disable_hunk_highlight'] = true + integration_augroup = + vim.api.nvim_create_augroup('diffs_' .. key .. '_' .. bufnr, { clear = true }) + vim.api.nvim_create_autocmd('User', { + pattern = event, + group = integration_augroup, + callback = function() + if vim.api.nvim_buf_is_valid(bufnr) and opts.attached_buffers[bufnr] then + opts.refresh(bufnr) + end + end, + }) + break + end end log.dbg('attaching to buffer %d', bufnr) @@ -69,11 +55,8 @@ function M.attach(opts, bufnr) callback = function() opts.attached_buffers[bufnr] = nil opts.cache:delete(bufnr) - if neogit_augroup then - pcall(vim.api.nvim_del_augroup_by_id, neogit_augroup) - end - if neojj_augroup then - pcall(vim.api.nvim_del_augroup_by_id, neojj_augroup) + if integration_augroup then + pcall(vim.api.nvim_del_augroup_by_id, integration_augroup) end end, }) diff --git a/lua/diffs/runtime/highlight_groups.lua b/lua/diffs/runtime/highlight_groups.lua index f95e4976..77d6e935 100644 --- a/lua/diffs/runtime/highlight_groups.lua +++ b/lua/diffs/runtime/highlight_groups.lua @@ -25,16 +25,6 @@ local function blend_color(hex, bg_hex, alpha) ---@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 - ---@param config diffs.Config ---@param is_default? boolean ---@return { transparent: boolean } @@ -43,8 +33,8 @@ function M.apply(config, is_default) local diff_add = vim.api.nvim_get_hl(0, { name = 'DiffAdd' }) local diff_delete = vim.api.nvim_get_hl(0, { name = 'DiffDelete' }) local line_nr = vim.api.nvim_get_hl(0, { name = 'LineNr' }) - local diff_added = resolve_hl('diffAdded') - local diff_removed = resolve_hl('diffRemoved') + local diff_added = vim.api.nvim_get_hl(0, { name = 'diffAdded', link = false }) + local diff_removed = vim.api.nvim_get_hl(0, { name = 'diffRemoved', link = false }) local dark = vim.o.background ~= 'light' local transparent = not normal.bg @@ -109,8 +99,8 @@ function M.apply(config, is_default) log.dbg('DiffsAdd.bg=#%06x DiffsAddText.bg=#%06x', blended_add, blended_add_text) log.dbg('DiffsDelete.bg=#%06x DiffsDeleteText.bg=#%06x', blended_del, blended_del_text) - local diff_change = resolve_hl('DiffChange') - local diff_text = resolve_hl('DiffText') + local diff_change = vim.api.nvim_get_hl(0, { name = 'DiffChange', link = false }) + local diff_text = vim.api.nvim_get_hl(0, { name = 'DiffText', link = false }) vim.api.nvim_set_hl(0, 'DiffsDiffAdd', { default = dflt, bg = diff_add.bg }) vim.api.nvim_set_hl( From 9c6181d347ba5299b219a90715fcc2df5ccb0dc0 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 16:11:48 -0600 Subject: [PATCH 30/32] refactor: remove custom configuration validation --- lua/diffs/config.lua | 87 -------------------------------------- lua/diffs/integrations.lua | 9 +--- spec/config_spec.lua | 78 ---------------------------------- spec/difftastic_spec.lua | 18 -------- 4 files changed, 1 insertion(+), 191 deletions(-) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 6cb57737..10d6daed 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -91,99 +91,12 @@ function M.compute_filetypes(opts) return integration_metadata.filetypes(opts or {}) end ----@param opts table -function M.validate(opts) - 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('view', opts.view, 'table', true) - if opts.view then - vim.validate('view.prefix', opts.view.prefix, 'boolean', true) - end - vim.validate('integrations', opts.integrations, 'table', true) - local integrations = opts.integrations or {} - for _, key in ipairs(integration_metadata.keys()) do - vim.validate('integrations.' .. key, integrations[key], 'boolean', true) - end - vim.validate('integrations.difftastic', integrations.difftastic, function(v) - return v == nil or type(v) == 'boolean' or type(v) == 'table' - end, 'boolean or table') - if type(integrations.difftastic) == 'table' then - vim.validate('integrations.difftastic.args', integrations.difftastic.args, 'table', true) - for i, arg in ipairs(integrations.difftastic.args or {}) do - vim.validate('integrations.difftastic.args[' .. i .. ']', arg, 'string') - end - end - vim.validate('extra_filetypes', opts.extra_filetypes, 'table', true) - vim.validate('highlights', opts.highlights, 'table', true) - - if opts.highlights then - vim.validate('highlights.background', opts.highlights.background, 'boolean', true) - vim.validate('highlights.blend_alpha', opts.highlights.blend_alpha, 'number', true) - vim.validate('highlights.overrides', opts.highlights.overrides, 'table', true) - vim.validate('highlights.warn_max_lines', opts.highlights.warn_max_lines, 'boolean', true) - - for _, key in ipairs({ 'treesitter', 'vim', 'intra' }) do - local path = 'highlights.' .. key - local value = opts.highlights[key] - local types = key == 'treesitter' and 'table' or { 'boolean', 'table' } - vim.validate(path, value, types, true) - if type(value) == 'table' then - vim.validate(path .. '.enabled', value.enabled, 'boolean', true) - vim.validate(path .. '.max_lines', value.max_lines, 'number', true) - if value.max_lines and value.max_lines < 1 then - error('diffs: ' .. path .. '.max_lines must be >= 1') - end - end - end - - if type(opts.highlights.intra) == 'table' then - vim.validate('highlights.intra.algorithm', opts.highlights.intra.algorithm, function(v) - return v == nil or v == 'default' or v == 'vscode' - end, "'default' or 'vscode'") - end - end - - vim.validate('conflict', opts.conflict, function(v) - return v == nil or type(v) == 'boolean' or type(v) == 'table' - end, 'boolean or table') - if type(opts.conflict) == 'table' then - vim.validate('conflict.enabled', opts.conflict.enabled, 'boolean', true) - vim.validate('conflict.keymaps', opts.conflict.keymaps, function(v) - return v == nil or v == false or type(v) == 'table' - end, 'table or false') - - if type(opts.conflict.keymaps) == 'table' then - local keymap_validator = function(v) - return v == nil or 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.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 -end - ---@param opts? table ---@return diffs.Config function M.new(opts) opts = vim.tbl_extend('force', opts or {}, { integrations = integration_metadata.normalize(opts and opts.integrations), }) - M.validate(opts) for group, keys in pairs({ conflict = { 'disable_diagnostics', 'show_virtual_text', 'format_virtual_text', 'show_actions' }, view = { 'change_bar', 'rail_separator' }, diff --git a/lua/diffs/integrations.lua b/lua/diffs/integrations.lua index e9ef49ee..421a25b7 100644 --- a/lua/diffs/integrations.lua +++ b/lua/diffs/integrations.lua @@ -1,6 +1,6 @@ local M = {} -local keys = { 'fugitive', 'neogit', 'neojj', 'gitsigns', 'committia', 'telescope' } +local keys = { 'fugitive', 'neogit', 'neojj' } local metadata = { fugitive = { @@ -16,19 +16,12 @@ local metadata = { }, } ----@return string[] -function M.keys() - return vim.deepcopy(keys) -end - ---@param values table? ---@return diffs.IntegrationsConfig function M.normalize(values) - vim.validate('integrations', values, 'table', true) local result = {} for key, value in pairs(values or {}) do if type(key) == 'number' then - vim.validate('integrations[' .. key .. ']', value, 'string') result[value] = true else result[key] = value diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 5265e343..32601093 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -114,84 +114,6 @@ describe('diffs.config', function() end end end) - - it('rejects removed integration table config', function() - for _, key in ipairs(integration_keys) do - local ok, err = pcall(config.new, { integrations = { [key] = {} } }) - assert.is_false(ok) - assert.matches('integrations.' .. key, err, 1, true) - assert.matches('boolean', err, 1, true) - end - end) - - it('rejects invalid integration value types', function() - for _, key in ipairs(integration_keys) do - local ok, err = pcall(config.new, { integrations = { [key] = 'yes' } }) - assert.is_false(ok) - assert.matches('integrations.' .. key, err, 1, true) - assert.matches('boolean', err, 1, true) - end - local ok, err = pcall(config.new, { integrations = { true } }) - assert.is_false(ok) - assert.matches('integrations[1]', err, 1, true) - assert.matches('string', err, 1, true) - end) - - it('validates numeric highlight limits', function() - local cases = { - { - input = { highlights = { treesitter = { max_lines = 0 } } }, - message = 'diffs: highlights.treesitter.max_lines must be >= 1', - }, - { - input = { highlights = { vim = { max_lines = 0 } } }, - message = 'diffs: highlights.vim.max_lines must be >= 1', - }, - { - input = { highlights = { intra = { max_lines = 0 } } }, - message = 'diffs: highlights.intra.max_lines must be >= 1', - }, - { - input = { highlights = { blend_alpha = 2 } }, - message = 'diffs: highlights.blend_alpha must be >= 0 and <= 1', - }, - } - - for _, case in ipairs(cases) do - local ok, err = pcall(config.new, case.input) - assert.is_false(ok) - assert.matches(case.message, err, 1, true) - end - end) - - it('validates intra diff algorithm names', function() - local ok, err = pcall(config.new, { - highlights = { - intra = { - algorithm = 'other', - }, - }, - }) - - assert.is_false(ok) - assert.matches('highlights.intra.algorithm', err, 1, true) - assert.matches('default', err, 1, true) - assert.matches('vscode', err, 1, true) - end) - - it('validates conflict keymap values', function() - local ok, err = pcall(config.new, { - conflict = { - keymaps = { - ours = true, - }, - }, - }) - - assert.is_false(ok) - assert.matches('conflict.keymaps.ours', err, 1, true) - assert.matches('string or false', err, 1, true) - end) end) describe('compute_filetypes', function() diff --git a/spec/difftastic_spec.lua b/spec/difftastic_spec.lua index 02a44c66..1390a28f 100644 --- a/spec/difftastic_spec.lua +++ b/spec/difftastic_spec.lua @@ -18,24 +18,6 @@ describe('diffs.difftastic', function() end) end) - it('rejects a non-boolean/table value', function() - assert.has.errors(function() - config.new({ integrations = { difftastic = 42 } }) - end) - end) - - it('rejects args that is not a list', function() - assert.has.errors(function() - config.new({ integrations = { difftastic = { args = 'nope' } } }) - end) - end) - - it('rejects args with non-string elements', function() - assert.has.errors(function() - config.new({ integrations = { difftastic = { args = { 123, '--flag' } } } }) - end) - end) - it('defaults to disabled when unset', function() local cfg = config.new({}) assert.is_nil(cfg.integrations.difftastic) From 60540e73cfd37d91ea526763770a1573f9b93dcc Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 16:12:03 -0600 Subject: [PATCH 31/32] deprecate: warn about the ineffective Committia option --- lua/diffs/config.lua | 7 +++++++ spec/plugin_spec.lua | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 10d6daed..0ea8e5c5 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -97,6 +97,13 @@ function M.new(opts) opts = vim.tbl_extend('force', opts or {}, { integrations = integration_metadata.normalize(opts and opts.integrations), }) + if opts.integrations.committia ~= nil then + vim.notify_once( + '[diffs]: integrations.committia is deprecated and ignored. Support ends on November 30, 2026. ' + .. 'Committia highlighting is automatic. See :help diffs.nvim-committia.', + vim.log.levels.WARN + ) + end for group, keys in pairs({ conflict = { 'disable_diagnostics', 'show_virtual_text', 'format_virtual_text', 'show_actions' }, view = { 'change_bar', 'rail_separator' }, diff --git a/spec/plugin_spec.lua b/spec/plugin_spec.lua index 5de0c58f..23205814 100644 --- a/spec/plugin_spec.lua +++ b/spec/plugin_spec.lua @@ -129,7 +129,7 @@ vim.fn.delete(path) 'vim.notify = function(message, level)', '_G.diffs_notifications[#_G.diffs_notifications + 1] = { message = message, level = level }', 'end', - 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { keymaps = { ours = "co" } }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "committia", "telescope" } }', + 'vim.g.diffs = { view = { prefix = false }, highlights = { background = false, intra = { algorithm = "vscode" } }, conflict = { keymaps = { ours = "co" } }, integrations = { "fugitive", "neogit", "neojj", "gitsigns", "telescope" } }', ('vim.opt.runtimepath:prepend(%s)'):format(vim.inspect(vim.fn.getcwd())), } @@ -152,7 +152,6 @@ vim.fn.delete(path) "print('runtime_neogit=' .. tostring(runtime_config.integrations.neogit))", "print('runtime_neojj=' .. tostring(runtime_config.integrations.neojj))", "print('runtime_gitsigns=' .. tostring(runtime_config.integrations.gitsigns))", - "print('runtime_committia=' .. tostring(runtime_config.integrations.committia))", "print('runtime_telescope=' .. tostring(runtime_config.integrations.telescope))", "print('runtime_background=' .. tostring(runtime_config.highlights.background))", "print('runtime_conflict_keymaps=' .. tostring(runtime_config.conflict.keymaps.ours))", @@ -195,7 +194,6 @@ vim.fn.delete(path) assert.matches('runtime_neogit=true', output, 1, true) assert.matches('runtime_neojj=true', output, 1, true) assert.matches('runtime_gitsigns=true', output, 1, true) - assert.matches('runtime_committia=true', output, 1, true) assert.matches('runtime_telescope=true', output, 1, true) assert.matches('runtime_background=false', output, 1, true) assert.matches('runtime_conflict_keymaps=co', output, 1, true) From d01adf9f5d42528e9b97372e7af7807de936b958 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Nov 2026 16:12:56 -0600 Subject: [PATCH 32/32] refactor: remove obsolete Committia configuration support --- lua/diffs/config.lua | 8 -------- spec/config_spec.lua | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lua/diffs/config.lua b/lua/diffs/config.lua index 0ea8e5c5..4beabb6b 100644 --- a/lua/diffs/config.lua +++ b/lua/diffs/config.lua @@ -45,7 +45,6 @@ local integration_metadata = require('diffs.integrations') ---@field neogit? boolean ---@field neojj? boolean ---@field gitsigns? boolean ----@field committia? boolean ---@field telescope? boolean ---@field difftastic? boolean|diffs.DifftasticConfig @@ -97,13 +96,6 @@ function M.new(opts) opts = vim.tbl_extend('force', opts or {}, { integrations = integration_metadata.normalize(opts and opts.integrations), }) - if opts.integrations.committia ~= nil then - vim.notify_once( - '[diffs]: integrations.committia is deprecated and ignored. Support ends on November 30, 2026. ' - .. 'Committia highlighting is automatic. See :help diffs.nvim-committia.', - vim.log.levels.WARN - ) - end for group, keys in pairs({ conflict = { 'disable_diagnostics', 'show_virtual_text', 'format_virtual_text', 'show_actions' }, view = { 'change_bar', 'rail_separator' }, diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 32601093..57d5eff1 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -2,7 +2,7 @@ require('spec.helpers') local config = require('diffs.config') describe('diffs.config', function() - local integration_keys = { 'fugitive', 'neogit', 'neojj', 'gitsigns', 'committia', 'telescope' } + local integration_keys = { 'fugitive', 'neogit', 'neojj', 'gitsigns', 'telescope' } describe('new', function() it('accepts nil config', function() @@ -39,7 +39,6 @@ describe('diffs.config', function() neogit = true, neojj = true, gitsigns = true, - committia = true, telescope = true, }, conflict = {