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 = {