Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- In the merge tool, `g?`, `]x` and `[x` also work from the ours, theirs and base panes

## [0.2.3] — 2026-10-09

### Fixed

- A file whose `.gitattributes` sets a clean filter, `ident` or `working-tree-encoding` showed changes `git diff` doesn't, and staging its hunks could stage different content from `git add`. Its worktree side now reads as git would store it
Expand Down
13 changes: 0 additions & 13 deletions lua/differ/git/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -398,19 +398,6 @@ local function untracked_additions(root, relpath)
return additions
end

-- whether `relpath` is currently conflicted
---@param root string
---@param relpath string
---@return boolean
function M.is_conflicted(root, relpath)
for _, p in ipairs(M.conflicted(root)) do
if p == relpath then
return true
end
end
return false
end

-- list changed files for a resolved source (used by the picker/panel). rev.source is
-- pure and can't tell a real ref from a typo, so this is the first call that finds
-- out: git's stderr rides along, else a typo reads as an empty change set
Expand Down
38 changes: 26 additions & 12 deletions lua/differ/merge/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -783,27 +783,31 @@ function M.close()
end
end

-- g?: a floating keymap cheatsheet for the merge result buffer, mirroring the diff
-- view's. rows read the session's resolved keymaps so a configured lhs shows correctly
-- g?: a floating keymap cheatsheet, mirroring the diff view's. rows read the session's
-- resolved keymaps so a configured lhs shows correctly; an input pane lists only its keys
local function show_help()
if not session then
return
end
local km = session.keymaps
local help = require("differ.ui.help")
local fmt, pair = help.fmt, help.pair
local rows = {
{ pair(km.next_conflict, km.prev_conflict), "next / previous conflict" },
{ pair(km.next_marker, km.prev_marker), "next / previous marker line" },
{ fmt(km.choose_ours), "take ours" },
{ fmt(km.choose_theirs), "take theirs" },
{ fmt(km.choose_base), "take base" },
{ fmt(km.choose_all), "take both (ours then theirs)" },
{ fmt(km.choose_none), "drop the conflict" },
{ ":w", "write the file (auto-stages once resolved)" },
local rows = { { pair(km.next_conflict, km.prev_conflict), "next / previous conflict" } }
if vim.api.nvim_get_current_buf() == session.result_buf then
vim.list_extend(rows, {
{ pair(km.next_marker, km.prev_marker), "next / previous marker line" },
{ fmt(km.choose_ours), "take ours" },
{ fmt(km.choose_theirs), "take theirs" },
{ fmt(km.choose_base), "take base" },
{ fmt(km.choose_all), "take both (ours then theirs)" },
{ fmt(km.choose_none), "drop the conflict" },
{ ":w", "write the file (auto-stages once resolved)" },
})
end
vim.list_extend(rows, {
{ ":Differ close", "close the merge tool" },
{ fmt(km.help), "this help" },
}
})
-- dismiss on the configured help key too, not just the hardcoded q/<Esc>
local dismiss = { "q", "<Esc>" }
vim.list_extend(dismiss, type(km.help) == "table" and km.help or { km.help })
Expand Down Expand Up @@ -997,6 +1001,16 @@ function lay_out(root, relpath, model, layout)
resolve_choice("none")
end, "differ: drop the conflict")
rb("help", show_help, "differ: keymap help")
-- the input panes are scratch buffers deleted on close, so these need no unbind
for _, buf in ipairs(bufs) do
bind(buf, km.next_conflict, function()
goto_conflict("next")
end, "differ: next conflict")
bind(buf, km.prev_conflict, function()
goto_conflict("prev")
end, "differ: previous conflict")
bind(buf, km.help, show_help, "differ: keymap help")
end
-- no close key here: the result buffer is the real file and stays editable, so q is
-- left to native macro recording. `:Differ close` ends the session

Expand Down
44 changes: 44 additions & 0 deletions test/nvim/mergetool_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,50 @@ describe(":Differ mergetool", function()
vim.api.nvim_win_close(float, true)
end)

it("binds conflict nav and g? on the input panes, with a pane-scoped cheatsheet", function()
local root = conflict_repo()
vim.cmd.edit(root .. "/f.txt")
merge.open({})
local s = merge.current()

local function callback(buf, desc)
for _, m in ipairs(vim.api.nvim_buf_get_keymap(buf, "n")) do
if m.desc == desc then
return m.callback
end
end
end
for _, buf in ipairs(s.bufs) do
assert.is_not_nil(callback(buf, "differ: next conflict"))
assert.is_not_nil(callback(buf, "differ: previous conflict"))
assert.is_not_nil(callback(buf, "differ: keymap help"))
end

local inp = s.inputs[1]
vim.api.nvim_set_current_win(inp.win)
vim.api.nvim_win_set_cursor(s.result_win, { 1, 0 })
callback(s.bufs[1], "differ: next conflict")()
assert.are.equal(s.regions[1].result_start, vim.api.nvim_win_get_cursor(s.result_win)[1])
assert.are.equal(inp.win, vim.api.nvim_get_current_win()) -- focus stays on the input

callback(s.bufs[1], "differ: keymap help")()
local float
for _, w in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_config(w).relative ~= "" then
float = w
end
end
assert.is_not_nil(float)
local txt = table.concat(
vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(float), 0, -1, false),
"\n"
)
assert.is_true(txt:find("next / previous conflict", 1, true) ~= nil)
assert.is_nil(txt:find("take ours", 1, true))
assert.is_nil(txt:find("marker line", 1, true))
vim.api.nvim_win_close(float, true)
end)

it("opts the result buffer out of format-on-save and restores it on close", function()
local root = conflict_repo()
vim.cmd.edit(root .. "/f.txt")
Expand Down
Loading