Terminal Core Contract
Behavioral contract of the standard buffer returned by TerminalBuffers.create.
Method KDoc defines individual parameters and validation. The
feature map and
gap map own capability status and
protocol scope.
Public API surfaces
TerminalBuffers.create(width, height, maxHistory) returns TerminalRenderBuffer,
which combines TerminalBuffer with TerminalRenderFrameReader over the same
state. Headless implementations may implement TerminalBuffer alone.
TerminalBuffer composes these roles:
| Role | Responsibility |
|---|---|
TerminalWriter |
Text, erase/edit operations, pen, titles, and protection |
TerminalCursor |
Positioning, movement, saved cursor, and tab stops |
TerminalModeController |
Durable modes, palette, and screen switching |
TerminalModeReader / TerminalInputState |
Typed or packed mode snapshots |
TerminalResponseChannel |
State-dependent query replies and queued bytes |
TerminalGraphics |
Complete image-command admission, placements and transfer invalidation |
TerminalReader |
Borrowed lines, palette, grid/cursor dimensions, and metadata |
TerminalInspector |
Allocating text and attribute inspection |
Depend on the narrowest role needed. The factory requires width at least
TerminalBuffers.MIN_WIDTH (two cells), positive height, and nonnegative history
capacity; maxHistory + height must fit in
Int. Zero history capacity disables primary scrollback. Creation installs no
lock: mutations, response access, cursor/grid/history reads, and borrowed frames
require external serialization. Factory mode snapshots are atomic, but cannot
make separately read grid state coherent.
Cursor positioning and cell reads use zero-based coordinates. With origin mode, positioning is relative to the top margin; with both origin and left/right margin modes, columns are relative to the left margin. Margin methods and rectangular commands retain their documented DEC one-based conventions; zero rectangle parameters mean omitted values. The host adapter performs parser-to-core coordinate conversion.
Render frame generations
Frames are valid only within their callback and must not escape it. Nested frame
reads on the same buffer throw IllegalStateException before disturbing the
outer frame. Callback failure releases the lease. Copy rows and cluster data
into caller-owned arrays before returning.
Generation values belong to one source. Compare them for equality, including across overflow; include source and active-buffer identity in retained cache keys.
| Value | Invalidation meaning |
|---|---|
frameGeneration |
Any visually relevant change |
contentGeneration |
Retained cell, authored-output provenance, or terminal-owned row-mapping changes |
structureGeneration |
Terminal shape or row mapping changes |
lineGeneration(row) |
That rendered row, including global reverse-video interpretation |
historyContentGeneration |
Per-buffer retained-history replacement, clear, or resize/reflow |
graphicsGeneration |
Image resources, placements and image geometry; text-row generations need not change |
Ordinary history admission and eviction preserve historyContentGeneration;
track row identities and discardedCount for those changes. Caller-requested
scrollback offsets do not mutate terminal state and must be included separately
in viewport cache keys. Reverse video preserves stored content, row identities,
and structure generation while invalidating rendered attributes for every row,
including history and the inactive screen. Global invalidation uses a counter
rather than walking retained rows.
outputEndAbsoluteRow is the exclusive absolute end of authored output in the
active buffer, independent of the viewport. It includes retained history,
written cells, and explicit blank linefeed sources, and excludes untouched live
capacity. Cursor motion alone does not extend it. Same-row erasure preserves
authored blanks; row replacement resets provenance. Reflow carries authored
output without treating cursor-placement or wide-wrap padding as new text.
Changes advance content and frame generations. The boundary can retreat and
does not imply that the final logical line is immutable or terminated.
TerminalReader.palette returns the current immutable effective palette without
acquiring a frame or allocating. Serialize the read; a retained palette remains
safe afterward. Unchanged indexed/dynamic color writes and unsupported targets
preserve palette identity and row generations. Host theme updates replace the
reset baseline even when effective values are equal; only an effective change
invalidates visible rows. Palette equality includes colors and presentation
preferences, including dark/light classification.
resetPaletteColors restores selected indexed entries, or all 256 entries for
an empty selection, from the latest theme baseline. resetDynamicColor restores
foreground, background, or cursor color from that same baseline. Other overrides
remain intact. Effective resets publish one immutable palette and invalidate
visible rows; unchanged resets preserve palette identity and generations.
Application color updates and resets never emit mode 2031 reports.
applyColorRequest consumes a borrowed OSC 21 batch synchronously in request
order. Queries capture the state at their position and share one response;
disabling replies leaves permitted mutations intact. Named resets restore both
the selected color and its rendering behavior from the latest theme. Setting a
fixed cursor or selection color clears that role's dynamic behavior; unrelated
roles remain unchanged.
pushColors and popColors save and restore immutable effective color snapshots
without replacing the theme reset baseline or host presentation preferences.
The stack is shared across screens and cleared by both terminal resets; see
the color-stack contract.
Writer contract
Printable ingress
writeCodepointaccepts one Unicode scalar. It performs placement and width handling without grapheme segmentation.writeAsciiaccepts a borrowed printable ASCII range (0x20..0x7E) and has the same cell/cursor semantics as successive scalar writes. It validates the entire range before mutation, does not retain the array, and leaves state unchanged for an empty range.writeTextdecodes UTF-16 as literal scalars. It does not interpret control characters or escape sequences. Use explicit control operations or a parser.writeClusteraccepts one pre-segmented sequence; core computes its width.lengthmust be in1..minOf(codepoints.size, 32).updatePreviousClusterreplaces the complete retained sequence of the most recently written printable cell. The caller includes the previously published prefix; core neither compares prefixes nor validates grapheme boundaries.
Both cluster operations validate and synchronously copy the used prefix, so the
caller may immediately reuse its array. Both reject lengths above
TerminalTextLimits.MAX_CLUSTER_CODEPOINTS (32) before any mutation, including
updates without a remembered target. Invalid scalar input is also rejected; see
Unicode scalar and width policy.
Cluster continuation preserves the target's attributes. Same-width updates preserve cursor position; in-row width changes adjust the following cursor and pending wrap against the active right margin. Narrowing clears the released spacer. Widening overwrites the next cell only when the span fits inside the margin; otherwise the cluster remains one cell. Updates do not insert or shift cells, including in insert mode, and do not undo completed wraps, scrolling, overwrites, or insert shifts. A printable write rejected by geometry or disabled autowrap clears the target; validation failure preserves it. A valid update with no remembered target is ignored.
All write paths preserve complete wide/cluster spans. Overwriting a spacer clears its previous occupant rather than leaving an orphaned leader or handle.
Erase and edit commands
Structural edits cancel pending wrap. newLine advances vertically without a
carriage return; carriageReturn moves to the active left boundary. The host
adapter applies newline mode when mapping linefeed controls.
scrollUp(count) and scrollDown(count) ignore nonpositive counts and cap work
at the region height. They preserve cursor position. Full-width upward scrolling
from the top of the viewport may admit rows to primary history; partial-width
scrolling never does. Downward scrolling does not consume history.
ICH / DCH operate within horizontal margins; IL / DL operate within the
vertical region and do nothing when the cursor is outside it. Vertical scrolling
and line edits preserve cells outside horizontal margins, except complete
occupants crossing a slice boundary. ECH erases without shifting; zero means
one cell and negative values are ignored. Rectangle operations use their
method-level DEC coordinates and extent rules.
Normal erasure ignores protection; selective erasure respects it. Clears ignore protection. Clearing operations have different state effects:
| Operation | Effect |
|---|---|
eraseEntireScreen |
Erases visible cells, preserving cursor and history |
eraseScreenAndHistory |
Erases history, preserving visible cells |
eraseBuffer |
Erases active screen and history with current erase attributes; preserves cursor, pen, modes, margins, tabs, saved cursor, and inactive screen |
clearScreen |
Clears visible cells and homes the cursor; preserves history |
clearAll |
Clears active screen and history, resets pen, homes cursor, clears its save slot, and resets tabs; retains margins |
reset |
Performs RIS; see buffer lifecycle below |
updatePenColors changes only non-null SGR fields. Null means unchanged,
CellColor.DEFAULT selects default color, and false clears a flag. Hyperlink IDs
and selective-erase protection survive. It does not change existing cells.
Cursor contract
Cursor movement clamps to applicable viewport or margin boundaries without integer overflow. Tab traversal is bounded by viewport width, not the supplied count. Cursor-affecting commands cancel pending wrap; saving or restoring the cursor follows the explicit saved-state rules below.
DECSC / DECRC
Each screen has a save slot containing column, row, both pen words, pending wrap, and origin mode. Charset designation and locking shifts remain parser-owned. The parser selects its charset save slot using the core's actual active screen through the command sink.
restoreCursor clamps saved coordinates to current bounds. Pending wrap is
restored only if the resulting column is the active right margin. Without a
save, it homes to absolute (0, 0), clears pending wrap and origin mode, and
resets the pen.
Reverse wrap
cursorLeft is shared by BS and CUB. With auto-wrap enabled, mode 45
(XTREVWRAP) crosses the left boundary only when the preceding viewport row has
soft-wrap provenance. It stops at a hard break or the top margin. Mode 1045
(XTREVWRAP2) takes precedence, crosses hard breaks, and cycles from the top
margin to the bottom margin. Neither operation scrolls, changes cell storage,
or enters history. Extended cycles use constant-time cell-offset arithmetic;
ordinary reverse wrap visits at most the preceding viewport rows.
The left boundary is the active left margin, or column zero when starting to its left; crossing a row lands at the active right margin. A cursor below the vertical region can enter it while moving backward. Above the top margin, extended motion stops at viewport row zero. Ordinary motion already at the left boundary on or above the top margin homes to that margin, matching Ghostty's XTREVWRAP behavior.
Positive motion always cancels pending wrap. In an active reverse-wrap mode, the phantom position consumes one step before physical motion; without reverse wrap it does not. Non-positive core counts remain no-ops; the parser normalizes omitted/zero CUB counts to one.
Both flags default to disabled and are serialized cursor state outside the
input/render mode snapshots. requestModeStatus reports each flag independently,
including while auto-wrap is disabled. Cursor save/restore, screen switches,
and ordinary resize preserve them; RIS and DECSTR disable both.
Tabs
HT, CHT, and CBT never wrap and stop at applicable horizontal boundaries.
HTS, TBC 0, and TBC 3 cancel pending wrap. Resize preserves surviving custom
stops, drops truncated stops, and seeds newly exposed columns with the default
8-column rhythm. RIS, clearAll, and DECCOLM restore default stops.
Mode-state contract
Mode 1007 defaults to enabled and controls alternate-screen wheel-to-cursor-key
input. XTSHIFTESCAPE (CSI > Ps s) accepts omitted/zero for local Shift override
and one for application Shift mouse capture; invalid values or malformed
parameter lists leave state unchanged. Capture defaults to disabled.
Both preferences are global, preserve pending wrap and render generations,
survive cursor save/restore, screen switches, resize, and DECSTR, and return to
defaults on RIS. Host mouse-reporting and alternate-wheel settings still apply.
The packed input word stores alternate-scroll suppression in bit 19 and Shift
capture in bit 63. A zero word preserves the enabled scrolling/local Shift
defaults for existing readers. Consume these fields through TerminalInputState
helpers; no new typed-snapshot constructor fields are required. DECRQM reports
the stored mode 1007 flag; XTSHIFTESCAPE has no DEC mode-status alias.
TerminalModeController changes durable modes. getModeSnapshot returns an
immutable atomic snapshot of the typed fields declared by
TerminalModeSnapshot, not an inventory of every mode. The packed snapshot
exposes TerminalModeBits plus the xterm resource helpers on
TerminalInputState; it includes DECCOLM and resources absent from the typed
snapshot. Capture one word when decoding several fields for one decision.
Existing bit positions, widths, sentinels, and mouse ordinal meanings are stable.
Bits 37..62 hold xterm resources; bits 19 and 63 are unassigned and must be
ignored. Neither snapshot promises space for every future mode.
keyModifierOption and keyFormatOption decode the same packed word. Resource
setters reject invalid identifiers/values before mutation; family resets restore
defaults atomically while preserving unrelated bits.
Independent TerminalInputState implementations can construct resource values
with withKeyModifierOption and withKeyFormatOption. These pure helpers return
a new packed word, validate supported resources and values, and preserve all
unrelated bits. The producer owns coherent publication; the helpers do not
update a terminal or input state.
Color-scheme and in-band resize subscriptions are serialized durable state
outside the packed input word and typed mode snapshot. Their DECRQM status is
available through requestModeStatus. Regular resize and screen switches
preserve subscriptions; RIS and DECSTR disable both. Enabling color-scheme
reporting emits no initial report, while every in-band resize enable request
reports the current geometry when permitted.
Auto-wrap and mode setters that home the cursor cancel pending wrap.
Reverse-wrap, input reporting, and presentation setters preserve it. resetCursorStyle
restores the configured default shape and enables the blink flag without
changing visibility, position, or pending wrap; the host owns blink animation.
Kitty keyboard flags and stacks are screen-local. Each stack retains at most 32 saved entries, evicting the oldest on overflow. A positive counted pop resets flags when the retained stack is exhausted, including exact exhaustion; a partial pop restores the last popped flags. Nonpositive counts do nothing, and work is constant regardless of the requested count. Soft and hard reset clear both stacks. Serialize these operations with other core mutation.
Unicode scalar and width policy
writeCodepoint, writeCluster, and updatePreviousCluster accept scalars in
0..0x10FFFF, excluding 0xD800..0xDFFF. Invalid input throws
IllegalArgumentException before grid, cursor, or pending-wrap mutation, even
without a continuation target. Cluster APIs validate the entire used prefix;
unused array entries are ignored. writeText replaces each unpaired UTF-16
surrogate with U+FFFD and preserves valid pairs. Parser UTF-8 recovery remains
outside core.
Width uses generated Unicode 17.0.0 tables, independently of JDK assignment, locale, and available fonts:
- Unlisted valid scalars default to one cell, including ordinary unassigned values.
- Reserved wide ranges retain East Asian Width defaults, including unassigned
CJK values and planes 2 and 3 through
xFFFD. - Noncharacters are valid and narrow.
- Private-use scalars and U+FFFD follow ambiguous-width mode.
- Zero-width classification, terminal cell-graphics overrides, and cluster variation-selector presentation rules retain precedence.
Presentation selectors do not universally specify a terminal cell count. For a cluster whose leading scalar is a recognized base in the pinned Unicode emoji variation table, retained VS15 selects one cell and retained VS16 selects two. VS15 takes precedence if both selectors are retained. This is KetraTerm's terminal compatibility policy; the selector itself has zero scalar width. Selectors on unsupported bases leave the ordinary width policy intact, including wide/fullwidth allocation and the ambiguous-width mode. Font metrics do not participate in cell allocation.
Reserved defaults follow the pinned Unicode East Asian Width data. These are cell-width rules, not host security permissions. They do not add segmentation to scalar ingress. DECFRA remains a single-cell fill operation and ignores invalid fill values.
Reader contract
TerminalReader.getLine returns a void line for an invalid row;
getCodepointAt returns 0 for an invalid coordinate. On a real borrowed
TerminalLine, callers must use valid columns within its width; these indexed
methods are not out-of-bounds probes.
Blank cells read as 0, wide spacers as -1, and cluster cells as their base
codepoint. isCluster identifies cells whose full payload requires
readCluster. Its destination must hold the complete sequence; insufficient
capacity throws IndexOutOfBoundsException. Core-owned lines retain at most
32 codepoints per cluster.
Use getClusterLength(col) to size a reusable destination before readCluster.
It returns the exact codepoint count, or zero for a scalar, blank, or wide spacer.
Keep the borrowed line, sizing, and copying inside one uninterrupted
serialization boundary. Clustered custom line implementations must override
the sizing operation; its scalar-only default rejects an unsupported cluster.
Alternatively, use
TerminalRenderFrame.copyLine and its primitive cluster-data sink. The callback
supplies the full length and a borrowed range to copy before returning. Keep
serialization held for either read path. Render frames expose primitive public
attributes and cell flags; getAttrAt offers allocating unpacked inspection.
Raw storage handles and internal arrays are not public APIs.
Image state
TerminalGraphics consumes complete borrowed commands and decoded byte ranges on
the serialized mutation thread. Uploads are validated before replacing retained
resources; queries do not store images or move the cursor. Placement requires
known cell-pixel geometry. Images have separate primary/alternate namespaces and
share the limits in TerminalGraphicsLimits; they do not occupy text cells.
IL/DL preserve ordinary image placements; actual screen scrolling moves them
subject to scrolling-region containment and clipping.
Render frames expose immutable resources through copyImages. Those resources
may outlive a callback; placement metadata remains borrowed. See the
render lifetime contract
for consumer ownership and the graphics contract
for geometry, cursor, scrolling and deletion rules.
Buffer lifecycle contract
Primary and alternate screens
Each screen has its own cursor, margins, save slot, ring, and row-owned cluster payloads. Alternate content has no scrollback.
| DEC mode | Core operation |
|---|---|
47 |
Non-clearing alternate switch, without cursor save/restore |
1047 |
Clearing alternate entry that homes cursor and resets margins; exit does not restore a save slot |
1048 |
Save/restore cursor without switching screens |
1049 |
Save primary cursor, enter a cleared alternate screen, then restore on exit |
Actual alternate entry also saves primary cursor shape and blink flag separately from the DECSC slot; exit restores them. Repeated entry/exit requests are no-ops and preserve this presentation snapshot. Alternate content survives non-clearing switches until a clearing entry or effective resize discards it. Resize and soft reset while alternate is active preserve the primary presentation snapshot; RIS restores configured default presentation.
Image namespaces follow screen selection and clearing entry. Pending graphics transfers are invalidated by a screen switch. Visible clearing removes visible placements; history clearing removes their retained-history portions. Ordinary partial text erasure leaves image placements intact.
Resize
resize(newWidth, newHeight, oldScrollbackOffset) validates the two-cell minimum
width, positive height, and retained capacity before allocation or mutation. Identical grid dimensions
avoid reflow and return a clamped offset plus current history size.
The five-argument overload accepts text-area pixel width and height together
with the grid and viewport offset, validating nonnegative pixels before
mutation. Either zero marks both pixel dimensions unavailable. The existing
three-argument overload preserves known pixels for an unchanged grid and marks
them unknown when the grid changes. Grid and pixel geometry commit together, so one
subscribed mode 2048 report describes the complete resulting size. A pixel-only
change reports without reflow or row-generation invalidation. Identical complete
geometry produces no report. Direct setWindowSizePixels updates use the same
pixel-change reporting rules.
An effective resize:
- Reconstructs and rewraps primary logical lines when width changes, relocating the cursor and copying surviving cluster payloads into new rows.
- Preserves retained primary physical rows, attributes, wrap flags, identities, and cluster handles for height-only changes. Untouched trailing rows serve as layout capacity; cursor and viewport positions adjust to the new boundary.
- Retains newest rows within configured capacity. A scrolled viewport keeps its retained anchor or clamps to the oldest surviving row if it is evicted.
- Wipes and recreates the alternate grid at the new dimensions.
- Resets vertical and horizontal margins on both screens, resizes tab stops, clamps saved cursors, and clears pending wrap on both screens.
The returned pair is (newScrollbackOffset, newHistorySize) for the active
screen. Alternate returns (0, 0) while primary is resized in the background.
Primary image placements retain physical cell coordinates on width reflow; height-only resize shifts them with the retained viewport boundary. Effective resize clears alternate images. Cell-pixel changes recompute image geometry and may advance graphics/frame generations without changing text-row generations.
Soft reset
softReset implements DECSTR (CSI ! p). It preserves visible content,
history, dimensions, tabs, active screen, and cursor position. It resets pen,
protection, margins on both screens, saved cursors to home/default state, and
pending wrap.
Mode defaults restore autowrap, cursor visibility/blinking, normal video, and normal cursor/keypad/input behavior. Bracketed paste, focus reporting, mouse tracking, synchronized output, color-scheme/resize reporting, and Kitty keyboard state reset; both xterm resource families return to defaults. Ambiguous-width policy, mouse encoding, the DECCOLM selection, and alternate-scroll/Shift-capture preferences survive. Palette, cell content and retained images are preserved. Incomplete graphics transfers are invalidated.
Hard reset
reset implements RIS (ESC c). It returns to primary, clears primary content
and history, homes the cursor, resets pen and terminal modes, restores full
primary scrolling margins and default tabs, clears both Kitty stacks, restores
default cursor presentation, and restores the host theme palette.
Retained alternate cells are not cleared by RIS. A later non-clearing mode 47
entry can revisit them; clearing alternate entry or an effective resize discards
that content. Graphics differ: RIS releases images and placements on both
screens and invalidates incomplete transfers.
DECCOLM
executeDeccolm accepts only widths 80 or 132; other values are ignored.
It resizes both screens, clears the active screen and history, homes to absolute
(0, 0), resets active margins and tabs, cancels pending wrap, and preserves both
saved-cursor slots. With alternate active, primary is reflowed in the background.
The accepted column-mode selection is independent of geometry: it survives ordinary resize, screen changes, and DECSTR, and resets on RIS. Status queries observe it without modifying state or render generations.
Protection contract
Protection applies to selective erasure. DECSCA stamps future cells; normal
writes and normal erasure ignore protection. Wide spacers inherit the leader's
protection, and reflow preserves it. Hard clears ignore it.
Terminal responses
Query methods and subscribed change reports enqueue bytes in TerminalResponseChannel.
readResponseBytes synchronously copies queued bytes into a caller-owned range;
invalid ranges are rejected without draining the queue or changing the array.
Sessions drain replies after core mutation and send them through the same
serialized outbound path as input. Direct hosts must do that themselves.
The host adapter must enforce terminal-response permission before calling query methods. Core's explicit capability allowlists govern reply contents and protocol-specific failure behavior. Denying the response family suppresses failure replies as well as success replies; supported queries and identities remain defined by the canonical feature map and protocol module.
setChangeNotificationsAllowed publishes the host's response permission for
automatic color-scheme and resize reports, including immediate mode 2048 enable
reports. This volatile permission may be published concurrently with core use;
it changes neither modes nor queued bytes. Explicit queries still require
permission checks by their caller. Session assembly applies its initial policy
to replies queued before ownership transfers. A changed host theme palette emits mode 2031 reports
independently of application palette overrides. See the
wire contract.
Invariants
Public mutation paths preserve complete wide spans, live cluster ownership, per-screen arena isolation, tab-stop width, and accurate render invalidation. Horizontal slices never copy incomplete wide source spans and clear destination occupants crossing their boundaries. Pending wrap is cleared by cursor/structural operations that cancel it, and preserved by non-cursor mode changes.