A Neovim plugin that provides VSCode-style side-by-side diff rendering with two-tier highlighting.
https://github.com/user-attachments/assets/64c41f01-dffe-4318-bce4-16eec8de356e
Demo: Quick walkthrough of diff features
curl or wget (for automatic binary download)nui.nvim (for explorer UI)No compiler required! The plugin automatically downloads pre-built binaries from GitHub releases.
Minimal installation:
{
"esmuellert/codediff.nvim",
dependencies = { "MunifTanjim/nui.nvim" },
cmd = "CodeDiff",
}
Note: The plugin automatically adapts to your colorscheme's background (dark/light). It uses
DiffAddandDiffDeletefor line-level diffs, and auto-adjusts brightness for character-level highlights (1.4x brighter for dark themes, 0.92x darker for light themes). See Highlight Groups for customization.
With custom configuration:
{
"esmuellert/codediff.nvim",
dependencies = { "MunifTanjim/nui.nvim" },
cmd = "CodeDiff",
config = function()
require("codediff").setup({
-- Highlight configuration
highlights = {
-- Line-level: accepts highlight group names or hex colors (e.g., "#2ea043")
line_insert = "DiffAdd", -- Line-level insertions
line_delete = "DiffDelete", -- Line-level deletions
-- Character-level: accepts highlight group names or hex colors
-- If specified, these override char_brightness calculation
char_insert = nil, -- Character-level insertions (nil = auto-derive)
char_delete = nil, -- Character-level deletions (nil = auto-derive)
-- Brightness multiplier (only used when char_insert/char_delete are nil)
-- nil = auto-detect based on background (1.4 for dark, 0.92 for light)
char_brightness = nil, -- Auto-adjust based on your colorscheme
-- Conflict sign highlights (for merge conflict views)
-- Accepts highlight group names or hex colors (e.g., "#f0883e")
-- nil = use default fallback chain
conflict_sign = nil, -- Unresolved: DiagnosticSignWarn -> #f0883e
conflict_sign_resolved = nil, -- Resolved: Comment -> #6e7681
conflict_sign_accepted = nil, -- Accepted: GitSignsAdd -> DiagnosticSignOk -> #3fb950
conflict_sign_rejected = nil, -- Rejected: GitSignsDelete -> DiagnosticSignError -> #f85149
},
-- Diff view behavior
diff = {
disable_inlay_hints = true, -- Disable inlay hints in diff windows for cleaner view
max_computation_time_ms = 5000, -- Maximum time for diff computation (VSCode default)
hide_merge_artifacts = false, -- Hide merge tool temp files (*.orig, *.BACKUP.*, *.BASE.*, *.LOCAL.*, *.REMOTE.*)
},
-- Explorer panel configuration
explorer = {
position = "left", -- "left" or "bottom"
width = 40, -- Width when position is "left" (columns)
height = 15, -- Height when position is "bottom" (lines)
indent_markers = true, -- Show indent markers in tree view (│, ├, └)
icons = {
folder_closed = "", -- Nerd Font folder icon (customize as needed)
folder_open = "", -- Nerd Font folder-open icon
},
view_mode = "list", -- "list" or "tree"
file_filter = {
ignore = {}, -- Glob patterns to hide (e.g., {"*.lock", "dist/*"})
},
},
-- Keymaps in diff view
keymaps = {
view = {
quit = "q", -- Close diff tab
toggle_explorer = "<leader>b", -- Toggle explorer visibility (explorer mode only)
next_hunk = "]c", -- Jump to next change
prev_hunk = "[c", -- Jump to previous change
next_file = "]f", -- Next file in explorer mode
prev_file = "[f", -- Previous file in explorer mode
diff_get = "do", -- Get change from other buffer (like vimdiff)
diff_put = "dp", -- Put change to other buffer (like vimdiff)
},
explorer = {
select = "<CR>", -- Open diff for selected file
hover = "K", -- Show file diff preview
refresh = "R", -- Refresh git status
toggle_view_mode = "i", -- Toggle between 'list' and 'tree' views
},
conflict = {
accept_incoming = "<leader>ct", -- Accept incoming (theirs/left) change
accept_current = "<leader>co", -- Accept current (ours/right) change
accept_both = "<leader>cb", -- Accept both changes (incoming first)
discard = "<leader>cx", -- Discard both, keep base
next_conflict = "]x", -- Jump to next conflict
prev_conflict = "[x", -- Jump to previous conflict
diffget_incoming = "2do", -- Get hunk from incoming (left/theirs) buffer
diffget_current = "3do", -- Get hunk from current (right/ours) buffer
},
},
})
end,
}
The C library will be downloaded automatically on first use. No build step needed!
The plugin automatically manages the C library installation:
Automatic Updates:
Manual Installation Commands:
" Install/update the library manually
:CodeDiff install
" Force reinstall (useful for troubleshooting)
:CodeDiff install!
Version Management:
The installer reads the VERSION file to download the matching library version from GitHub releases. This ensures compatibility between the Lua code and C library.
If you prefer to install manually without a plugin manager:
git clone https://github.com/esmuellert/codediff.nvim ~/.local/share/nvim/codediff.nvim
init.lua:vim.opt.rtp:append("~/.local/share/nvim/codediff.nvim")
The plugin requires a C library binary in the plugin root directory. The plugin auto-detects these filenames:
libvscode_diff.so or libvscode_diff_<version>.so (Linux/BSD)libvscode_diff.dylib or libvscode_diff_<version>.dylib (macOS)libvscode_diff.dll or libvscode_diff_<version>.dll (Windows)Option A: Download from GitHub releases (recommended)
Download the appropriate binary from the GitHub releases page and place it in the plugin root directory. Rename it to match the expected format: libvscode_diff.so/.dylib/.dll or libvscode_diff_<version>.so/.dylib/.dll. Linux users: If your system lacks OpenMP, also download libgomp_linux_{arch}_{version}.so.1 and rename it to libgomp.so.1 in the same directory.
Option B: Build from source
Build requirements: C compiler (GCC/Clang/MSVC/MinGW) or CMake 3.15+
Using build scripts (no CMake required):
# Linux/macOS/BSD
./build.sh
# Windows
build.cmd
Or using CMake:
cmake -B build
cmake --build build
Both methods automatically place the library in the plugin root directory.
The :CodeDiff command supports multiple modes:
Open an interactive file explorer showing changed files:
" Show git status in explorer (default)
:CodeDiff
" Show changes for specific revision in explorer
:CodeDiff HEAD~5
" Compare against a branch
:CodeDiff main
" Compare against a specific commit
:CodeDiff abc123
" Compare two revisions (e.g. HEAD vs main)
:CodeDiff main HEAD
Compare the current buffer with a git revision:
" Compare with last commit
:CodeDiff file HEAD
" Compare with previous commit
:CodeDiff file HEAD~1
" Compare with specific commit
:CodeDiff file abc123
" Compare with branch
:CodeDiff file main
" Compare with tag
:CodeDiff file v1.0.0
" Compare two revisions for current file
:CodeDiff file main HEAD
Requirements:
Behavior:
Compare two arbitrary files side-by-side:
:CodeDiff file file_a.txt file_b.txt
Use CodeDiff as your git merge tool for resolving conflicts:
git config --global merge.tool codediff
git config --global mergetool.codediff.cmd 'nvim "$MERGED" -c "CodeDiff merge \"$MERGED\""'
-- Primary user API - setup configuration
require("codediff").setup({
highlights = {
line_insert = "DiffAdd",
line_delete = "DiffDelete",
char_brightness = 1.4,
},
})
-- Advanced usage - direct access to internal modules
local diff = require("codediff.diff")
local render = require("codediff.ui")
local git = require("codediff.git")
-- Example 1: Compute diff between two sets of lines
local lines_a = {"line 1", "line 2"}
local lines_b = {"line 1", "modified line 2"}
local lines_diff = diff.compute_diff(lines_a, lines_b)
-- Example 2: Get file content from git (async)
git.get_file_content("HEAD~1", "/path/to/repo", "relative/path.lua", function(err, lines)
if err then
vim.notify(err, vim.log.levels.ERROR)
return
end
-- Use lines...
end)
-- Example 3: Get git root for a file (async)
git.get_git_root("/path/to/file.lua", function(err, git_root)
if not err then
-- File is in a git repository
end
end)
C Module (libvscode-diff/): Fast diff computation and render plan generation
rangeMapping.ts data structuresLua FFI Layer (lua/vscode-diff/diff.lua): Bridge between C and Lua
Render Module (lua/vscode-diff/render/): Neovim buffer rendering
The plugin handles syntax highlighting differently based on buffer type:
Working files (editable):
Git history files (read-only):
The plugin defines highlight groups matching VSCode's diff colors:
CodeDiffLineInsert - Light green background for inserted linesCodeDiffLineDelete - Light red background for deleted linesCodeDiffCharInsert - Deep/dark green for inserted charactersCodeDiffCharDelete - Deep/dark red for deleted charactersCodeDiffFiller - Gray foreground for filler line slashes (╱╱╱)Dawnfox Light - Default configuration with auto-detected brightness (char_brightness = 0.92 for light themes):
Catppuccin Mocha - Default configuration with auto-detected brightness (char_brightness = 1.4 for dark themes):
Kanagawa Lotus - Default configuration with auto-detected brightness (char_brightness = 0.92 for light themes):
Default behavior:
DiffAdd and DiffDelete for line-level highlightsvim.o.background:background = "dark"): Brightness multiplied by 1.4 (40% brighter)background = "light"): Brightness multiplied by 0.92 (8% darker)char_brightness value if neededCustomization examples:
-- Use hex colors directly
highlights = {
line_insert = "#1d3042",
line_delete = "#351d2b",
char_brightness = 1.5, -- Override auto-detection with explicit value
}
-- Override character colors explicitly
highlights = {
line_insert = "DiffAdd",
line_delete = "DiffDelete",
char_insert = "#3fb950",
char_delete = "#ff7b72",
}
-- Mix highlight groups and hex colors
highlights = {
line_insert = "String",
char_delete = "#ff0000",
}
make clean && make
Run all tests:
make test # Run all tests (C + Lua integration)
Run specific test suites:
make test-c # C unit tests only
make test-lua # Lua integration tests only
For more details on the test structure, see tests/README.md.
codediff.nvim/
├── libvscode-diff/ # C diff engine
│ ├── src/ # C implementation
│ ├── include/ # C headers
│ └── tests/ # C unit tests
├── lua/
│ ├── codediff/ # Main Lua modules
│ │ ├── init.lua # Main API
│ │ ├── config.lua # Configuration
│ │ ├── diff.lua # FFI interface
│ │ ├── git.lua # Git operations
│ │ ├── commands.lua # Command handlers
│ │ ├── installer.lua # Binary installer
│ │ └── ui/ # UI components
│ │ ├── core.lua # Diff rendering
│ │ ├── highlights.lua # Highlight setup
│ │ ├── view/ # View management
│ │ ├── explorer/ # Git status explorer
│ │ ├── lifecycle/ # Lifecycle management
│ │ └── conflict/ # Conflict resolution
│ └── vscode-diff/ # Backward compatibility shims
├── plugin/ # Plugin entry point
│ └── codediff.lua # Auto-loaded on startup
├── tests/ # Test suite (plenary.nvim)
├── docs/ # Production docs
├── dev-docs/ # Development docs
├── Makefile # Build automation
└── README.md
This plugin follows VSCode's diff rendering architecture:
src/vs/editor/common/diff/rangeMapping.tssrc/vs/editor/browser/widget/diffEditor/registrations.contribution.tssrc/vs/editor/browser/widget/diffEditor/style.cssMIT
Contributions are welcome! Please ensure:
make test)