Skip to main content

TermEvent

Enum TermEvent 

Source
#[non_exhaustive]
pub enum TermEvent {
Show 21 variants Title(String), Bell, Cwd(String), ColumnMode { cols: usize, }, ColorSchemeQuery, SetPaletteColor { index: u8, spec: String, }, SetForeground(String), SetBackground(String), ResetPaletteColor(Option<u8>), QueryPaletteColor { index: u8, terminator: Terminator, }, SetCursorColor(String), QueryCursorColor { terminator: Terminator, }, ResetCursorColor, ResetForeground, ResetBackground, QueryForeground { terminator: Terminator, }, QueryBackground { terminator: Terminator, }, ClipboardStore { target: ClipboardTarget, text: String, }, QueryClipboard { target: ClipboardTarget, terminator: Terminator, }, MarkerDisposed(MarkerId), MarkerCreated { id: MarkerId, line: u32, kind: MarkerKind, evicted_total: u64, epoch: u32, },
}
Expand description

A consumer-facing event emitted while parsing the VT stream.

#[non_exhaustive], so a consumer must carry a _ arm and a new variant never breaks one. Decided 2026-09-02, by the maintainer, while #828 was adding two — and what decided it was neither this slice nor any consumer we can see.

What decided it is CLAUDE.md’s own identity statement: justerm-core is not penterm-only — it is a reusable, independent crate.” That sentence says there are consumers we cannot edit, which is precisely what this attribute defends; a crate whose identity were “internal, used by penterm” would want the opposite, because there a broken build is the compiler doing us a favour. So this follows from a call already made rather than from a preference, and it reverses only if that identity does.

