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-partybeamterm). - 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 onvte, notalacritty_terminal; adopt then replacebeamtermwith the first-partyjusterm-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
- Apache License, Version 2.0 (
LICENSE-APACHE) - MIT license (
LICENSE-MIT)
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_PRESENTbits — the cell itself is three packed words, noOptionfield. All access is through the accessor seam (#44); construct withCell::from_partsorCell::default. - Cell
Flags - Per-cell flags: the standard SGR attributes plus layout markers.
- Command
Line - 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 fromexit. - Cursor
- The input position, its pending-wrap state, and the current pen.
- Engine
- The terminal engine: pairs the
vteparser with our state model. - Frame
- One serialized damage cycle: the decoded logical form that
encode/decoderound-trip.link_tableholds this frame’s OSC 8 hyperlink URIs, each shipped once and referenced bySpan::links. Grapheme clusters have no table — since v14 (#621) they are inlined at their column inSpan::combining, because nothing interned them and the table only bought an index to overflow. - Grid
- The current screen:
rows×colscells. - 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 (allNonefor legacy). - Line
Damage - The damaged column span of a single line.
- Logical
Line - One soft-wrap-joined logical line touching the viewport.
- Marker
Entry - 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. - Marker
Id - 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, andTermEvent::MarkerDisposedsignals when its line has left the buffer. - Marker
Index - 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. - Marker
Position - 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_paramremaps; kitty uses the bits directly (#23). - Mouse
Event - A mouse event in viewport cell coordinates (0-based — the encoding shifts to 1-based on the wire).
- Mouse
Events - 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.
- Scroll
Op - A first-class scroll: rows
[top..=bottom]shifted bycountlines (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). - Search
Options - Search modes beyond the default literal + smart-case (see
Term::search_with). Mirrors xterm.js’sISearchOptions(#314). The default (all off / smart-case) is exactlyTerm::search. - Selection
Span - One highlighted run on a single viewport row: columns
left..=right(both inclusive).selection_rangereturns 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.
- Tracked
Id - A stable handle to a tracked buffer position (#691), handed out by
Term::track_point.
Enums§
- Clipboard
Target - Which selection an
OSC 52clipboard request names (#828). - Color
- A cell’s foreground or background colour, stored as a reference.
- Cursor
Shape - 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). - Decode
Error - Why a byte buffer could not be decoded into a
Frame. - Frame
Kind - 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).
- Keypad
Key - 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).
- Marker
Kind - What a marker means (#158). A plain
add_markerdecoration 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. - Mouse
Action - What the mouse did.
- Mouse
Button - Which mouse button an event concerns.
Noneon aMouseEventmeans bare motion with no button held. - Selection
Type - 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).
- Term
Damage - What changed since the last
reset_damage(). - Term
Event - A consumer-facing event emitted while parsing the VT stream.
- Terminator
- Which byte ended an OSC sequence — and therefore which one ends its reply.
- Underline
Style - 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
OutputStartmark will freeze, inchars (#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— sameu16header 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
VERSIONbyte), 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
Cellto its fixed 14-byte little-endian record:cu32 (Unicode scalar) ·fgu32 ·bgu32 ·flagsu16. Width derives fromflags. - 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), andRgb(0,0,0)stay distinct. - is_
valid_ regex - Whether
patternis a regexTerm::search_withcan run (opts.regex = true) — atrueguaranteessearch_withwill build the pattern, and afalseis exactly the case it silently swallows into an empty result (#316 D2).