A Neovim plugin that displays difftastic's structural diffs in a side-by-side
view with syntax highlighting.
- Side-by-side diff view with synchronized scrolling
- Hierarchical file tree sidebar with directory collapsing
- Syntax highlighting for the source language
- Filler lines to visually indicate alignment gaps
- Support for both jj and git version control
- Optional snacks.nvim picker for selecting a revision/commit
- Neovim 0.9+
- nui.nvim
- difftastic (
difftcommand) - jj or git version control
- Rust toolchain (only if building from source)
- snacks.nvim (optional, only for
:DifftPick)
Warning
This plugin requires difftastic with aligned_lines support in JSON output. This feature is available in
main, though hasn't yet landed in a release.
To build difftastic from source:
# Clone with 'jj'
jj git clone git@github.com:Wilfred/difftastic.git --colocate
# Or, clone with 'git'
git clone git@github.com:Wilfred/difftastic.git
# Install 'difft' with the 'aligned_lines' feature
cd difftastic && cargo install --path .{
"clabby/difftastic.nvim",
dependencies = {
"MunifTanjim/nui.nvim",
-- optional: only needed for :DifftPick
"folke/snacks.nvim",
},
config = function()
require("difftastic-nvim").setup({
download = true, -- Auto-download pre-built binary
snacks_picker = {
enabled = true,
},
})
end,
}If you prefer to build locally or pre-built binaries aren't available for your platform:
{
"clabby/difftastic.nvim",
dependencies = { "MunifTanjim/nui.nvim" },
config = function()
require("difftastic-nvim").setup()
end,
}Requires a Rust toolchain. The plugin automatically builds from source on first use if the library isn't found.
| Command | Description |
|---|---|
:Difft |
Open diff view for unstaged changes (git) or uncommitted changes (jj) |
:Difft --staged |
Open diff view for staged changes (git only) |
:Difft <ref> |
Open diff view for a jj revset or git commit/range |
:DifftPick |
Pick a jj revision or git commit using snacks.nvim (with preview) |
:DifftPickRange |
Pick end revision, then pick a parent revision as range start |
:DifftClose |
Close the diff view |
:DifftUpdate |
Update to latest release (requires download = true) |
" Diff uncommitted changes (working copy vs @)
:Difft
" Diff the current change
:Difft @
" Diff the parent of the current change
:Difft @-
" Diff a change-id prefix (equivalent to jj diff -r w)
:Difft w
" Diff a specific revision
:Difft abc123" Diff unstaged changes (working tree vs index)
:Difft
" Diff staged changes (index vs HEAD)
:Difft --staged
" Diff the last commit
:Difft HEAD
" Diff a specific commit
:Difft abc123
" Diff a commit range
:Difft main..HEADAll keybindings are buffer-local and configurable via setup(). Defaults:
| Key | Action |
|---|---|
]f |
Next file |
[f |
Previous file |
]c |
Next hunk |
[c |
Previous hunk |
<Tab> |
Toggle focus between file tree and diff |
<CR> |
Open file under cursor (in file tree) |
gf |
Go to file at cursor position (opens in previous tab or new tab) |
q |
Close diff view |
The gf keymap works from the right pane (new/working version) and jumps to the corresponding line and column in an editable buffer. If on a filler line, it jumps to the nearest non-filler line.
Filler lines (╱╱╱) indicate where content exists on one side but not the other.
require("difftastic-nvim").setup({
download = false, -- Auto-download pre-built binary (default: false)
vcs = "jj", -- "jj" (default) or "git"
highlight_mode = "treesitter", -- "treesitter" (default) or "difftastic"
hunk_wrap_file = true, -- Next hunk at last hunk goes to next file
scroll_to_first_hunk = true, -- Auto-scroll to first hunk after opening a file (default: true)
snacks_picker = {
enabled = false, -- opt-in snacks.nvim integration (default: false)
limit = 200, -- number of revisions/commits to list in :DifftPick
jj_log_revset = nil, -- optional: jj revset for picker log (nil = omit -r and use jj default)
},
keymaps = {
next_file = "]f",
prev_file = "[f",
next_hunk = "]c",
prev_hunk = "[c",
close = "q",
focus_tree = "<Tab>",
focus_diff = "<Tab>",
select = "<CR>",
goto_file = "gf",
},
tree = {
width = 40,
icons = {
enable = true, -- use nvim-web-devicons if available
dir_open = "",
dir_closed = "",
},
},
highlights = {
-- Override any highlight group (see Highlight Groups below)
-- DifftAdded = { bg = "#2d4a3e" },
},
})All options are optional. Only specify what you want to override.
The highlight_mode option controls how syntax highlighting is applied:
treesitter(default): Full syntax highlighting via Neovim's treesitter. Changes are shown with background colors.difftastic: Minimal highlighting like the CLI. No syntax colors; changes are shown with foreground colors (green/red) to make diffs more prominent.
Highlights automatically inherit from your colorscheme's semantic groups (Added, Removed, Directory, Normal) and update when you switch themes. Background colors are derived by blending the foreground color with your Normal background at 25% opacity.
Treesitter mode (background colors):
| Group | Default | Description |
|---|---|---|
DifftAdded |
Derived from Added |
Added lines background |
DifftRemoved |
Derived from Removed |
Removed lines background |
Difftastic mode (foreground colors):
| Group | Default | Description |
|---|---|---|
DifftAddedFg |
Links to Added |
Added text |
DifftRemovedFg |
Links to Removed |
Removed text |
DifftAddedInlineFg |
Added + bold |
Inline added text |
DifftRemovedInlineFg |
Removed + bold |
Inline removed text |
Tree:
| Group | Default | Description |
|---|---|---|
DifftDirectory |
Links to Directory |
Directory names |
DifftFileAdded |
Links to Added |
Added files |
DifftFileDeleted |
Links to Removed |
Deleted files |
DifftTreeCurrent |
Derived from Normal |
Current file highlight |
Picker:
| Group | Default | Description |
|---|---|---|
DifftPickerPreviewHover |
Derived from Normal |
Hovered jj revision lines in picker preview |
DifftPickerJjIconCurrent |
Links to Added |
@ icon in jj picker list |
DifftPickerJjIconImmutable |
Links to Removed |
◆ icon in jj picker list |
DifftPickerJjIconNormal |
Links to Directory |
○ icon in jj picker list |
DifftPickerJjDesc |
Derived from Normal |
Description text in jj picker list |
DifftPickerJjRevset |
Links to Identifier |
Change/revset id in jj picker list |
DifftPickerJjAge |
Links to Comment |
Age field in jj picker list |
Other:
| Group | Default | Description |
|---|---|---|
DifftFiller |
Derived from Normal |
Filler lines for alignment gaps |