Three measurements, so the next reader does not have to retake them:

  • crates.io reverse dependencies: zero (the single row the API returns is this crate itself), across 248 downloads split over 11 versions — i.e. no external consumer exists today. That is why the identity statement had to decide it: there was nothing to observe.
  • Cost in this workspace: zero. cargo test --workspace (87 suites) and clippy -D warnings both stay green. A same-crate match may still be exhaustive, justerm-wasm-decode and justerm-renderer never name TermEvent, and justerm-web’s events.ts is a deliberately narrower union (title / bell / cwd).
  • The window closes at 1.0.0. Adding this is free while the crate is 0.x and is itself a breaking change afterwards, while an enum without it turns every future variant into a major bump. Conformance here is cumulative by design (#47 is a perpetual tail) and the two slices before this one added three variants and two, so that rate is measured rather than assumed.

The argument that lost, recorded because it is a real cost. An exhaustive match is a feature for a consumer: the compiler tells them a new event exists and makes them decide about it. penterm’s route_event is the worked example — its ColumnMode and ColorSchemeQuery arms carry a comment explaining why each is dropped, written by someone the compiler had just informed. That signal is given up here, and it now has to come from release notes. It loses because this is a notification channel where ignoring an unknown event is documented as safe, so the guarantee belongs in prose rather than in the type — but a consumer who wanted the old behaviour is not imagining the loss.

(penterm was the evidence that an outside exhaustive matcher can exist — its five-variant match predates nine minor versions of this enum — and not the reason. It is being reimplemented, which is exactly why it could not be.)

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

Title(String)

The window title is now this string.

Read the tense carefully: since #823 this is not only “the application set a title”. Two paths emit it — OSC 0/OSC 2, and an XTWINOPS title pop (CSI 23 t) restoring what an earlier CSI 22 t saved. A consumer that treats it as “the title is now this” is correct for both; one that treats it as “the application just chose this” is wrong for the second, which is why there is no separate pop event.

A pop can legitimately restore the empty string — every application measured pushes at startup, before setting a title of its own — and that means “go back to whatever you would show by default”, not “show a blank title”.

§

Bell

The terminal bell rang (BEL, 0x07).

§

Cwd(String)

The working directory was reported (OSC 7), e.g. file://host/path.

§

ColumnMode

The app requested 80/132-column mode (DECCOLM ?3). justerm is dimension-free, so this is a request — the consumer may honor it by calling resize(cols, rows), or ignore it. cols is 80 or 132 (#82).

Fields

§cols: usize
§

ColorSchemeQuery

The app queried the light/dark color scheme (DSR CSI ? 996 n). justerm is theme-agnostic, so the consumer (which knows the scheme) answers by calling Engine::report_color_scheme (#85).

§

SetPaletteColor

The app set ANSI palette entry index to spec (OSC 4). One event per index ; spec pair in the sequence. The cell still references Indexed(index) — only the consumer’s palette[index] changes, so the engine stays theme-agnostic (#122).

Fields

§index: u8
§spec: String
§

SetForeground(String)

The app set the default foreground colour (OSC 10). Raw spec, forwarded for the consumer to apply — theme-agnostic, like SetBackground (#122).

§

SetBackground(String)

The app set the default background colour (OSC 11). The engine is theme-agnostic, so it forwards the raw spec string (rgb:…/#…) for the consumer to parse and apply to its palette — it never holds hex (#122).

§

ResetPaletteColor(Option<u8>)

The app reset palette entries to the theme default (OSC 104). None = the whole table (no argument); Some(index) = one entry, one event per index given. The consumer restores its palette (#122).

§

QueryPaletteColor

The app queried ANSI palette entry index (OSC 4 with ? for that pair); the consumer answers with report_palette_color (#122).

Fields

§index: u8
§terminator: Terminator

The terminator report_palette_color must answer with (#836).

§

SetCursorColor(String)

The app set the cursor colour (OSC 12, #832). The third slot of the same dynamic-colour sequence SetForeground and SetBackground ride, and theme-agnostic for the same reason: the raw spec is forwarded and the consumer — which owns the palette and the cursor’s contrast guard — applies it.

§

QueryCursorColor

The app queried the cursor colour (OSC 12 with ?); the consumer answers with report_cursor_color (#832).

Fields

§terminator: Terminator

The terminator report_cursor_color must answer with (#836).

§

ResetCursorColor

The app reset the cursor colour to the theme default (OSC 112, #832). The third member of the 110/111/112 reset family, and the one real applications emit most: nvim sends it on startup, on every alt-screen transition and on exit.

§

ResetForeground

The app reset the default foreground to the theme default (OSC 110, #122).

§

ResetBackground

The app reset the default background to the theme default (OSC 111, #122).

§

QueryForeground

The app queried the default foreground colour (OSC 10 with ?); the consumer answers with report_foreground (#122).

Fields

§terminator: Terminator

The terminator report_foreground must answer with (#836).

§

QueryBackground

The app queried the default background colour (OSC 11 with ?). The theme-agnostic engine relays it; the consumer answers with report_background (#122), mirroring ColorSchemeQuery.

Fields

§terminator: Terminator

The terminator report_background must answer with (#836).

§

ClipboardStore

The app asked for text to be put on target (OSC 52 with a payload, #828). The engine has already base64-decoded it, and holds no clipboard of its own.

This is a request, not a fact. Whether the copy happens is the consumer’s: it owns the platform clipboard, any permission model and any prompt, and a consumer that drops this event has refused the copy. The engine carries no allow/deny knob, which is where it parts company with alacritty — alacritty gates the same sequence behind a four-state config (alacritty_terminal/src/term/mod.rs:1706) because alacritty is the consumer. Under ADR-0017 that gate lives one layer out.

An empty text means “clear it”. ESC ] 52 ; c ; ESC \ carries a payload that decodes to nothing, and both the spec and xterm end that exchange with an empty selection — the spec because Pd “becomes the new selection” whatever it is (ctlseqs.txt:2166), xterm because it clears the buffer before appending anything (misc.c:3410). Note this is not the spec’s “neither a base64 string nor ? clause at ctlseqs.txt:2174, which the engine diverges from: an empty payload is a perfectly well-formed encoding of no bytes, so it never reaches that sentence. Citing :2174 here, as an earlier draft did, would have the same line standing as authority followed and as authority departed from. ghostty pins the same input under a test named “clear clipboard” (src/terminal/osc/parsers/clipboard_operation.zig:93). It needs no rule of its own here, which is the argument for having none: an empty payload is a well-formed encoding of no bytes, so it reaches the consumer through the ordinary path.

Fields

§text: String
§

QueryClipboard

The app asked what is on target (OSC 52 with a ? payload, #828). The consumer answers by calling report_clipboard, which encodes the reply — or declines, which is how a clipboard read is refused independently of a write.

The engine cannot answer this itself and deliberately holds nothing that would let it: a query is answered from the consumer’s clipboard or not at all, so there is no engine state here for a hostile application to read back. Same Query… + report_… shape as OSC 4/10/11/12.

Fields

§terminator: Terminator

The terminator report_clipboard must answer with (#836).

§

MarkerDisposed(MarkerId)

A decoration marker’s line left the buffer — evicted past the scrollback cap, or scrolled out of an in-screen region (#118). The handle is now dead; the consumer drops the decoration bound to it. This is the frame-mode equivalent of xterm’s IMarker.onDispose — disposal is a point-in-time fact (a marker absent from a frame may merely be scrolled off-screen), so it rides the event queue, not the frame overlay.

§

MarkerCreated

A marker was created (#490) — by add_marker, or by the stream through an OSC 133 command mark, which the consumer never called for.

The mirror of TermEvent::MarkerDisposed, and it exists for the same reason ADR-0020 R1 gives: an appearance is an occurrence, not state, so it rides this queue rather than a frame field. Without it a consumer that pulled a marker index (Engine::marker_index) has no way to learn of a marker born after its pull — the population would only ever shrink.

line is absolute at the moment of creation, and evicted_total / epoch are the instant it is absolute at — the same triple crate::MarkerIndex carries, because this event is that pull’s incremental mirror. The consumer appends the entry with the basis it arrived on and rebases it exactly like a pulled one.

The two are one fact and neither is usable alone (#737). A single feed can create a marker and then evict, so by the end of the batch the buffer’s origin has moved out from under the line this event already carries. Frame::evicted_total is the basis at the end of that batch, so reading line against it misplaces the marker by however much the batch evicted after the birth — measured at three lines, with the event line, both frame bases, the epoch and Frame::marker_count all identical to the batch that evicted first and needs no adjustment at all.

And a basis dates only a uniform move (#741). Eviction shifts every marker by the same amount, which is what one scalar can say; a reflow or a region rotate moves them individually, which is what epoch is for. A birth still queued when the epoch moves describes a buffer that no longer exists, and carrying only the basis leaves that indistinguishable from a birth in the current generation — measured, a mark at absolute 3 reflowed to 5 with the basis unmoved at 0.

Deliberately not an epoch bump: a bump costs a whole re-pull, and creation is O(1) information.

Fields

§line: u32
§evicted_total: u64

Lines evicted since RIS at the moment of creation — the basis line is absolute at. Carried rather than inferred so that placement does not depend on whether the consumer drains this queue before or after it reads the frame, which nothing in the API specifies.

It is the same quantity Frame::evicted_total reports, so a consumer whose transport crosses a language boundary owes it the same treatment: the wasm frame getter hands its u64 over as an f64 deliberately (exact to 2^53), because a BigInt on one side of a subtraction and a number on the other is a TypeError, not a rounding question.

§epoch: u32

The marker generation this line belongs to — crate::MarkerIndex::epoch at the moment of creation (#741). Two lines dated with different epochs are answers about different buffers and nothing rebases one onto the other, so a consumer adopts this entry only into the generation it names and lets the re-pull that the bump already forces supply it otherwise.

Compare it for equality, never for order: the counter is wrapping_add, so < is meaningless across a wrap while == is exact.

Trait Implementations§

Source§

impl Clone for TermEvent

Source§

fn clone(&self) -> TermEvent

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TermEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for TermEvent

Source§

impl PartialEq for TermEvent

Source§

fn eq(&self, other: &TermEvent) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for TermEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.