Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 106 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ documents without distorting either one.
and resolve-on-write staging.
- Recursive folder comparison with efficient external digest checks and child
file diffs that return to the folder view.
- One-column overview gutters that show changed regions proportionally across
each document.
- A minimap for your ordinary editor windows (`:DiffBanditMinimap`): git hunks
against the index, diagnostics, search matches, the visible range, and, with
image connectors, the code's shape in syntax colours.
- Overview gutters (minimaps) that show changed regions proportionally across
each document, with the visible range, visual selection, and search matches.
Click or drag in them to jump, and scroll the pane with the wheel. With image
connectors they are drawn at pixel resolution, so every hunk keeps its own
mark, and can show the code's shape in syntax colours.
- Binary file support through a read-only hex comparison view.
- Theme-friendly semantic colors derived from the active colorscheme, with
optional overrides.
Expand Down Expand Up @@ -195,6 +201,30 @@ diagnostics. The left side of two-way diffs and the local/remote sides of merge
views keep their filetype and syntax highlighting, while warnings and
completion remain focused on the editable target/result pane.

## Editor Minimap

The diff view's overview gutters are also available as a minimap in ordinary
editor windows. `:DiffBanditMinimap` toggles it for the current window
(`:DiffBanditMinimap open|close`, or `require("diffbandit").minimap(action)`),
and `minimap.auto = true` opens it for every eligible window.

- It is a float over the window's right edge, so it never changes your window
layout. It covers the last few text columns, and hides when the window is
narrower than `minimap.min_window_width`.
- It shows hunks of the buffer against the git index, computed by DiffBandit
(no gitsigns needed). Hunks update shortly after edits and when the index
changes (write, buffer enter, focus return). The hunk under the cursor is
outlined.
- It also shows diagnostics (warnings and errors by default), search matches,
the visual selection, the visible range and the cursor line.
- With image connectors it draws the code's shape in syntax colours, 12 cells
wide. Otherwise it is a one-column cell strip.
- Click or drag in it to jump, and scroll the window with the wheel over it.
It adds no mappings to your buffers: clicks are handled when they enter the
minimap, which hands focus back on release.
- Help, quickfix and other special buffers, excluded filetypes, floats, and
DiffBandit's own tabs are skipped.

## Git Modes

