Resolve.nvim
A Neovim plugin for resolving merge conflicts with conflict marker highlighting and resolution commands.
Install / Use
/learn @spacedentist/Resolve.nvimREADME
You can watch a three-minute demo on YouTube
Features
- Automatically detect merge conflicts in buffers
- Semantic highlighting for both markers and content sections with automatic light/dark theme support
- Navigate between conflicts quickly
- Resolve conflicts with simple commands
- Support for standard 3-way merge and diff3 formats
- View diffs between base and each version separately or together in a floating window
- List all conflicts in quickfix window
- Buffer-local keymaps (only active in buffers with conflicts)
- Matchit integration for
%jumping between conflict markers <Plug>mappings for easy custom keybinding- Customisable hooks/callbacks on conflict detection
Development
This 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.
Requirements
- Neovim >= 0.9
- delta - required for diff view features (optional if you only use highlighting and conflict resolution)
Installation
Using lazy.nvim
{
"spacedentist/resolve.nvim",
event = { "BufReadPre", "BufNewFile" },
opts = {},
}
Using packer.nvim
use {
"spacedentist/resolve.nvim",
config = function()
require("resolve").setup()
end,
}
Using vim-plug
Plug 'spacedentist/resolve.nvim'
lua << EOF
require("resolve").setup()
EOF
Configuration
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,
})
Theming and Highlights
The plugin creates highlight groups for both conflict markers and their content sections, with semantic colours that automatically adapt to light/dark backgrounds:
Marker Highlights (bold lines)
| 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.
Section Highlights (content between markers)
| 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.
Customising Highlights
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", {})
Usage
Default Keymaps
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".
Commands
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)
Custom Keymaps
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" })
Available <Plug> Mappings
The following <Plug> mappings
Related Skills
node-connect
345.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
106.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
