Skip to main content

Crate justerm_core

Crate justerm_core 

Source
Expand description

§justerm-core

A pure terminal engine in Rust — the core crate of the justerm family. Feed it a VT byte stream; it owns the terminal state (grid + scrollback + cursor + selection) and emits a viewport snapshot, damage, scroll ops, and extractable text. It is not a renderer and not a full emulator.

  • No I/O — the caller feeds bytes (feed(&[u8])); justerm-core never touches a PTY/SSH/socket.
  • No IPC — it provides a binary format, not transport.
  • No rendering — a renderer draws (the family’s first-party justerm-renderer, WebGL2, replacing the third-party beamterm).
  • Theme-agnostic — colours are references (Default / Indexed / RGB), never resolved hex; the consumer resolves them.

§Install

cargo add justerm-core

§Usage

Feed VT bytes in, read terminal state out — no PTY, no rendering, no theme:

use justerm_core::{Color, Engine};

// An 80×24 viewport. The caller owns I/O; justerm-core only parses bytes.
let mut term = Engine::new(80, 24);

// A VT stream: "hi" in SGR red (ESC[31m … ESC[0m).
term.feed(b"\x1b[31mhi\x1b[0m");

// Read the grid back: the character, and the colour *reference*
// (Indexed(1) = ANSI red — resolving it to a hex value is the consumer's job).
assert_eq!(term.grid().cell(0, 0).c(), 'h');
assert_eq!(term.grid().cell(0, 0).fg(), Color::Indexed(1));

Engine also exposes damage() (the line + column ranges changed since the last read), viewport_line() / viewport_logical_lines(), resize(), and a binary wire format (serialize) for shipping frames to a consumer with no TypeScript mirror to drift.

Status: in active use. The core engine is implemented and consumed across the family (justerm-wasm-decode, justerm-web, justerm-renderer). VT compliance is cumulative — the common cases are covered and the long tail grows as dogfooding surfaces it. See the issue tracker for the current frontier. First consumer: PenTerm (a Tauri terminal app).

§Docs

  • CLAUDE.md — identity, boundary invariants, conventions, working method.
  • CONTEXT.md — glossary.
  • docs/architecture.md — the contract: cell, damage, viewport/scroll, cadence, selection, serialization, engine API — plus a Hidden VT state checklist (with where to look in reference impls) for implementers.
  • docs/adr/ — key decisions (build on vte, not alacritty_terminal; adopt then replace beamterm with the first-party justerm-renderer, ADR-0002 → ADR-0018).

§Web consumers

The wire format’s decoder is shipped to the web as justerm-wasm-decode — the native decode compiled to WASM and published to npm, version-locked to this crate, so the backend encoder and the webview decoder share one implementation (no TypeScript mirror to drift). It decodes into structure-of-arrays cell columns and ships the format-owned helpers (resolveRgb / buildPalette / flags); the theme values (your palette) and render policy (atlas, cursor) stay the consumer’s adapter. See justerm-wasm-decode/README.md and ADR-0008.

§License

Licensed under either of

at your option.

§Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Structs§

Cell
One character position: a base glyph, fg/bg colour references, and flags. Combining marks (#45) and an OSC 8 hyperlink (#46) attach via per-row maps, signalled by the COMBINED_PRESENT / LINK_PRESENT bits — the cell itself is three packed words, no Option field. All access is through the accessor seam (#44); construct with Cell::from_parts or Cell::default.
CellFlags
Per-cell flags: the standard SGR attributes plus layout markers.
CommandLine
One executed shell command recovered from OSC-133 marks (#166), for screen-reader command navigation. The consumer jumps prompt-to-prompt over these and announces command + a success/fail signal from exit.
Cursor
The input position, its pending-wrap state, and the current pen.
Engine
The terminal engine: pairs the vte parser with our state model.
Frame
One serialized damage cycle: the decoded logical form that encode/decode round-trip. link_table holds this frame’s OSC 8 hyperlink URIs, each shipped once and referenced by Span::links. Grapheme clusters have no table — since v14 (#621) they are inlined at their column in Span::combining, because nothing interned them and the table only bought an index to overflow.
Grid
The current screen: rows × cols cells.
Hyperlink
A declared OSC 8 hyperlink, as handed to a consumer.
KeyEvent
A key event: a key, the modifiers held with it, its press/repeat/release type (defaults to Press), and consumer-supplied extras the kitty protocol’s alternate-keys / associated-text flags report (all None for legacy).
LineDamage
The damaged column span of a single line.
LogicalLine
One soft-wrap-joined logical line touching the viewport.
MarkerEntry
One live marker, as the pull query reports it (#490): its stable id, its absolute [scrollback ++ screen] line, and the static facts a consumer would otherwise have to re-learn from every frame.
MarkerId
A stable handle to a buffer line, handed out by Engine::add_marker (#118). Monotonic per engine. The consumer attaches a decoration to the id; the frame reports where the marker currently sits, and TermEvent::MarkerDisposed signals when its line has left the buffer.
MarkerIndex
The answer to Term::marker_index (#490) — every live marker of the active buffer, plus the basis that says how long the answer stays usable.
MarkerPosition
A marker projected onto the viewport (#118): its id, the row it sits on, and its kind (#159). Only markers visible in the current viewport are reported; an off-screen marker is omitted but still alive (death comes via MarkerDisposed, not absence — so the consumer can tell “scrolled away” from “gone”). The kind carries the OSC 133 command-boundary role + exit code so the consumer can drive prompt-to-prompt navigation and success/fail signals (#160).
Match
One literal match, inclusive on both ends, in absolute buffer coordinates.
Modifiers
Modifier keys held during an event. The bit values follow the kitty scheme (the superset): Shift=1, Alt=2, Ctrl=4, Super=8, Hyper=16, Meta=32, CapsLock=64, NumLock=128. Legacy xterm can only express the first three plus Meta-at-8, so csi_param remaps; kitty uses the bits directly (#23).
MouseEvent
A mouse event in viewport cell coordinates (0-based — the encoding shifts to 1-based on the wire).
MouseEvents
The mouse event categories the active tracking mode reports (#129) — the routing mask the frame carries so a frame-mode consumer sends an event to the app (a wanted bit set) or keeps it local (selection/scrollback). It is the single source encode_mouse’s restriction shares, so the wire mask and the encode-time gate cannot drift.
Overlay
Interaction overlays projected onto the viewport (#108): highlight spans the engine carries on the frame so a frame-mode consumer can paint them without an in-process model query. Positions only — highlight colour is the consumer’s (theme-agnostic). Coordinates are viewport rows/cols, re-projected by frame() against the scroll offset so the engine stays the single anchoring authority.
Pen
The current SGR state — the appearance copied into each printed cell.
Row
One row of cells plus its per-row, column-keyed combining, link, and underline-colour maps.
ScrollOp
A first-class scroll: rows [top..=bottom] shifted by count lines (positive = up, negative = down). The renderer moves the rows instead of redrawing them. Recorded by the engine — which executes the scroll — rather than diff-detected (ADR-0003).
SearchOptions
Search modes beyond the default literal + smart-case (see Term::search_with). Mirrors xterm.js’s ISearchOptions (#314). The default (all off / smart-case) is exactly Term::search.
SelectionSpan
One highlighted run on a single viewport row: columns left..=right (both inclusive). selection_range returns one per visible row the selection touches — the renderer paints these. Off-screen rows are not emitted.
Span
A damaged column run on one line, with its cells.
Term
Owns the authoritative screen state and applies VT actions to it.
TrackedId
A stable handle to a tracked buffer position (#691), handed out by Term::track_point.

Enums§

ClipboardTarget
Which selection an OSC 52 clipboard request names (#828).
Color
A cell’s foreground or background colour, stored as a reference.
CursorShape
The cursor’s drawn shape (DECSCUSR / the renderer’s caret glyph). The engine reports it on the frame; the renderer draws it. Default Block (#81).
DecodeError
Why a byte buffer could not be decoded into a Frame.
FrameKind
Whether a frame redraws everything or just its spans.
Key
A logical key press from the consumer (already decoded from the platform’s keyboard event — justerm does not read hardware).
KeyAction
Press / repeat / release. Legacy reports only presses; the kitty protocol’s “report event types” flag (bit 1) carries repeat and release too (#23).
KeypadKey
A numeric-keypad key. In application-keypad mode (DECNKM ?66 / DECKPAM, #74) these encode as the classic VT100/VT220 SS3 sequences; in numeric mode as the literal character. The consumer produces these for raw keypad identity — it owns NumLock / key-location resolution (#83).
MarkerKind
What a marker means (#158). A plain add_marker decoration carries no semantics (MarkerKind::Plain); OSC 133 shell-integration marks carry the command-boundary role (prompt/command/output start, or command finished with its optional exit code). The engine only parses and anchors these — the success/failure colour, earcon and prompt-to-prompt navigation are consumer policy (ADR-0017), driven off the kind + exit the wire (#159) carries.
MouseAction
What the mouse did.
MouseButton
Which mouse button an event concerns. None on a MouseEvent means bare motion with no button held.
SelectionType
What a selection covers.
Side
Which half of a cell an anchor sits on — the left or right edge. Lets a drag include or exclude the cell under the pointer (mouse precision).
TermDamage
What changed since the last reset_damage().
TermEvent
A consumer-facing event emitted while parsing the VT stream.
Terminator
Which byte ended an OSC sequence — and therefore which one ends its reply.
UnderlineStyle
How a cell’s underline is drawn — SGR 4 : Ps (#829).

Constants§

CELL_RECORD_LEN
Length in bytes of one fixed-width wire cell record (see encode_cell_record).
DEFAULT_WORD_SEPARATORS
The built-in word-boundary set for Word (semantic) selection — the default value of Term::set_word_separators, and policy the consumer may replace (ADR-0017: mechanism in core, policy injected).
MAX_COLUMNS
The widest grid the engine will hold, and the mirror of MIN_COLUMNS — but derived from a different kind of constraint, which is why the two are not symmetric.
MAX_COMMAND_TEXT
The longest command text an OSC-133 OutputStart mark will freeze, in chars (#750). A longer command is captured truncated to this many characters.
MAX_MARKERS
The most live markers one buffer will hold (#721).
MAX_ROWS
The tallest grid the engine will hold. The row half of MAX_COLUMNS — same u16 header field, same reasoning, same silent-clamp contract.
MIN_COLUMNS
The narrowest screen the engine represents: two columns.
WIRE_VERSION
The wire-format version (the gating VERSION byte), exposed so a binding can assert at load that its decoder matches the backend encoder (#34/ADR-0008).

Functions§

decode
Deserialize the binary wire format back into a Frame.
encode
Serialize a frame to the binary wire format.
encode_cell_record
Encode one Cell to its fixed 14-byte little-endian record: c u32 (Unicode scalar) · fg u32 · bg u32 · flags u16. Width derives from flags.
encode_color
A colour reference as a tagged u32: high byte = tag (0 = Default, 1 = Indexed, 2 = Rgb), low 24 bits = payload. The tag is mandatory so Default, Indexed(0), and Rgb(0,0,0) stay distinct.
is_valid_regex
Whether pattern is a regex Term::search_with can run (opts.regex = true) — a true guarantees search_with will build the pattern, and a false is exactly the case it silently swallows into an empty result (#316 D2).