- Parse and display difftastic output with full ANSI color support
- Navigate between file changes with keybindings
- Jump to changed files directly from diff view
- Customizable window layouts (buffer, float, ivy-style)
- File header customization support
- Neovim 0.10+
- Difftastic (external diff tool)
difft --color=always $left $rightreturn {
"ahkohd/difft.nvim",
config = function()
require("difft").setup()
end,
}--luacheck: globals Difft
return {
"ahkohd/difft.nvim",
keys = {
{
"<leader>d",
function()
if Difft.is_visible() then
Difft.hide()
else
Difft.diff()
end
end,
desc = "Toggle Difft",
},
},
config = function()
require("difft").setup({
command = "jj diff --no-pager", -- or "git diff"
layout = "float", -- nil (buffer), "float", or "ivy_taller"
})
end,
}--luacheck: globals Difft
return {
"ahkohd/difft.nvim",
keys = {
{
"<leader>d",
function()
if Difft.is_visible() then
Difft.hide()
else
Difft.diff()
end
end,
desc = "Toggle Difft",
},
},
config = function()
require("difft").setup({
layout = "ivy_taller",
no_diff_message = "All clean! No changes detected.",
loading_message = "Loading diff...",
window = {
number = false,
relativenumber = false,
border = "rounded",
},
--- Custom header content with webdev icons
header = {
content = function(filename, step, _language)
local devicons = require("nvim-web-devicons")
local basename = vim.fn.fnamemodify(filename, ":t")
local icon, hl = devicons.get_icon(basename)
-- Get the bg from FloatTitle (what DifftFileHeader links to)
local header_hl = vim.api.nvim_get_hl(0, { name = "FloatTitle", link = false })
-- Create custom highlight with devicon fg + header bg
local icon_hl = hl
if hl and header_hl.bg then
local devicon_colors = vim.api.nvim_get_hl(0, { name = hl })
if devicon_colors.fg then
local custom_hl_name = "DifftIcon_" .. hl
vim.api.nvim_set_hl(0, custom_hl_name, {
fg = devicon_colors.fg,
bg = header_hl.bg,
})
icon_hl = custom_hl_name
end
end
local result = {}
table.insert(result, { " " })
table.insert(result, { icon and (icon .. " ") or "", icon_hl })
table.insert(result, { filename })
table.insert(result, { " " })
if step then
table.insert(result, { "• " })
table.insert(result, { tostring(step.current) })
table.insert(result, { "/" })
table.insert(result, { tostring(step.of) })
table.insert(result, { " " })
end
return result
end,
highlight = {
link = "FloatTitle",
full_width = true,
},
},
})
end,
}layout = nil -- Open in current buffer
layout = "float" -- Centered floating window
layout = "ivy_taller" -- Bottom window (ivy-style)window = {
width = 0.9, -- Float window width (0-1)
height = 0.8, -- Float window height (0-1)
title = " Difft ", -- Window title
number = false, -- Show line numbers
relativenumber = false,
border = "rounded", -- Border style: "none", "single", "double", "rounded", "solid", "shadow", or custom array
}keymaps = {
next = "<Down>", -- Next file change
prev = "<Up>", -- Previous file change
close = "q", -- Close diff window (float only)
refresh = "r", -- Refresh diff
first = "gg", -- First file change
last = "G", -- Last file change
}jump = {
enabled = true, -- Enable file jumping
["<CR>"] = "edit", -- Open file in current window
["<C-v>"] = "vsplit", -- Open file in vertical split
["<C-x>"] = "split", -- Open file in horizontal split
["<C-t>"] = "tabedit", -- Open file in new tab
}header = {
content = function(filename, step, language)
if step then
return string.format("[%d/%d] %s (%s)", step.current, step.of, filename, language)
end
return string.format("%s (%s)", filename, language)
end,
highlight = {
link = "FloatTitle",
full_width = true,
},
}header = {
content = function(filename, step, language)
local devicons = require("nvim-web-devicons")
local basename = vim.fn.fnamemodify(filename, ":t")
local icon, hl = devicons.get_icon(basename)
local result = {}
table.insert(result, { " " })
table.insert(result, { icon and (icon .. " ") or "", hl })
table.insert(result, { filename })
if step then
table.insert(result, { " • " })
table.insert(result, { tostring(step.current) })
table.insert(result, { "/" })
table.insert(result, { tostring(step.of) })
end
return result
end,
highlight = {
link = "FloatTitle",
full_width = true,
},
}-- Link to existing highlight group
highlight = {
link = "FloatTitle",
full_width = false,
}
-- Custom colors
highlight = {
fg = "#ffffff",
bg = "#5c6370",
full_width = true,
}
-- Link colors separately
highlight = {
fg = { link = "Statement" },
bg = { link = "Visual" },
full_width = false,
}command = "jj diff --no-pager" -- Diff command to execute
auto_jump = true -- Jump to first change on open
no_diff_message = "No changes found"
loading_message = "Loading diff..."
refresh_on_resize = true -- Auto-refresh on window resize-- Open diff
require("difft").diff()
-- Open with custom command
require("difft").diff({ cmd = "git diff" })
-- Close diff
require("difft").close()
-- Hide diff (float only, keeps buffer)
require("difft").hide()
-- Refresh current diff
require("difft").refresh()
-- Check if diff exists
if require("difft").exists() then
-- ...
end
-- Check if diff is visible
if require("difft").is_visible() then
-- ...
endThe plugin also exposes a global Difft table:
Difft.diff()
Difft.close()
Difft.hide()
Difft.refresh()
Difft.exists()
Difft.is_visible()vim.keymap.set("n", "<leader>d", function()
if Difft.is_visible() then
Difft.hide()
else
Difft.diff()
end
end, { desc = "Toggle difft" })When viewing a diff:
<Down>/<Up>- Navigate between file changesgg/G- Jump to first/last change<CR>- Open file at cursor (jump to changed line)<C-v>/<C-x>/<C-t>- Open file in split/tabr- Refresh diffq- Close diff (floating windows only)
The plugin uses these highlight groups for diff content:
DiffAdd- Added linesDiffDelete- Deleted linesDiffChange- Changed linesDifftFileHeader- File headers (customizable)DifftFileHeaderBg- Header background for full-width mode
Run tests with:
nvim -l tests/run.luaSee tests/README.md for details.