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 ships one: justerm-renderer (WASM + WebGL2).
  • 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

  • docs/architecture.md — the contract: cell, damage, viewport/scroll, cadence, selection, serialization, engine API.
  • CONTEXT.md — glossary.
  • docs/adr/ — the decision records behind the design.

§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 for its API.

§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 and an OSC 8 hyperlink 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; 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, 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 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: 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. 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 — 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: its id, the row it sits on, and its kind. 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.
Match
One literal match, inclusive on both ends, in absolute buffer coordinates.
ModifiedKeys
Which modified presses of the four keys whose bare form is a C0 control — Enter, Tab, Backspace, Escape — reach the application distinct from the bare key, under the keyboard modes currently in effect. A set bit means the modifier survives the encoding; a clear bit means the application receives exactly what the bare key sends. Carried on the frame as crate::Frame::modified_keys, derived from the encoder crate::Term::encode_key runs.
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.
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 — 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: 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. 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, handed out by Term::track_point.

Enums§

ClipboardTarget
Which selection an OSC 52 clipboard request names.
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 the application’s choice on the frame as an OptionNone until DECSCUSR sets one — and the consumer draws it, falling back to its own default shape.
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.
KeypadKey
A numeric-keypad key. In application-keypad mode (DECNKM ?66 / DECKPAM) 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.
MarkerKind
What a marker means. 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 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.

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. A longer command is captured truncated to this many characters.
MAX_MARKERS
The most live markers one buffer will hold.
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 (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.