imagediff

package
v0.28.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package imagediff is a pure layout module for the inline image-diff feature. It takes raw old/new image bytes plus the available terminal cell space and produces a RenderPlan describing how (or whether) to lay the comparison out — no terminal I/O, no git knowledge, no bubbletea knowledge.

Index

Constants

View Source
const SettleDebounce = 80 * time.Millisecond

SettleDebounce is how long the overlay waits after the last disrupting event before computing and placing a new image-diff overlay (ADR 0010's lifecycle rule — short enough to feel immediate, long enough to absorb a stream of j/k movement without thrashing).

Variables

This section is empty.

Functions

func DefaultDetectCapability

func DefaultDetectCapability() kittygraphics.Capability

DefaultDetectCapability detects the host terminal's kitty-graphics capability using stdout's TIOCGWINSZ for the cell pixel size. It is the production detector both diff-panel hosts pass to NewOverlay; tests inject a fake.

func HasImageExtension

func HasImageExtension(path string) bool

HasImageExtension reports whether path's extension is in the image-diff allowlist. It is the first (cheapest) gate of image-diff eligibility, shared by every host.

func WriteToStdout

func WriteToStdout(data []byte)

WriteToStdout writes raw kitty escape sequences to stdout, as a side effect outside bubbletea's render loop (ADR 0010). It is the production writer both hosts pass to NewOverlay.

Types

type Layout

type Layout int

Layout describes how the old/new images are arranged within the available space.

const (
	// LayoutFallback means the caller should render binarySummaryLine() instead.
	LayoutFallback Layout = iota
	// LayoutSideBySide places both images side by side, each scaled to its own aspect ratio.
	LayoutSideBySide
	// LayoutCentered places a single image centered in the available space (added/deleted file).
	LayoutCentered
)

type Overlay

type Overlay struct {
	// contains filtered or unexported fields
}

Overlay is the reusable controller for the inline image-diff kitty overlay (ADR 0010). It owns the eager-clear / debounced-replace lifecycle, the settle timer, the cached terminal capability, and the place/clear commands. It is embedded by any diff panel that opts in (status, commit) and parameterized by host callbacks supplied at construction (write bytes, detect capability) plus a SettleHost passed at settle time (selection, geometry, blobs, modal state).

The host that paints the overlay must report its diff body geometry in absolute screen cells; a panel composed into a split view learns its origin from the container (see the "Screen origin" glossary entry).

func NewOverlay

func NewOverlay(writeBytes func(data []byte), detectCapability func() kittygraphics.Capability) Overlay

NewOverlay builds an Overlay wired to host-specific I/O: writeBytes pushes raw kitty escape sequences to the terminal (outside bubbletea's render loop), and detectCapability probes the host terminal's graphics support once.

func (*Overlay) Capability

func (o *Overlay) Capability() kittygraphics.Capability

Capability detects (and caches) the host terminal's kitty-graphics capability, mirroring ui.DetectTerminal's caching of $KITTY_*/$TMUX checks — detection runs at most once per Overlay value lineage.

func (Overlay) Dirty

func (o Overlay) Dirty() bool

Dirty reports whether a disrupting event has been flagged since the last Disrupt.

func (Overlay) Disrupt

func (o Overlay) Disrupt(enabled bool) (Overlay, tea.Cmd)

Disrupt implements ADR 0010's eager-clear / debounced-replace lifecycle rule. It is invoked centrally from the host's Update whenever dirty was set by a disrupting event: any active placements are cleared immediately and unconditionally, and (when enabled) a new settle debounce is (re)started so a fresh placement can be computed once the model stops moving. enabled mirrors the host's image-diffs config toggle.

func (Overlay) FallbackPath

func (o Overlay) FallbackPath() string

FallbackPath returns the path the most recent settle marked as fallback (the host renders the binary summary line for it instead of reserving overlay space).

func (Overlay) HandleSettle

func (o Overlay) HandleSettle(msg SettleMsg, host SettleHost) (Overlay, tea.Cmd)

HandleSettle runs once the debounce timer from the most recent disrupting event elapses. If the model has settled (msg.Seq still current), the selection is still an image-diff candidate, no modal is open, and geometry is stable, it fetches the blobs, computes a layout plan, and either emits a placement command or records the file as a fallback (rendered as the binary summary line by the host's View).

func (Overlay) HasActivePlacements

func (o Overlay) HasActivePlacements() bool

HasActivePlacements reports whether any kitty placements are currently on screen.

func (*Overlay) MarkDirty

func (o *Overlay) MarkDirty()

MarkDirty flags a disrupting event. The host's Update wrapper consumes it via Disrupt.

func (Overlay) OnDeactivate

func (o Overlay) OnDeactivate() tea.Cmd

OnDeactivate clears any active placements when the host page is switched away from — a disrupting event per ADR 0010, since the overlay would otherwise be left floating over whatever the next page renders. Clearing is eager and unconditional.

func (Overlay) SettleSeq

func (o Overlay) SettleSeq() int

SettleSeq returns the current settle-debounce sequence number, so callers can construct a matching SettleMsg (used by tests that bypass the real timer).

type Placement

type Placement struct {
	Col, Row int
	SpanCols int
	SpanRows int
}

Placement describes where and at what cell-span an image should be rendered, relative to the top-left corner of the available area.

type RenderPlan

type RenderPlan struct {
	Fallback bool
	Layout   Layout

	// Old/New are nil when that side isn't present (added/deleted file) or the
	// overall plan is a fallback.
	Old *Placement
	New *Placement
}

RenderPlan is the layout decision for a single image-diff comparison.

func Plan

func Plan(old, new []byte, availCols, availRows int, pxPerCol, pxPerRow float64) RenderPlan

Plan decodes old and new image bytes and decides how to lay out a side-by-side (or single centered) comparison within availCols x availRows terminal cells, given the host's pixel-per-cell ratio (pxPerCol x pxPerRow).

Whenever either side fails to decode, both sides are absent, or the combined input exceeds the size cap, Plan returns a fallback plan — the caller's job reduces to a single `if plan.Fallback { show binarySummaryLine() }` branch.

type SettleHost

type SettleHost interface {
	// SelectedImageFile returns the selected file's path and whether it is an
	// image-diff candidate (extension allowlist + config enabled + terminal
	// supported). ("", false) skips placement entirely.
	SelectedImageFile() (path string, ok bool)
	// ModalOpen reports whether a modal currently occludes the diff panel — a
	// kitty placement would paint over it at the graphics layer, so the overlay
	// waits for the modal to close (which is itself a disrupting event).
	ModalOpen() bool
	// PanelGeometry returns the absolute screen rect of the diff body: the
	// top-left cell plus the available column/row span.
	PanelGeometry() (originCol, originRow, availCols, availRows int, ok bool)
	// FetchBlobs returns the old/new image bytes for the selected file. oldOK /
	// newOK report side presence (false for the absent side of an added/deleted
	// file — an expected state, not an error).
	FetchBlobs() (old, newBytes []byte, oldOK, newOK bool)
}

SettleHost supplies the model-state-dependent inputs the overlay needs at place time. The host implements it (usually the diff panel's Model value); the overlay calls the methods lazily and in order so expensive work (blob fetch) only runs once the cheap gates pass.

type SettleMsg

type SettleMsg struct{ Seq int }

SettleMsg fires when the debounce timer started by the most recent disrupting event elapses. Seq is compared against Overlay.settleSeq so stale timers (superseded by a later disrupting event) are ignored. It is exported so host containers that don't broadcast unknown messages (log, stash) can forward it explicitly to the panel that owns the overlay.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL