Tips and FAQ
July 13, 2026 · View on GitHub
Common questions, useful patterns, and known compatibility issues.
General Tips
- Hide untracked files:
DiffviewOpen -uno
- Exclude certain paths:
DiffviewOpen -- :!exclude/this :!and/this
- Run as if git was started in a specific directory:
DiffviewOpen -C/foo/bar/baz
- Diff the index against a git rev:
DiffviewOpen HEAD~2 --cached- Defaults to
HEADif no rev is given.
- Compare against merge-base (PR-style diff):
DiffviewOpen origin/main...HEAD --merge-base- Shows only changes introduced since branching.
- Use as a merge tool from the command line:
:DiffviewOpenautomatically detects conflicts during a merge, rebase, cherry-pick, or revert, so it can replacegit mergetool. Add a git alias for convenience:# In ~/.gitconfig: [alias] diffview = "!nvim -c DiffviewOpen"- Then run
git diffviewafter a conflicted merge or rebase. Stage resolved files with-in the file panel before quitting, or withgit addafterwards.
- Trace line evolution:
- Visual select lines, then
:'<,'>DiffviewFileHistory --follow - Or for single line:
:.DiffviewFileHistory --follow
- Visual select lines, then
- Diff two arbitrary files (like
vimdiff)::DiffviewDiffFiles file1 file2- This works without a VCS repository.
- To use it as a replacement for
nvim -d, add a shell function:dvdiff() { nvim -c "DiffviewDiffFiles ${1// /\\ } ${2// /\\ }" } - Then run
dvdiff file1 file2from the command line.
Understanding Revision Arguments
DiffviewOpen HEAD~5compares HEAD~5 to working tree (all changes since)DiffviewOpen HEAD~5..HEADcompares HEAD~5 to HEAD (excludes working tree changes)DiffviewOpen HEAD~5^..HEAD~5shows changes within that single commit- For viewing a specific commit's changes, use
DiffviewFileHistoryinstead
FAQ
- Q: How do I get the diagonal lines in place of deleted lines in
diff-mode?
- A: Change your
:h 'fillchars':- (vimscript):
set fillchars+=diff:╱ - (Lua):
vim.opt.fillchars:append { diff = "╱" }
- (vimscript):
- Note: whether or not the diagonal lines will line up nicely will depend on your terminal emulator. The terminal used in the screenshots is Kitty.
- A: Change your
- Q: How do I jump between hunks in the diff?
- A: Use
[cand]c :h jumpto-diffs
- A: Use
Diff Display
- Inline (unified) diff:
- Use the
diff1_inlinelayout to display diffs in a single window, with deletions rendered as virtual lines above the corresponding position and intra-line changes highlighted withDiffText. - To make it the default view:
view.default.layout = "diff1_inline". The layout is automatically appended toview.cycle_layouts.defaultif missing, sog<C-x>cycles back to it without extra config. - To cycle through it alongside side-by-side layouts, list them all
explicitly:
require("diffview").setup({ view = { cycle_layouts = { default = { "diff2_horizontal", "diff1_inline" }, }, }, }) - Navigate hunks with
]c/[c(mapped tonext_inline_hunkandprev_inline_hunk). - Inline is not available in the merge tool (it needs a 2-way diff).
- Use the
- Overleaf-style inline diff (strikethrough for deletions):
- Set
view.inline.style = "overleaf"to render deleted characters as inline virtual text with strikethrough, next to the added characters they were replaced by. Whole-line deletions are also shown with a strikethrough instead of a plain delete background.require("diffview").setup({ view = { default = { layout = "diff1_inline" }, inline = { style = "overleaf" }, }, }) - Customise the strikethrough via
DiffviewDiffDeleteInline(see the next entry on overriding inline groups).
- Set
- Customise inline char-level highlights:
- In the
diff1_inlinelayout, changed characters use these groups: the unified style highlights them withDiffviewDiffTextInline, while the "overleaf" style usesDiffviewDiffAddInlinefor added chars andDiffviewDiffDeleteInlinefor the strikethrough deletions. Their backgrounds derive by default fromDiffText,DiffviewDiffAdd, andDiffviewDiffDeleterespectively. The unified group tracksDiffText(as the built-in side-by-side diff does), so changes stay visible against the paired-rowDiffviewDiffChangebackdrop even when your colourscheme givesDiffAddandDiffChangesimilar tints (e.g. tokyonight); it falls back toDiffAddfor schemes that leaveDiffTextunset. - These groups are re-derived on every colourscheme change, so set
overrides from a
ColorSchemeautocmd rather than once at startup (a plain:hiis overwritten on the next change). Register the autocmd afterrequire("diffview").setup({...})so it runs after diffview's own rebuild and has the final say:require("diffview").setup({...}) vim.api.nvim_create_autocmd("ColorScheme", { callback = function() vim.api.nvim_set_hl(0, "DiffviewDiffTextInline", { bg = "#3a4a3a" }) vim.api.nvim_set_hl(0, "DiffviewDiffAddInline", { bg = "#2e4326" }) vim.api.nvim_set_hl(0, "DiffviewDiffDeleteInline", { bg = "#552020", fg = "#ff8080", strikethrough = true, }) end, }) DiffviewDiffTextInlinedoes not track later overrides toDiffText: its bg is derived only atsetup()and on eachColorScheme, and Neovim emits no event for a standaloneDiffTextchange. OverrideDiffviewDiffTextInlinedirectly instead.
- In the
- Better diff display (changes shown as add+delete instead of modification):
- Set Neovim's
diffoptto use a better algorithm:vim.opt.diffopt:append { "algorithm:histogram" }
- Alternatives:
algorithm:patienceoralgorithm:minimal - This affects how Neovim's built-in diff mode displays changes.
- Set Neovim's
- VSCode-style character-level highlighting:
- diffchar.vim enhances diff
mode with precise character and word-level highlighting. It automatically
activates in diff mode, adding a second layer of highlights on top of
Neovim's built-in line-level
DiffChangebackgrounds. This gives VSCode-style dual-layer highlighting: light backgrounds for changed lines plus fine-grained highlights for the exact characters that differ. - diffchar.vim works with diffview out of the box. Install the plugin and
open a diff -- no additional configuration is needed. Note that
diffchar.vim only applies to diff-mode layouts (
diff2_*,diff3_*,diff4_*); thediff1_inlinelayout renders inline changes via extmarks rather than Neovim's diff mode, so diffchar.vim has no effect there -- see Customise inline char-level highlights above fordiff1_inline's built-in mechanism. You may want to enable visual indicators next to deleted characters to get VSCode-style character-level diffs, or disable diffchar's default keymaps (<leader>g,<leader>p) if they conflict with your mappings:{ 'rickhowe/diffchar.vim', config = function() -- Use bold/underline on adjacent chars instead of virtual blank columns. vim.g.DiffDelPosVisible = 1 -- Disable diffchar default keymaps. -- See: https://github.com/rickhowe/diffchar.vim/issues/21 vim.cmd([[ nmap <leader>g <Nop> nmap <leader>p <Nop> ]]) end, } - diffchar supports multiple diff granularities via
g:DiffUnit:'Char'(character-level),'Word1'(words separated by non-word characters),'Word2'(whitespace-delimited words), and custom delimiter patterns. It also offers multi-colour matching viag:DiffColorsto visually correlate corresponding changed units across windows.
- diffchar.vim enhances diff
mode with precise character and word-level highlighting. It automatically
activates in diff mode, adding a second layer of highlights on top of
Neovim's built-in line-level
LSP and Formatting in Diff Buffers
- LSP clients are automatically detached from non-working-tree diff buffers
(those with
diffview://URIs). This prevents errors from LSP servers that do not support the custom URI scheme, and avoids incorrect LSP features on historical content. - Auto-formatting is disabled on these buffers (
vim.b.autoformat = false). - Inlay hints are automatically disabled for non-working-tree buffers to prevent position mismatch errors.
- Diagnostics and other LSP features only appear for the working tree (LOCAL)
side of diffs. To see them, compare against the working tree:
DiffviewOpen main(notmain..HEAD).
Neogit Integration
- Configure Neogit with
integrations = { diffview = true }for seamless integration.
Keymap Configuration
The keymaps config is structured as a table with sub-tables for various
different contexts where mappings can be declared. In these sub-tables
key-value pairs are treated as the {lhs} and {rhs} of a normal mode
mapping. The implementation uses vim.keymap.set() (which implies noremap),
and all mappings use silent. In most contexts, nowait is also set. The
{rhs} can be either a vim command in the form of a string, or a lua
function:
view = {
-- Vim command:
["a"] = "<Cmd>echom 'foo'<CR>",
-- Lua function:
["b"] = function() print("bar") end,
}
For more control (i.e. mappings for other modes), you can also define index
values as list-like tables containing the arguments for vim.keymap.set().
This way you can also change all the :map-arguments with the only exception
being the buffer field, as this will be overridden with the target buffer
number:
view = {
-- Normal and visual mode mapping to vim command:
{ { "n", "v" }, "<leader>a", "<Cmd>echom 'foo'<CR>", { silent = true } },
-- Visual mode mapping to lua function:
{ "v", "<leader>b", function() print("bar") end, { nowait = true } },
}
To disable any single mapping without disabling them all, set its {rhs} to
false:
view = {
-- Disable the default normal mode mapping for `<tab>`:
["<tab>"] = false,
-- Disable the default visual mode mapping for `gf`:
{ "x", "gf", false },
}
Most of the mapped file panel actions also work from the view if they are added
to the view maps (and vice versa). The exception is for actions that only
really make sense specifically in the file panel, such as next_entry,
prev_entry. Actions such as toggle_stage_entry and restore_entry work
just fine from the view. When invoked from the view, these will target the file
currently open in the view rather than the file under the cursor in the file
panel.
For more details on how to set mappings for other modes, actions, and more see:
:h diffview-config-keymaps:h diffview-actions
Customizing Default Keymaps
The default keymaps (<leader>e, <leader>b, <leader>c*) may conflict
with your configuration. Override them in your setup:
local actions = require("diffview.actions")
require("diffview").setup({
keymaps = {
view = {
-- Use localleader instead to avoid conflicts
{ "n", "<localleader>e", actions.focus_files },
{ "n", "<localleader>b", actions.toggle_files },
-- Or disable specific mappings
{ "n", "<leader>e", false },
},
},
})
Platform Notes
- MSYS2/Cygwin on Windows:
- If you use MSYS2 or Cygwin git with native Windows Neovim, path conversion
is handled automatically via
cygpath. Ensurecygpathis on yourPATH. Alternatively, install Git for Windows which uses native Windows paths and avoids the issue entirely.
- If you use MSYS2 or Cygwin git with native Windows Neovim, path conversion
is handled automatically via
Known Compatibility Issues
Some plugins may conflict with diffview's window layout or keymaps. Here are known issues and workarounds:
-
lens.vim (automatic window resizing):
- camspiers/lens.vim automatically resizes windows based on focus, which interferes with diffview's layout.
- Workaround: Configure lens.vim to exclude diffview filetypes:
-- In your lens.vim or lens.nvim config: vim.g['lens#disabled_filetypes'] = { 'DiffviewFiles', 'DiffviewFileHistory', 'DiffviewFileHistoryPanel' }
-
which-key.nvim shows
diffview_ignoreentries:- In a diffview buffer, which-key lists several
z*fold commands with the descriptiondiffview_ignore. Filter them out in your setup:require("which-key").setup({ filter = function(mapping) return mapping.desc ~= "diffview_ignore" end, })
- In a diffview buffer, which-key lists several
-
Scrollbind misalignment with context or winbar plugins:
-
Plugins that add lines at the top of windows (code context, breadcrumbs) cause the diff panes to fall out of visual sync.
-
nvim-treesitter-context: Two steps are needed. First, configure treesitter-context to disable itself for diffview buffers using the
on_attachcallback:require("treesitter-context").setup({ on_attach = function(buf) return not vim.b[buf].ts_context_disable end, })Then add diffview hooks to force treesitter-context to re-evaluate
on_attachat the right times. This is necessary because treesitter-context only evaluateson_attachonce per buffer (onBufReadPost), so working-tree files that were loaded before diffview opened would otherwise keep context enabled:require("diffview").setup({ hooks = { diff_buf_win_enter = function(bufnr, winid, ctx) -- Re-trigger treesitter-context's on_attach evaluation. -- The group name is an internal detail of nvim-treesitter-context -- and may differ across versions; verify it matches your install -- or omit it to fire all BufReadPost handlers. -- `modeline = false` prevents `nvim_exec_autocmds`'s default -- post-autocmd modeline pass from running `:set` commands (e.g. -- `fileencoding`) against the diffview buffer, which is -- non-modifiable and would raise E21. pcall(vim.api.nvim_exec_autocmds, "BufReadPost", { buffer = bufnr, group = "treesitter_context_update", modeline = false, }) end, view_closed = function() local ok, tsc = pcall(require, "treesitter-context") if ok and tsc.enabled() then tsc.enable() end end, }, }) -
barbecue.nvim and other winbar plugins: Unlike treesitter-context, barbecue resets the winbar on every
CursorMovedandBufWinEnter, so clearing it per-window is not sufficient. Instead, toggle barbecue's visibility usingview_enter/view_leavehooks (these fire when switching to and from the diffview tab):require("diffview").setup({ hooks = { view_enter = function() pcall(function() require("barbecue.ui").toggle(false) end) end, view_leave = function() pcall(function() require("barbecue.ui").toggle(true) end) end, }, })
-
-
vim-markdown (preservim/vim-markdown):
- vim-markdown's
after/ftplugin/markdown.vimsetsfoldmethod=exprwith a markdown section foldexpr. In diff buffers this would collapse markdown sections and hide diff content. Diffview suppresses the syntheticFileTypeevent that would otherwise let that ftplugin run on diffview buffers, sofoldmethod=diffis preserved and vim-markdown's folds are not applied. - If you still see section folds in diff buffers (e.g. because another
plugin re-fires
FileTypeon the buffer), raise the initial fold level:require("diffview").setup({ view = { foldlevel = 99 }, }) - To raise the fold level only for markdown diff buffers (leaving the
default of 0 in place for other filetypes), use the
diff_buf_win_enterhook instead:require("diffview").setup({ hooks = { diff_buf_win_enter = function(bufnr, winid) if vim.bo[bufnr].filetype == "markdown" then vim.wo[winid].foldlevel = 99 end end, }, })
- vim-markdown's
-
Plugins that act on the current window/buffer (fzf-lua, blame.nvim, etc.):
- Panel windows are pinned to their buffer via
winfixbuf, so plugins that try to load a different buffer into the current window (e.g., fzf-lua) will fail when invoked from the panel. - Plugins that run a job against the current buffer's name (e.g.,
blame.nvim) will fail
because the panel buffer is
nofile. - Workaround: wrap the offending keymaps with a helper that, when
invoked from a diffview panel, first focuses the diff's main window.
This both avoids the
winfixbuferror and makes the picked file open in the right place:local function in_diff_window(fn) return function() if vim.wo.winfixbuf then local ok, lib = pcall(require, "diffview.lib") if ok then local view = lib.get_current_view() if view and view.cur_layout then local main = view.cur_layout:get_main_win() if main and main.id and vim.api.nvim_win_is_valid(main.id) then vim.api.nvim_set_current_win(main.id) end end end end fn() end end vim.keymap.set("n", "<a-f>", in_diff_window(function() require("fzf-lua").files() end)) vim.keymap.set("n", "<a-b>", in_diff_window(function() vim.cmd("BlameToggle") end)) - If you would rather not use the wrapper, stickybuf.nvim will bounce the buffer to a non-panel window (so files at least open somewhere, though not necessarily the diff's main window).
- Panel windows are pinned to their buffer via