Here are some practical snippets and keymaps for common diffview workflows. Each recipe shows only the relevant options. Combine them as needed.
These keymaps are commonly used patterns for working with diffview:
-- Toggle diffview open/close
vim.keymap.set('n', '<leader>dv', '<cmd>DiffviewToggle<cr>', { desc = 'Toggle Diffview' })
-- Diff working directory
vim.keymap.set('n', '<leader>do', '<cmd>DiffviewOpen<cr>', { desc = 'Diffview open' })
vim.keymap.set('n', '<leader>dc', '<cmd>DiffviewClose<cr>', { desc = 'Diffview close' })
-- File history
vim.keymap.set('n', '<leader>dh', '<cmd>DiffviewFileHistory %<cr>', { desc = 'File history (current file)' })
vim.keymap.set('n', '<leader>dH', '<cmd>DiffviewFileHistory<cr>', { desc = 'File history (repo)' })
-- Visual mode: history for selection
vim.keymap.set('v', '<leader>dh', "<Esc><cmd>'<,'>DiffviewFileHistory --follow<CR>", { desc = 'Range history' })
-- Single line history
vim.keymap.set('n', '<leader>dl', '<cmd>.DiffviewFileHistory --follow<CR>', { desc = 'Line history' })
-- Diff against main/master branch (useful before merging)
vim.keymap.set('n', '<leader>dm', function()
-- Try main first, fall back to master
local result = vim.fn.systemlist({ 'git', 'rev-parse', '--verify', 'main' })
local ok = vim.v.shell_error == 0 and result[1] ~= nil and result[1] ~= ''
local branch = ok and 'main' or 'master'
vim.cmd('DiffviewOpen ' .. branch)
end, { desc = 'Diff against main/master' })If the right side of the diff is showing the local state of a file, you can
restore the file to the state from the left side of the diff (key binding X
from the file panel by default). The current state of the file is stored in the
git object database, and a command is echoed that shows how to undo the change.
The hooks table allows you to define callbacks for various events emitted
from Diffview. The available hooks are documented in detail in
:h diffview-config-hooks. The hook events are also available as User
autocommands. See :h diffview-user-autocmds for more details.
Examples:
hooks = {
diff_buf_read = function(bufnr)
-- Change local options in diff buffers
vim.opt_local.wrap = false
vim.opt_local.list = false
vim.opt_local.colorcolumn = { 80 }
end,
view_opened = function(view)
print(
("A new %s was opened on tab page %d!")
:format(view.class:name(), vim.api.nvim_tabpage_get_number(view.tabpage))
)
end,
}Minimal / Clean
Strip away visual noise and auto-clean resources on close.
require("diffview").setup({
show_help_hints = false,
hide_merge_artifacts = true,
clean_up_buffers = true,
auto_close_on_empty = true,
})PR Review
Optimised for reviewing pull requests against a base branch. --imply-local
makes the right-side buffer editable so you can fix things as you review.
require("diffview").setup({
default_args = {
DiffviewOpen = { "--imply-local" },
},
file_panel = {
show_branch_name = true,
always_show_sections = true,
},
})Open with a symmetric range to see only the changes introduced by the branch:
:DiffviewOpen origin/main...HEAD
PR Review Progress Tracking
Use file selections (<Space> key) to track which files you've reviewed.
Selected files show a ■ indicator; directories show ■ when all
files are selected or ▣ when some are.
To persist your progress across Neovim restarts, enable
persist_selections:
require("diffview").setup({
persist_selections = { enabled = true },
})The DiffviewSelectionChanged User autocmd fires whenever selections
change, allowing external plugins to react:
-- Example: log selection changes (replace with your own integration).
vim.api.nvim_create_autocmd("User", {
pattern = "DiffviewSelectionChanged",
callback = function()
local sel = require("diffview.api").selections
local paths = sel.get_paths()
vim.notify(
#paths > 0
and ("Reviewed: " .. table.concat(paths, ", "))
or "No files marked as reviewed"
)
end,
})The public selections API (see :h diffview-selections-api) provides
stable functions for programmatic access:
local sel = require("diffview.api").selections
sel.get() -- { { path = "...", kind = "working" }, ... }
sel.get_paths() -- { "src/a.lua", "src/b.lua" }
sel.is_selected("a.lua") -- true / false
sel.select({ "a.lua" }) -- additive
sel.deselect({ "a.lua" })
sel.set({ "a.lua" }) -- replace entire selection
sel.clear()
sel.any() -- true if anything is selected
sel.count() -- number of selected filesThis is useful for integrating with external review tools. For example, to save and restore selections across a diffview reopen:
local sel = require("diffview.api").selections
local saved = sel.get_paths()
-- ... close and reopen diffview ...
sel.set(saved)You can also replace the revision range in-place with set_revs
(see :h diffview-set-revs-api), which avoids the close/reopen
entirely:
local api = require("diffview.api")
api.set_revs("new_base_sha..HEAD")Combining both APIs enables a seamless rebase workflow for tools like gitlab.nvim:
local api = require("diffview.api")
local sel = api.selections
-- Save reviewed files before rebase.
local reviewed = sel.get_paths()
-- ... perform server-side rebase, obtain new_base_sha ...
-- Update the diff range in-place (no screen reshuffling).
api.set_revs(new_base_sha .. ".." .. source_branch)
-- Restore reviewed-file marks.
sel.set(reviewed)Better Diffs
Enable enhanced highlighting and use the histogram diff algorithm for more readable diffs. Pair with diffchar.vim for character-level precision (see Companion Plugins for setup details).
require("diffview").setup({
enhanced_diff_hl = true,
diffopt = { algorithm = "histogram" },
})File History Power User
Show both numeric and bar stats, use relative dates, and reorder commit info for a denser history view.
require("diffview").setup({
file_history_panel = {
stat_style = "both",
date_format = "relative",
commit_format = { "hash", "subject", "author", "date", "ref", "reflog", "status", "files", "stats" },
},
view = {
file_history = {
layout = "diff2_vertical",
},
},
})Merge Conflict Resolution
Use a 4-way diff layout showing BASE, OURS, THEIRS, and the merge result. Winbar labels help identify each pane. Diagnostics are disabled to reduce noise during conflict resolution.
require("diffview").setup({
view = {
merge_tool = {
layout = "diff4_mixed",
disable_diagnostics = true,
winbar_info = true,
},
cycle_layouts = {
merge_tool = { "diff4_mixed", "diff3_mixed", "diff3_horizontal", "diff1_plain" },
},
},
})Telescope Integration
Use Telescope to select branches or commits for diffview:
-- Diff against a branch selected via Telescope
vim.keymap.set('n', '<leader>db', function()
require('telescope.builtin').git_branches({
attach_mappings = function(_, map)
map('i', '<CR>', function(prompt_bufnr)
local selection = require('telescope.actions.state').get_selected_entry()
require('telescope.actions').close(prompt_bufnr)
vim.cmd('DiffviewOpen ' .. selection.value)
end)
return true
end,
})
end, { desc = 'Diffview branch' })
-- File history for a commit selected via Telescope
vim.keymap.set('n', '<leader>dC', function()
require('telescope.builtin').git_commits({
attach_mappings = function(_, map)
map('i', '<CR>', function(prompt_bufnr)
local selection = require('telescope.actions.state').get_selected_entry()
require('telescope.actions').close(prompt_bufnr)
vim.cmd('DiffviewOpen ' .. selection.value .. '^!')
end)
return true
end,
})
end, { desc = 'Diffview commit' })Commit Graph
To browse a commit graph and open its commits in diffview, you have two
options. Neogit renders a graph
natively (graph_style = "unicode" or "kitty") and opens diffs through
its diffview integration. Alternatively,
gitgraph.nvim is a lightweight,
graph-only alternative.
gitgraph draws the graph and exposes on_select_commit /
on_select_range_commit hooks. Wire them to :DiffviewOpen so that pressing
<CR> on a commit or a visual selection opens it in diffview's layouts.
require("gitgraph").setup({
hooks = {
-- <CR> on a commit: show that commit's own changes.
on_select_commit = function(commit)
vim.cmd("DiffviewOpen " .. commit.hash .. "^!")
end,
-- <CR> over a visual range: diff the whole selected range.
on_select_range_commit = function(from, to)
vim.cmd("DiffviewOpen " .. from.hash .. "~1.." .. to.hash)
end,
},
})
vim.keymap.set("n", "<leader>dg", function()
require("gitgraph").draw({}, { all = true, max_count = 5000 })
end, { desc = "Commit graph" })gitgraph can render the graph with plain box-drawing characters or with Kitty
terminal branch glyphs via its symbols table; see its README for details.