`:DiffBanditGit` accepts common diff scopes:
Expand Down Expand Up @@ -533,10 +563,21 @@ require("diffbandit").setup({
image_colorspace = "auto", -- "srgb" | "display-p3" (auto: P3 for Ghostty on macOS)
},
split_blend = 0.3,
overview = {
overview = { -- the diff view's overview gutters
enabled = true,
width = 1,
cursor = true,
viewport = true, -- shade the rows covering the visible range
renderer = "auto", -- "auto" (image when the connector is) | "cells"
min_reduction = nil, -- lines per row below which short files stop stretching (nil = auto)
search = true, -- show 'hlsearch' matches
selection = true, -- show the visual selection
mouse = true, -- click/drag to jump, wheel to scroll the pane
code_shape = true, -- image renderer: draw the code's shape in syntax colours (false: hunk strip)
code_width = 12, -- overview width (cells) with code_shape
focus_follow = true, -- code shape: only the focused pane's strip is full width
compact_width = 4, -- the other pane's strip: the same shape, squeezed
code_debounce_ms = 400, -- rebuild the code shape once edits pause
},
status = {
enabled = true,
Expand Down Expand Up @@ -565,6 +606,34 @@ require("diffbandit").setup({
})
```

The editor minimap (`:DiffBanditMinimap`) has its own section with the same
rendering options as `ui.overview`:

```lua
require("diffbandit").setup({
minimap = {
auto = false, -- open for every eligible window
renderer = "auto", -- "auto" (image when supported) | "cells"
width = 1, -- cell-strip width
code_shape = true, -- image: draw the code's shape in syntax colours
code_width = 12,
min_reduction = nil,
viewport = true,
cursor = true,
search = true,
selection = true,
mouse = true,
git = true, -- hunks against the git index
git_debounce_ms = 300,
diagnostics = { enabled = true, min_severity = "warn" },
min_window_width = 60,
exclude_filetypes = { "help", "qf", "netrw", "gitcommit", "gitrebase" },
zindex = 20,
},
})
```


Theme colors are derived from the active colorscheme's diff highlight groups.
Use `ui.theme.colors` for semantic color overrides, or `ui.theme.highlights` to
override specific `DiffBandit*` highlight groups.
Expand Down Expand Up @@ -614,6 +683,39 @@ cell renderer is used, unchanged.
set `vim.g.diffbandit_connector_renderer` or
`DIFFBANDIT_CONNECTOR_RENDERER`. Inside tmux, auto mode uses cells.
- If the terminal rejects an image later, every open view falls back to cells.
- The overview gutters become images too (`ui.overview.renderer = "auto"`).
In the cell overview a long file squeezes several hunks into one cell row.
The image gives each hunk its own mark at its exact position, about 20x
the vertical resolution. One-line hunks stay at least 2px tall, and the
current chunk is outlined. The viewport band and cursor row are cell
backgrounds showing through the image, so scrolling never re-sends it.
Set `ui.overview.renderer = "cells"` to keep the cell overview.
- By default (`ui.overview.code_shape = true`) the image overview is a code
minimap (`code_width` cells, ~120 columns at 1px per character, 2px on
HiDPI); set it to `false` for the one-column hunk strip. Each
line's text is drawn in its treesitter colours, falling back to legacy
syntax for files up to 3000 lines and to the Normal colour otherwise. Hunks
are bands behind the text and search matches are translucent bands. Short
files are not stretched: by default characters are twice as tall as wide
(`ui.overview.min_reduction` sets lines per row explicitly). While you edit,
the previous shape stays until typing pauses for `code_debounce_ms`.
- Only the focused pane's code minimap is full width (`code_width`); the other
pane's is `compact_width` cells, the same shape squeezed horizontally. The
editable right side starts focused. When focus moves to the other pane the
two strips swap sizes and the layout shifts over. The panes keep equal
widths, and the shape is not rebuilt. Set `ui.overview.focus_follow = false`
to keep both strips full width.

#### Overview interaction

Both renderers shade the visible range, show the visual selection as a row
band, and mark search matches while `'hlsearch'` is active (a right-half block
in cells, a tick in the image). Click or drag in an overview to centre that
spot in its pane, and use the mouse wheel over it to scroll the pane. Clicks
resolve to the cell row under the mouse because Neovim reports no pixel
positions. Set `ui.overview.min_reduction` (lines per row) to stop short files
from stretching to fill the strip. The overview never scrolls: long files are
always shown whole.

`DIFFBANDIT_PERF=1` adds `kitty curves/raster/transmit` stage timings to the
render log. `tools/bench_kitty.lua` compares repaint cost against the cell
Expand Down Expand Up @@ -652,6 +754,7 @@ diffbandit.merge("path/to/conflicted-file")
diffbandit.commit_panel({})
diffbandit.git_panel({}) -- working-tree changes, panel open on the first file
diffbandit.launch({ "main...HEAD" }) -- what bin/diffbandit runs (git diff-style args)
diffbandit.minimap("toggle") -- editor minimap: "toggle" | "open" | "close"

-- Predicates. `is_running()` is the current tab (2-way, merge, or folder).
-- Tabpage ids and bufnrs are both integers, so overloads take a table:
Expand Down
60 changes: 59 additions & 1 deletion doc/diffbandit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ Core features:
- Optional commit panel with file staging and commit message entry.
- Merge conflict group with a local/result/remote resolver.
- Recursive folder comparison using external digest commands.
- One-column overview gutters at the outer document edges.
- Overview gutters (minimaps) at the outer document edges: visible range,
visual selection and search matches; click/drag to jump, wheel to scroll.
Pixel resolution and an optional code-shape minimap with image connectors.
- Read-only binary hex comparison view.
- Theme-friendly colors derived from the active colorscheme.

Expand Down Expand Up @@ -74,6 +76,14 @@ COMMANDS *diffbandit-commands*
:DiffBanditBuffers {left-bufnr} {right-bufnr}
Open a DiffBandit view comparing two loaded buffers.

*:DiffBanditMinimap*
:DiffBanditMinimap [toggle|open|close]
Toggle (default), open or close the minimap of the current window: a
float over its right edge with git hunks against the index,
diagnostics, search matches, the visual selection and the visible
range; the code's shape in syntax colours with image connectors.
Click or drag to jump, wheel to scroll. See |diffbandit-minimap|.

*:DiffBanditFolderDiff*
:DiffBanditFolderDiff {left-folder} {right-folder}
Open a read-only recursive folder comparison.
Expand Down Expand Up @@ -498,6 +508,27 @@ Common configuration:
enabled = true,
width = 1,
cursor = true,
-- Shade the rows covering the visible range.
viewport = true,
-- "auto": draw the overview as a pixel-resolution image whenever
-- the connector uses the Kitty image renderer; "cells": never.
renderer = "auto",
-- Lines per row below which short files stop stretching.
-- nil = auto (fill; code shape: characters twice as tall as wide).
min_reduction = nil,
search = true, -- 'hlsearch' matches
selection = true, -- visual selection
mouse = true, -- click/drag to jump, wheel to scroll the pane
-- Image renderer only: draw each line's text in syntax colours
-- (false: the one-column hunk strip).
code_shape = true,
code_width = 12,
-- Code shape: only the focused pane's strip is code_width wide; the
-- other is compact_width (the same shape, squeezed). Focus moving
-- to a pane swaps them. false: both full width.
focus_follow = true,
compact_width = 4,
code_debounce_ms = 400,
},
status = {
enabled = true,
Expand Down Expand Up @@ -538,6 +569,32 @@ and DiffChange highlight groups. Use ui.theme.colors for semantic color
overrides, or ui.theme.highlights to override specific DiffBandit* groups.

==============================================================================
EDITOR MINIMAP *diffbandit-minimap*

The minimap of ordinary editor windows shares the diff view's overview
renderer. Configure it under `minimap`:
>
minimap = {
auto = false, -- open for every eligible window
renderer = "auto", -- "auto" (image when supported) | "cells"
width = 1, -- cell-strip width
code_shape = true, -- image: code shape in syntax colours
code_width = 12,
min_reduction = nil,
viewport = true, cursor = true, search = true, selection = true,
mouse = true,
git = true, -- hunks against the git index
git_debounce_ms = 300,
diagnostics = { enabled = true, min_severity = "warn" },
min_window_width = 60,
exclude_filetypes = { "help", "qf", "netrw", "gitcommit", "gitrebase" },
zindex = 20,
}
<
It is a float over the window's right edge (the layout never changes; the
last text columns are covered) and adds no mappings to your buffers. Special
buffers, excluded filetypes, floats and DiffBandit's own tabs are skipped.

LUA API *diffbandit-lua-api*

>
Expand All @@ -554,6 +611,7 @@ LUA API *diffbandit-lua-api*
diffbandit.git_commit("HEAD")
diffbandit.git_panel({})
diffbandit.launch({ "main...HEAD" })
diffbandit.minimap("toggle") -- "toggle" | "open" | "close"
diffbandit.git_compare("main", "feature")
diffbandit.git_compare_branches({})
diffbandit.git_checkout("feature")
Expand Down
2 changes: 2 additions & 0 deletions doc/tags
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
:DiffBanditGitMenu diffbandit.txt /*:DiffBanditGitMenu*
:DiffBanditLaunch diffbandit.txt /*:DiffBanditLaunch*
:DiffBanditMerge diffbandit.txt /*:DiffBanditMerge*
:DiffBanditMinimap diffbandit.txt /*:DiffBanditMinimap*
:DiffBanditStageHunk diffbandit.txt /*:DiffBanditStageHunk*
:DiffBanditToggleStageHunk diffbandit.txt /*:DiffBanditToggleStageHunk*
:DiffBanditUndo diffbandit.txt /*:DiffBanditUndo*
Expand Down Expand Up @@ -41,6 +42,7 @@ diffbandit-launcher diffbandit.txt /*diffbandit-launcher*
diffbandit-lua-api diffbandit.txt /*diffbandit-lua-api*
diffbandit-mappings diffbandit.txt /*diffbandit-mappings*
diffbandit-merge diffbandit.txt /*diffbandit-merge*
diffbandit-minimap diffbandit.txt /*diffbandit-minimap*
diffbandit-overview diffbandit.txt /*diffbandit-overview*
diffbandit-q diffbandit.txt /*diffbandit-q*
diffbandit-testing diffbandit.txt /*diffbandit-testing*
Expand Down
58 changes: 58 additions & 0 deletions lua/diffbandit/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,33 @@ local defaults = {
enabled = true,
width = 1,
cursor = true,
-- Shade the rows covering the visible range of the pane.
viewport = true,
-- "auto" draws the overview as a pixel-resolution image whenever the
-- connector uses the Kitty image renderer (every hunk gets its own
-- mark instead of sharing a cell row); "cells" always uses cells.
renderer = "auto",
-- Lines per overview row below which a short file stops stretching
-- and takes only the top of the strip. nil = auto: always fill for
-- the hunk overview; for code shape, characters twice as tall as wide.
min_reduction = nil,
-- Show search matches ('hlsearch' active) and the visual selection.
search = true,
selection = true,
-- Click/drag in the overview to jump, mouse wheel to scroll the pane.
mouse = true,
-- Image renderer only: draw each line's text shape in syntax colours
-- (treesitter, else legacy syntax), code_width cells wide. Set false
-- for the one-column hunk strip (the cell renderer always uses it).
code_shape = true,
code_width = 12,
-- Code shape: only the focused pane's strip is code_width wide; the
-- other pane's is compact_width (the same shape, squeezed). Focus
-- moving to a pane swaps them. false: both full width.
focus_follow = true,
compact_width = 4,
-- Edits rebuild the code shape once typing pauses this long.
code_debounce_ms = 400,
-- CursorMoved overview repaint (marks already cached; cursor glyph moves).
debounce_ms = 32,
},
Expand Down Expand Up @@ -210,6 +237,37 @@ local defaults = {
close = "q",
},
},
-- Minimap for ordinary editor windows (:DiffBanditMinimap). A float on
-- the window's right edge; same rendering as the diff view's overview
-- (ui.overview), with git hunks against the index and diagnostics.
minimap = {
-- Open for every eligible window automatically (else per command).
auto = false,
-- "auto": a pixel image when the terminal supports Kitty graphics (as
-- resolved for ui.connector_renderer); "cells": a cell strip.
renderer = "auto",
-- Cell-strip width; the image draws the code's shape code_width wide.
width = 1,
code_shape = true,
code_width = 12,
min_reduction = nil,
viewport = true,
cursor = true,
search = true,
selection = true,
mouse = true,
debounce_ms = 32,
code_debounce_ms = 400,
-- Hunks against the git index, refreshed this long after edits.
git = true,
git_debounce_ms = 300,
-- Diagnostics at least this severe ("error" | "warn" | "info" | "hint").
diagnostics = { enabled = true, min_severity = "warn" },
-- Windows narrower than this get no minimap.
min_window_width = 60,
exclude_filetypes = { "help", "qf", "netrw", "gitcommit", "gitrebase" },
zindex = 20,
},
}

function M.defaults()
Expand Down
12 changes: 12 additions & 0 deletions lua/diffbandit/git/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,18 @@ function M.read_index(root, path)
return read_blob(root, ":" .. path)
end

--- Async index blob: callback(text | nil, err) on the main loop. nil text
--- means the path is not in the index (untracked) or git failed.
function M.read_index_async(root, path, callback)
return git_async(root, { "show", "--no-ext-diff", ":" .. path }, function(ok, err, output)
if ok then
callback(output or "", nil)
else
callback(nil, err)
end
end)
end

function M.read_head(root, path, base)
return read_blob(root, (base or "HEAD") .. ":" .. path)
end
Expand Down
20 changes: 20 additions & 0 deletions lua/diffbandit/highlights.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ local base_defaults = {
DiffBanditAccentText = { link = "Identifier" },
DiffBanditOverviewContext = { link = "Normal" },
DiffBanditOverviewCursor = { link = "CursorLineNr" },
DiffBanditOverviewViewport = { link = "CursorLine" },
DiffBanditOverviewCursorBand = { link = "Visual" },
DiffBanditOverviewSearch = { link = "Search" },
DiffBanditOverviewSelection = { link = "Visual" },
-- Neutral cursorline so it doesn't wash out range backgrounds
DiffBanditCursorLine = { bg = "NONE" },
}
Expand Down Expand Up @@ -341,6 +345,22 @@ local function apply_diff_variants(config)
sp = normal_fg,
bold = true,
})
-- Visible-range band, and the image overview's cursor row (a background:
-- image placeholder cells cannot carry an underline).
local viewport_bg = blend_color(normal_bg, normal_fg, 0.12)
apply_group("DiffBanditOverviewViewport", { bg = viewport_bg, fg = viewport_bg })
apply_group("DiffBanditOverviewCursorBand", { bg = blend_color(normal_bg, normal_fg, 0.4) })
-- Search matches (fg: a right-half block in cells, a tick in the image)
-- and the visual selection (a row background).
local search_color = color_to_number(adopt_diff_colors("Search").bg) or blend_color(normal_bg, 0xE5C07B, 0.85)
apply_group("DiffBanditOverviewSearch", { fg = search_color })
local visual_bg = color_to_number(adopt_diff_colors("Visual").bg) or blend_color(normal_bg, normal_fg, 0.25)
apply_group("DiffBanditOverviewSelection", { bg = visual_bg, fg = visual_bg })
local diag_fallback = { Error = 0xE06C75, Warn = 0xE5C07B, Info = 0x61AFEF, Hint = 0x56B6C2 }
for name, fallback in pairs(diag_fallback) do
local fg = color_to_number(adopt_diff_colors("Diagnostic" .. name).fg) or fallback
apply_group("DiffBanditOverviewDiag" .. name, { fg = fg })
end

-- Variants for line numbers with diff backgrounds (for visual text overlay)
local line_number_fg = get_foreground_color("LineNr", "#808080")
Expand Down
Loading
Loading