Skip to main content

Terminator

Enum Terminator 

Source
pub enum Terminator {
    St,
    Bel,
}
Expand description

Which byte ended an OSC sequence — and therefore which one ends its reply.

The engine relays this rather than choosing: a query event carries the terminator the request arrived with, and the consumer hands it back to the matching report_*. Under ADR-0017 the parse-time fact is a mechanism only the engine can observe, while which terminator to send is policy — and a consumer cannot exercise a policy on a fact it was never given, which is what #836 measured: bell_terminated was discarded at the parser boundary before any event was queued.

The spec settles the direction, not just the reference tally. ctlseqs.txt:2020“XTerm accepts either BEL or ST for terminating OSC sequences, and when returning information, uses the same terminator used in a query. While the latter is preferred, the former is supported for legacy applications.” Under ADR-0004 that outranks every implementation, this one included. On the colour path all three implementations that echo carry the terminator outward with the request rather than remembering it: alacritty binds it into the reply formatter it sends its consumer (alacritty_terminal/src/term/mod.rs:1678), ghostty makes it a field on the parsed command (src/terminal/osc.zig:87, written at src/termio/stream_handler.zig:1497), and xterm threads it as a parameter (misc.c:3567, emitted at :3593). xterm.js is the one that always sends ST (CoreBrowserTerminal.ts:239) — which is what this engine used to do.

The colour qualifier is load-bearing, because OSC 52 has a counterexample. xterm does store the terminator for the clipboard: int base64_final; on the screen (ptyx.h:2637), written when the request is parsed (misc.c:3389) and read a file away when the paste finally arrives (button.c:2218), because its X selection retrieval is asynchronous. That is shape (b) — the one this engine weighed and did not take — on the exact sequence report_clipboard answers.

It is recorded here because it strengthens the choice rather than undoing it. xterm stores precisely because the reply is detached from the request, and its store is a single scalar, so two overlapping paste requests would collide exactly as one stored terminator does here. Detachment is the condition crate::Engine::drain_events creates for every family at once, not just one — so carrying answers it where storing only postpones it.

Carrying beats remembering for a reason that is this channel’s. crate::Engine::drain_events hands over a batch, so a consumer can hold two colour queries at once and answer them in either order; one remembered scalar could not say which exchange it belonged to. An occurrence’s payload is detached from its instant by the queue — ADR-0029 D4 records the same shape for coordinates — so there is no re-ask and the fact must ride the event.

Exhaustive on purpose (#843’s rule). The space is closed at exactly two, with a date for each (ctlseqs.txt:2024-2028), so there is no member a later slice may name and nothing for #[non_exhaustive] to preserve.

The closure rests on the input space, not on the spec alone (#847). ECMA-48 gives ST a third encoding — the 8-bit C1 0x9C — and it is absent here because crate::Engine::feed does not treat a lone 0x80..=0x9F byte as a control at all, not because the spec stops at two. A reader who finds 0x9C in ctlseqs.txt and concludes this enum is missing a member has the reasoning backwards: were that contract ever revisited, the member would follow, and it is the contract that is load-bearing. ghostty reaches the same shape independently — src/terminal/osc.zig:252 is a two-member { st, bel } with no trailing _, which is Zig’s marker for an open enum and is used at 23 other sites in that tree. Convergence on both the partition and the closure is the non-arbitrariness signal.

Variants§

§

St

ESC \ (ST), the terminator ECMA-48 documents and xterm prefers.

The default, and what an OSC ended by any other byte that ends one resolves to. Those streams are real rather than theoretical: vte ends a string on three byte classes — BEL, the cancel pair CAN/SUB (0x18 / 0x1a), and a bare ESC opening the next sequence — and only the first is reported as bell-terminated. So a cancelled query is still relayed, and answered ST.

Read “any other byte that ends one” strictly: the 8-bit C1 ST (0x9C) is not a fourth class (#847). It does not end the string, so there is no event to carry a terminator and nothing resolves to this variant — the OSC stays open instead. See crate::Engine::feed for why that is a contract.

That is the right answer, not a fallback: xterm hardcodes ST on exactly this shape (charproc.c:8964, “should be ST”) and ghostty’s Terminator.init returns .st for a missing byte (src/terminal/osc.zig:263).

§

Bel

BEL (0x07), supported for legacy applications.

Real applications still emit it: nvim 0.8.0 asks ESC ] 11 ; ? BEL in this crate’s own cursor_color_nvim.raw fixture. And the shell idiom printf '\e]11;?\a'; read -d $'\a' reads until BEL, so an ST answer to a BEL question blocks that read until it times out.

Trait Implementations§

Source§

impl Clone for Terminator

Source§

fn clone(&self) -> Terminator

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 Copy for Terminator

Source§

impl Debug for Terminator

Source§

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

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

impl Default for Terminator

Source§

fn default() -> Terminator

Returns the “default value” for a type. Read more
Source§

impl Eq for Terminator

Source§

impl Hash for Terminator

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for Terminator

Source§

fn eq(&self, other: &Terminator) -> 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 Terminator

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.