DevelopmentAll versions
On this page

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

  • writeCodepoint accepts one Unicode scalar. It performs placement and width handling without grapheme segmentation.
  • writeAscii accepts 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.
  • writeText decodes UTF-16 as literal scalars. It does not interpret control characters or escape sequences. Use explicit control operations or a parser.
  • writeCluster accepts one pre-segmented sequence; core computes its width. length must be in 1..minOf(codepoints.size, 32).
  • updatePreviousCluster replaces 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.