You can watch a three-minute demo on YouTube
% jumping between conflict markers<Plug> mappings for easy custom keybindingThis plugin was inspired by prior work:
The feature I was missing in other plugins was a quick way to compare the local or remote side of the conflict with the common base (which is included in conflicted files if enabled, e.g. by the merge.conflictStyle setting in git set to diff3).
Development was aided by Claude Code. Pull requests are welcome. If you use AI coding tools, please read and understand all code changes before submitting them as a PR.
{
"spacedentist/resolve.nvim",
event = { "BufReadPre", "BufNewFile" },
opts = {},
}
use {
"spacedentist/resolve.nvim",
config = function()
require("resolve").setup()
end,
}
Plug 'spacedentist/resolve.nvim'
lua << EOF
require("resolve").setup()
EOF
Default configuration:
require("resolve").setup({
-- Conflict marker patterns (Lua patterns, must match from start of line)
markers = {
ours = "^<<<<<<<+", -- Start of "ours" section
theirs = "^>>>>>>>+", -- End of "theirs" section
ancestor = "^|||||||+", -- Start of ancestor/base section (diff3)
separator = "^=======+$", -- Separator between sections
},
-- Set to false to disable default keymaps
default_keymaps = true,
-- Callback function called when conflicts are detected
-- Receives: { bufnr = number, conflicts = table }
on_conflict_detected = nil,
-- Callback function called when all conflicts are resolved
-- Receives: { bufnr = number }
on_conflicts_resolved = nil,
})
The plugin creates highlight groups for both conflict markers and their content sections, with semantic colours that automatically adapt to light/dark backgrounds:
| Highlight Group | Marker | Dark Theme | Light Theme | Meaning |
|---|---|---|---|---|
ResolveOursMarker |
<<<<<<< |
Green tint | Light green | Your changes (keep) |
ResolveTheirsMarker |
>>>>>>> |
Blue tint | Light blue | Incoming changes |
ResolveSeparatorMarker |
======= |
Grey | Light grey | Neutral divider |
ResolveAncestorMarker |
||||||| |
Amber tint | Light amber | Original/base (diff3) |
All markers are displayed in bold with normal text colour and a tinted background.
| Highlight Group | Section | Dark Theme | Light Theme | Meaning |
|---|---|---|---|---|
ResolveOursSection |
Ours content | Subtle green | Very light green | Your changes |
ResolveTheirsSection |
Theirs content | Subtle blue | Very light blue | Incoming changes |
ResolveAncestorSection |
Base content | Subtle amber | Very light amber | Original (diff3) |
The section highlights provide a subtle background tint to help visually distinguish which code belongs to which side.
The highlights automatically update when you change colour schemes or toggle between light/dark backgrounds.
Override the highlight groups in your config to customise the appearance:
-- After calling setup(), override any highlights you want to change
-- Marker highlights (bold lines)
vim.api.nvim_set_hl(0, "ResolveOursMarker", { bg = "#3d5c3d", bold = true })
vim.api.nvim_set_hl(0, "ResolveTheirsMarker", { bg = "#3d4d5c", bold = true })
vim.api.nvim_set_hl(0, "ResolveSeparatorMarker", { bg = "#4a4a4a", bold = true })
vim.api.nvim_set_hl(0, "ResolveAncestorMarker", { bg = "#5c4d3d", bold = true })
-- Section highlights (content areas)
vim.api.nvim_set_hl(0, "ResolveOursSection", { bg = "#2a3a2a" })
vim.api.nvim_set_hl(0, "ResolveTheirsSection", { bg = "#2a2f3a" })
vim.api.nvim_set_hl(0, "ResolveAncestorSection", { bg = "#3a322a" })
Or link to existing highlight groups if you prefer theme-matched colours:
-- Markers
vim.api.nvim_set_hl(0, "ResolveOursMarker", { link = "DiffAdd" })
vim.api.nvim_set_hl(0, "ResolveTheirsMarker", { link = "DiffChange" })
vim.api.nvim_set_hl(0, "ResolveSeparatorMarker", { link = "NonText" })
vim.api.nvim_set_hl(0, "ResolveAncestorMarker", { link = "DiffText" })
-- Sections
vim.api.nvim_set_hl(0, "ResolveOursSection", { link = "DiffAdd" })
vim.api.nvim_set_hl(0, "ResolveTheirsSection", { link = "DiffChange" })
vim.api.nvim_set_hl(0, "ResolveAncestorSection", { link = "DiffText" })
To disable section highlights entirely while keeping marker highlights:
vim.api.nvim_set_hl(0, "ResolveOursSection", {})
vim.api.nvim_set_hl(0, "ResolveTheirsSection", {})
vim.api.nvim_set_hl(0, "ResolveAncestorSection", {})
When default_keymaps is enabled (keymaps are buffer-local, only active when conflicts exist):
]x - Navigate to next conflict[x - Navigate to previous conflict<leader>gco - Choose ours (current changes)<leader>gct - Choose theirs (incoming changes)<leader>gcb - Choose both (keep both versions)<leader>gcB - Choose both reverse (theirs then ours)<leader>gcm - Choose base/ancestor (diff3 only)<leader>gcn - Choose none (delete conflict)<leader>gcl - List all conflicts in quickfix window<leader>gcdo - Show diff ours (base → ours, diff3 only)<leader>gcdt - Show diff theirs (base → theirs, diff3 only)<leader>gcdb - Show diff both (base → ours and base → theirs, diff3 only)<leader>gcdv - Show diff ours → theirs (direct comparison, works without diff3)<leader>gcdV - Show diff theirs → ours (reverse comparison, works without diff3)The <leader>gc prefix displays as "Git Conflicts" in which-key, and <leader>gcd displays as "Diff".
The plugin provides the following commands:
:ResolveNext - Navigate to next conflict:ResolvePrev - Navigate to previous conflict:ResolveOurs - Choose ours version:ResolveTheirs - Choose theirs version:ResolveBoth - Choose both versions (ours then theirs):ResolveBothReverse - Choose both versions (theirs then ours):ResolveBase - Choose base/ancestor version (diff3 only):ResolveNone - Choose neither version:ResolveList - List all conflicts in quickfix:ResolveDetect - Manually detect conflicts:ResolveDiffOurs - Show diff of our changes from base (diff3 only):ResolveDiffTheirs - Show diff of their changes from base (diff3 only):ResolveDiffBoth - Show both diffs in floating window (diff3 only):ResolveDiffOursTheirs - Show diff ours → theirs (works without diff3):ResolveDiffTheirsOurs - Show diff theirs → ours (works without diff3)If you prefer custom keymaps, disable the default ones and set your own using the <Plug> mappings:
require("resolve").setup({
default_keymaps = false,
})
-- Example: Set custom keymaps using <Plug> mappings
-- Register groups for which-key (optional)
vim.keymap.set("n", "<leader>gc", "", { desc = "+Git Conflicts" })
vim.keymap.set("n", "<leader>gcd", "", { desc = "+Diff" })
vim.keymap.set("n", "]c", "<Plug>(resolve-next)", { desc = "Next conflict" })
vim.keymap.set("n", "[c", "<Plug>(resolve-prev)", { desc = "Previous conflict" })
vim.keymap.set("n", "<leader>gco", "<Plug>(resolve-ours)", { desc = "Choose ours" })
vim.keymap.set("n", "<leader>gct", "<Plug>(resolve-theirs)", { desc = "Choose theirs" })
vim.keymap.set("n", "<leader>gcb", "<Plug>(resolve-both)", { desc = "Choose both" })
vim.keymap.set("n", "<leader>gcB", "<Plug>(resolve-both-reverse)", { desc = "Choose both reverse" })
vim.keymap.set("n", "<leader>gcm", "<Plug>(resolve-base)", { desc = "Choose base" })
vim.keymap.set("n", "<leader>gcn", "<Plug>(resolve-none)", { desc = "Choose none" })
vim.keymap.set("n", "<leader>gcdo", "<Plug>(resolve-diff-ours)", { desc = "Diff ours" })
vim.keymap.set("n", "<leader>gcdt", "<Plug>(resolve-diff-theirs)", { desc = "Diff theirs" })
vim.keymap.set("n", "<leader>gcdb", "<Plug>(resolve-diff-both)", { desc = "Diff both" })
vim.keymap.set("n", "<leader>gcdv", "<Plug>(resolve-diff-vs)", { desc = "Diff ours → theirs" })
vim.keymap.set("n", "<leader>gcdV", "<Plug>(resolve-diff-vs-reverse)", { desc = "Diff theirs → ours" })
vim.keymap.set("n", "<leader>gcl", "<Plug>(resolve-list)", { desc = "List conflicts" })
<Plug> MappingsThe following <Plug> mappings are always available for custom keybindings:
<Plug>(resolve-next) - Navigate to next conflict<Plug>(resolve-prev) - Navigate to previous conflict<Plug>(resolve-ours) - Choose ours version<Plug>(resolve-theirs) - Choose theirs version<Plug>(resolve-both) - Choose both versions (ours then theirs)<Plug>(resolve-both-reverse) - Choose both versions (theirs then ours)<Plug>(resolve-base) - Choose base version<Plug>(resolve-none) - Choose neither version<Plug>(resolve-diff-ours) - Show diff ours (base → ours)<Plug>(resolve-diff-theirs) - Show diff theirs (base → theirs)<Plug>(resolve-diff-both) - Show both diffs<Plug>(resolve-diff-vs) - Show diff ours → theirs<Plug>(resolve-diff-vs-reverse) - Show diff theirs → ours<Plug>(resolve-list) - List conflicts in quickfixNote: The default keymaps use <leader>gc prefix (git conflicts) to avoid conflicts with LSP-specific keybindings that may be dynamically set under <leader>c when language servers attach.
When default_keymaps is enabled, keymaps are only set in buffers that contain conflicts. This prevents the keymaps from interfering with other plugins or workflows in files without conflicts.
The plugin automatically registers the <leader>gc group with which-key (if installed), displaying "Git Conflicts" when you press <leader>g in a buffer with conflicts.
The plugin integrates with Vim's matchit to allow jumping between conflict markers using %. When a buffer contains conflicts, you can press % on any marker (<<<<<<<, |||||||, =======, >>>>>>>) to jump to the corresponding marker in the conflict.
You can view diffs to help understand what changed on each side of the conflict.
Diff3-style conflicts (with base/ancestor):
<leader>gcdo (or :ResolveDiffOurs) - Show base → ours (what you changed)<leader>gcdt (or :ResolveDiffTheirs) - Show base → theirs (what they changed)<leader>gcdb (or :ResolveDiffBoth) - Show both diffs in one windowAll conflicts (including non-diff3):
<leader>gcdv (or :ResolveDiffOursTheirs) - Show ours → theirs (what changes if you accept theirs)<leader>gcdV (or :ResolveDiffTheirsOurs) - Show theirs → ours (what changes if you accept ours)The direct comparison views are useful when you want to see the actual differences between the two sides without comparing to the base. They work even when there's no ancestor section. The arrow indicates the diff direction: additions appear as lines being added to the left side to produce the right side.
The diff view uses delta for beautiful syntax highlighting with intra-line change emphasis. Press q or <Esc> to close the floating window.
You can run custom code when conflicts are detected or resolved using callbacks:
require("resolve").setup({
on_conflict_detected = function(info)
-- Called when conflicts are found in a buffer
-- info.bufnr: buffer number
-- info.conflicts: table of conflict data
vim.notify(string.format("Found %d conflicts!", #info.conflicts), vim.log.levels.WARN)
-- Example: Auto-open quickfix list when conflicts detected
vim.schedule(function()
require("resolve").list_conflicts()
end)
end,
on_conflicts_resolved = function(info)
-- Called when all conflicts in a buffer are resolved
-- info.bufnr: buffer number
vim.notify("All conflicts resolved!", vim.log.levels.INFO)
-- Example: Remove custom keymaps you added in on_conflict_detected
vim.keymap.del("n", "<leader>cc", { buffer = info.bufnr })
end,
})
The on_conflicts_resolved hook is particularly useful for cleaning up custom keymaps or other buffer-local setup you added in on_conflict_detected.
When you open a file with merge conflicts, resolve.nvim automatically:
<<<<<<<, =======, >>>>>>>)Standard 3-way merge:
<<<<<<< HEAD (ours)
Your changes
=======
Their changes
>>>>>>> branch-name (theirs)
diff3 style:
<<<<<<< HEAD (ours)
Your changes
||||||| ancestor
Original content
=======
Their changes
>>>>>>> branch-name (theirs)
MIT