pub struct Engine { /* private fields */ }Expand description
The terminal engine: pairs the vte parser with our state model.
Parser and Term are kept as separate fields because Parser::advance
borrows both the parser and the performer mutably at once — a single struct
owning both could not satisfy the borrow checker.
Implementations§
Source§impl Engine
impl Engine
Sourcepub fn new(cols: usize, rows: usize) -> Self
pub fn new(cols: usize, rows: usize) -> Self
A blank engine with a cols × rows screen and a default scrollback cap.
cols is widened to MIN_COLUMNS — a narrower screen cannot represent a
width-2 glyph, so the engine clamps rather than accepting a size it would
have three different answers for (#547).
Sourcepub fn with_scrollback(
cols: usize,
rows: usize,
scrollback_limit: usize,
) -> Self
pub fn with_scrollback( cols: usize, rows: usize, scrollback_limit: usize, ) -> Self
Like Engine::new but with an explicit scrollback line limit. cols is
clamped to MIN_COLUMNS the same way.
Sourcepub fn feed(&mut self, bytes: &[u8])
pub fn feed(&mut self, bytes: &[u8])
Push a slice of VT bytes. The caller owns the PTY/SSH/socket I/O — the engine only consumes the bytes it is handed.
Sourcepub fn resize(&mut self, cols: usize, rows: usize)
pub fn resize(&mut self, cols: usize, rows: usize)
Resize the screen to cols x rows. Rows that scroll off the top enter
scrollback; the whole screen is damaged.
The primary screen reflows; the alternate screen does not (#567). On the primary, soft-wrapped logical lines are re-split at the new width — scrollback included, since it is one buffer with the screen — so a long line keeps its tail instead of being truncated. Reflow is not gated on DECAWM: the wrap flag records that a row continues into the next one, which stays true after a re-split, and re-reading a momentary mode at resize time would decide the fate of history written under the opposite setting. The alt screen is re-fit only — rows are dropped or added to reach the new size and nothing re-wraps, because a full-screen application places its own lines and re-wrapping them would change what it drew.
What a consumer must redo afterwards. Query-derived state is invalidated and user-authored state is re-anchored: search highlights are dropped (re-run the search at the new width — a reflow moves match coordinates and can change the match set), while the selection is carried to its new coordinates for you.
cols is widened to MIN_COLUMNS silently: a resize(1, rows) during
a pane drag yields a two-column screen with no error. Read the resulting
width back from Engine::grid or the frame header rather than assuming the
value passed here, and size the PTY from that same width (#547).
Sourcepub fn bracketed_paste(&self) -> bool
pub fn bracketed_paste(&self) -> bool
Whether bracketed-paste mode (DEC ?2004) is enabled. A consumer’s input encoder reads this to decide whether to wrap pasted text in markers.
Sourcepub fn encode_key(&self, ev: KeyEvent) -> Option<Vec<u8>>
pub fn encode_key(&self, ev: KeyEvent) -> Option<Vec<u8>>
Encode a key event to the bytes an application expects, honouring the
engine’s cursor-key mode (DECCKM). The inverse of Engine::feed — the
consumer hands a decoded key event and writes the bytes to its PTY.
Returns None for a key with no defined encoding.
Sourcepub fn encode_mouse(&self, ev: MouseEvent) -> Option<Vec<u8>>
pub fn encode_mouse(&self, ev: MouseEvent) -> Option<Vec<u8>>
Encode a mouse event using the engine’s active tracking mode + encoding.
Returns None when mouse reporting is off, or when the event is filtered
out by the mode (e.g. a bare move while only ?1000 is set).
Sourcepub fn encode_paste(&self, text: &str) -> Vec<u8> ⓘ
pub fn encode_paste(&self, text: &str) -> Vec<u8> ⓘ
Encode pasted text — wrapped in bracketed-paste markers when ?2004 is on, raw otherwise.
Sourcepub fn encode_focus(&self, focused: bool) -> Option<Vec<u8>>
pub fn encode_focus(&self, focused: bool) -> Option<Vec<u8>>
Encode a focus change (CSI I on focus-in, CSI O on focus-out), or
None when focus reporting (?1004) is off.
Sourcepub fn drain_events(&mut self) -> Vec<TermEvent>
pub fn drain_events(&mut self) -> Vec<TermEvent>
Take the consumer events accumulated since the last drain (title / bell /
cwd — see TermEvent), emptying the queue. The pull counterpart to a
callback: poll this alongside Engine::frame.
Sourcepub fn drain_replies(&mut self) -> Vec<u8> ⓘ
pub fn drain_replies(&mut self) -> Vec<u8> ⓘ
Take the reply bytes the engine produced for app queries (DA / DSR /
DECRQM) since the last drain — the consumer writes them straight back to
the PTY. The inbound-query counterpart to Engine::drain_events.
Sourcepub fn link_at(&self, row: usize, col: usize) -> Option<Hyperlink>
pub fn link_at(&self, row: usize, col: usize) -> Option<Hyperlink>
The OSC 8 hyperlink URI at screen (row, col) — the live grid, same
coordinates as Engine::grid’s cell(row, col) — or None if that cell
carries no declared link.
One call, not two, since #628. This returned a NonZeroU32 index that a
second method resolved against a buffer-wide pool; the pool is gone (it was never
reclaimed, and nothing interned across opens that a shared Arc does not), so
there is no index left to hand out.
Owned, not borrowed — a &str into the row’s map would be tied to &Engine,
so a hover handler could not keep it across the next Engine::feed. Measured:
the borrow reads at 0.75 ns but cannot be held at all, and the caller’s workaround
(copying the string) costs 62.6 ns against this handle’s 17.9 ns. See
Hyperlink.
Do not confuse this with a decoded Span’s links, which is a frame-local
index into that frame’s link_table and belongs to the wire, not to the engine.
The old two-call form invited exactly that mix-up and its doc-comment recommended
it: the two index spaces coincide only when a frame carries a single link.
Sourcepub fn underline_color_at(&self, row: usize, col: usize) -> Color
pub fn underline_color_at(&self, row: usize, col: usize) -> Color
The underline colour (SGR 58, #520) at screen (row, col) — same
coordinates as Engine::grid’s cell(row, col). A theme-agnostic
Color reference; Color::Default means the underline follows the
glyph’s foreground (the common case, and what a cell with no SGR 58 returns).
Like the hyperlink, the colour rides a per-row side table, not the 12-byte
Cell (#520).
Sourcepub fn viewport_link_at(&self, row: usize, col: usize) -> Option<Hyperlink>
pub fn viewport_link_at(&self, row: usize, col: usize) -> Option<Hyperlink>
The OSC 8 hyperlink URI at viewport (row, col) — the visible window
including scrollback at the current scroll, same coordinates as
Engine::viewport_line — or None. Mirror of Engine::link_at, including
its #628 note about the vanished index.
Sourcepub fn scrollback_len(&self) -> usize
pub fn scrollback_len(&self) -> usize
Number of lines currently held in scrollback history.
Sourcepub fn synchronized_output(&self) -> bool
pub fn synchronized_output(&self) -> bool
Whether the app has an open synchronized-output block (DEC ?2026):
it has asked that the next frame of output be painted atomically. The
engine only reports this — the consumer owns the paint-hold and the
spec-mandated timeout (a buggy app that never closes the block must not
freeze the screen forever, and the engine has no clock). Poll this after
feed; while it is true, defer applying frames, and apply once it
clears (or your own timeout fires). (#73)
Sourcepub fn color_scheme_updates(&self) -> bool
pub fn color_scheme_updates(&self) -> bool
Whether the app enabled color-scheme-update notifications (DEC ?2031).
The engine is theme-agnostic — it never knows the scheme. The consumer
answers a TermEvent::ColorSchemeQuery (from ?996) and, when its
scheme changes and this is true, sends an unsolicited notification, in
both cases by calling Engine::report_color_scheme (#85).
Sourcepub fn report_color_scheme(&mut self, dark: bool)
pub fn report_color_scheme(&mut self, dark: bool)
Report the current light/dark color scheme to the app as CSI ? 997 ; 1 n
(dark) / ; 2 n (light), drained via Engine::drain_replies. Call this
to answer a TermEvent::ColorSchemeQuery, or — guarded by
Engine::color_scheme_updates — when the scheme changes. The engine only
formats the bit you pass; it stores no scheme (#85).
Sourcepub fn report_background(&mut self, spec: &str)
pub fn report_background(&mut self, spec: &str)
Answer an OSC 11 QueryBackground event (#122): the consumer hands back
the current background spec (it owns the palette) and the engine queues
the OSC 11 reply for drain_replies. Theme-agnostic — the engine never
knows the colour, only formats the envelope.
Sourcepub fn report_foreground(&mut self, spec: &str)
pub fn report_foreground(&mut self, spec: &str)
Answer an OSC 10 QueryForeground event (#122): queue the OSC 10 reply
from the consumer-supplied spec. Theme-agnostic envelope-only.
Sourcepub fn report_palette_color(&mut self, index: u8, spec: &str)
pub fn report_palette_color(&mut self, index: u8, spec: &str)
Answer an OSC 4 QueryPaletteColor event (#122): queue the OSC 4 reply for
index from the consumer-supplied spec. Theme-agnostic envelope-only.
Sourcepub fn win32_input_mode(&self) -> bool
pub fn win32_input_mode(&self) -> bool
Whether the app enabled win32-input-mode (DEC ?9001): it asked for
keys as raw Windows key-records. The engine only tracks the flag — encoding
the records (CSI Vk;Sc;Uc;Kd;Cs;Rc _) is a non-goal (raw passthrough, no
semantic conversion), so Engine::encode_key is unchanged. A ConPTY
consumer reads this to decide whether to emit the records itself (#86).
Sourcepub fn damage(&self) -> TermDamage
pub fn damage(&self) -> TermDamage
What changed since the last Engine::reset_damage — line ranges each
with a changed column span (see ADR-0003).
Sourcepub fn frame(&self) -> Frame
pub fn frame(&self) -> Frame
Build a serializable Frame of the current diff — the damaged spans
(or every row, when Full), the recorded scroll op, and a frame-local
grapheme side-table. Pass it to encode for the wire (see #6). Reading
a frame does not clear damage; call Engine::reset_damage on ack.
Sourcepub fn reset_damage(&mut self)
pub fn reset_damage(&mut self)
Clear accumulated damage after a frame is applied (the consumer’s ack).
Sourcepub fn mark_fully_damaged(&mut self)
pub fn mark_fully_damaged(&mut self)
Force the next Engine::frame to be a Full frame (every row), even if
little changed. The use case is reattach / late subscribe: a renderer
that connects after output has already been parsed needs the whole current
viewport once, then incremental diffs. Marks the screen fully damaged; the
next frame() reports FrameKind::Full.
Sourcepub fn scroll_delta(&self) -> Option<ScrollOp>
pub fn scroll_delta(&self) -> Option<ScrollOp>
The first-class scroll recorded since the last Engine::reset_damage,
if any — lets the renderer shift rows instead of redrawing them.
count is capped at the scroll region’s own height (#661). Repeated
scrolls of one region accumulate into a single op between acks, and a flood
accumulates far past the region: 32 KB of newlines in one Engine::feed is
enough. Shifting a region by more than its height already moves every source
row outside it, so the surplus names nothing a consumer can act on — while it
did overflow the i16 this value rides on the wire and arrive as a scroll in
the opposite direction. Suppressed entirely while the viewport is scrolled
up, since a content scroll must not shift a frozen view.
Sourcepub fn viewport_line(&self, i: usize) -> &[Cell]
pub fn viewport_line(&self, i: usize) -> &[Cell]
The cells of visible row i (0..rows) at the current scroll position.
Sourcepub fn scroll_up(&mut self, n: usize)
pub fn scroll_up(&mut self, n: usize)
Scroll the viewport up by n lines into scrollback history.
Sourcepub fn scroll_down(&mut self, n: usize)
pub fn scroll_down(&mut self, n: usize)
Scroll the viewport down by n lines toward the live screen.
Sourcepub fn scroll_to_bottom(&mut self)
pub fn scroll_to_bottom(&mut self)
Jump the viewport back to the live screen (follow the bottom).
Sourcepub fn selection_begin(
&mut self,
row: usize,
col: usize,
side: Side,
ty: SelectionType,
)
pub fn selection_begin( &mut self, row: usize, col: usize, side: Side, ty: SelectionType, )
Begin a selection of ty at viewport cell (row, col), on side of the
cell. Coordinates are viewport-relative (what a mouse event carries).
Sourcepub fn selection_extend(&mut self, row: usize, col: usize, side: Side)
pub fn selection_extend(&mut self, row: usize, col: usize, side: Side)
Extend the live selection to viewport cell (row, col), on side.
Sourcepub fn set_word_separators(&mut self, separators: &str)
pub fn set_word_separators(&mut self, separators: &str)
Replace the characters that end a word for SelectionType::Word — consumer
policy injected into a core mechanism (ADR-0017). Defaults to
DEFAULT_WORD_SEPARATORS. ' ' is forced in; see Term::set_word_separators
for why that floor is load-bearing rather than defensive.
Sourcepub fn word_separators(&self) -> &str
pub fn word_separators(&self) -> &str
The word-boundary set currently in force (including the forced ' ').
Sourcepub fn selection_clear(&mut self)
pub fn selection_clear(&mut self)
Clear the selection.
Sourcepub fn selection_range(&self) -> Vec<SelectionSpan>
pub fn selection_range(&self) -> Vec<SelectionSpan>
The selection projected onto the viewport: one inclusive-column span per visible row, for the renderer to highlight. Empty when nothing is selected or the selection is fully scrolled off-screen.
Sourcepub fn selection_text(&self) -> Option<String>
pub fn selection_text(&self) -> Option<String>
The selected text for copy (respects scrollback), or None if no
selection.
Sourcepub fn search(&self, query: &str) -> Vec<Match>
pub fn search(&self, query: &str) -> Vec<Match>
Literal search over the grid + scrollback, returning every match in
absolute buffer coordinates (top-to-bottom). Smart-case: a query with no
uppercase matches case-insensitively. The consumer drives next/prev by
walking the returned Vec and calling Engine::scroll_to_match.
Sourcepub fn search_with(&self, query: &str, opts: SearchOptions) -> Vec<Match>
pub fn search_with(&self, query: &str, opts: SearchOptions) -> Vec<Match>
Search with explicit SearchOptions — regex, whole-word, and a case-sensitivity override
beyond the literal + smart-case search (#314).
Sourcepub fn viewport_logical_lines(&self) -> Vec<LogicalLine>
pub fn viewport_logical_lines(&self) -> Vec<LogicalLine>
The viewport’s logical lines (#113/ADR-0017): each soft-wrap-joined line’s
text plus a per-char map to its viewport (row, col). The buffer-wide
mechanism for consumer-side URL detection — the consumer runs its own
regex / new URL() over the text and maps matches back through cells.
Also serves the a11y mirror (#119).
Sourcepub fn accessible_text(&self) -> String
pub fn accessible_text(&self) -> String
The whole buffer (scrollback + screen) as one text document for a
screen-reader accessible view (#150) — soft-wrap-joined, wide-spacers
skipped, trailing blanks trimmed at the logical end, \n between logical
lines. A query seam the consumer summons (frame mode: over IPC, like
selection_text); no wire-format change. On the
alt screen only the alt buffer is shown.
Sourcepub fn scroll_to_match(&mut self, m: &Match)
pub fn scroll_to_match(&mut self, m: &Match)
Scroll the viewport so m is visible (next/prev navigation: the consumer
picks the match, the engine scrolls to it).
Sourcepub fn match_spans(&self, m: &Match) -> Vec<SelectionSpan>
pub fn match_spans(&self, m: &Match) -> Vec<SelectionSpan>
The match projected onto the viewport as inclusive-column spans per visible row, for the renderer to highlight.
Sourcepub fn set_search_highlights(&mut self, matches: Vec<Match>)
pub fn set_search_highlights(&mut self, matches: Vec<Match>)
Set the search highlights the frame should carry (#108). The
consumer owns match navigation, so it hands the set to highlight back
here; Engine::frame then projects them onto the viewport overlay
alongside the selection. An empty vec clears the highlights.
Sourcepub fn set_active_search_highlight(&mut self, index: Option<usize>)
pub fn set_active_search_highlight(&mut self, index: Option<usize>)
Designate which member of the held highlight set is the active match
(#428) — the one next/prev navigation currently points at (that choice is
the consumer’s policy). Engine::frame projects it into the overlay’s
active_match group; it also stays in matches, and the renderer’s
highlight ranking resolves the overlap (#424). None or an out-of-range
index projects nothing. Passing a new set to
set_search_highlights resets the
designation, so re-designate after every hand-over.
Sourcepub fn set_active_search_match(&mut self, m: Option<Match>)
pub fn set_active_search_match(&mut self, m: Option<Match>)
Designate the active match by its absolute span (#436), independent of
the held highlight set — the past-cap path. A backend that caps its
hand-over (the documented 1000, xterm’s highlightLimit) can still give
the current match its active emphasis: xterm builds its active
decoration from the found result outside the capped list, and this is
that model. The span projects through the same wrap-aware viewport math
as any match; past the cap it paints the ACTIVE colour only (no plain
highlight underneath — honest about the cap). None clears. Same
lifecycle as the index form: reset on every
set_search_highlights hand-over and on
any coordinate-shifting invalidation (eviction, region scroll, reflow,
alt-screen swaps), so re-designate after each hand-over.
Sourcepub fn add_marker(&mut self, row: usize) -> MarkerId
pub fn add_marker(&mut self, row: usize) -> MarkerId
Register a decoration marker at viewport row, returning its stable id
(#118). The marker anchors the content currently on that row and tracks
it through scroll/eviction/reflow; Engine::frame reports its viewport
position while visible. Use the id to remove it or to match the
TermEvent::MarkerDisposed fired when its line leaves the buffer.
A buffer holds at most MAX_MARKERS live markers (#721) — the population is
also grown by the stream, through OSC 133 command marks, so it is bounded.
Past the cap the oldest marker is retired and announced through the same
MarkerDisposed event, so a consumer that already handles disposal needs no new
handling; a consumer that ignores it can leave a decoration bound to a dead id.
Sourcepub fn remove_marker(&mut self, id: MarkerId)
pub fn remove_marker(&mut self, id: MarkerId)
Remove a marker by id (#118), firing TermEvent::MarkerDisposed. A no-op
for an unknown or already-disposed id.
Sourcepub fn track_point(&mut self, line: usize, col: usize) -> TrackedId
pub fn track_point(&mut self, line: usize, col: usize) -> TrackedId
Track absolute buffer (line, col), returning a stable id (#691): the
engine keeps the position on the content that is there now, through
scrollback eviction, region scrolls and reflow.
This is what an absolute coordinate held outside the engine needs to stay
meaningful — a search anchor carrying an emphasis across a re-search is the
case it exists for. The engine renumbers this space (evicting the oldest
history line shifts every index down by one), and it renumbers it in the
consumer’s absence, so a remembered Match silently comes to name
different text.
Mechanism only: which position is worth remembering, and what to do once it
is gone, stay with the consumer (ADR-0017). Release it with
Engine::untrack_point — the engine cannot know when you are done.
The line is maintained; the column is carried, not tracked. In-row edits
(ICH / DCH) shift cells past a tracked column without moving it, so a point
on text that was pushed sideways names the wrong cell in that row. No
reference maintains a column here either — xterm’s markers carry none at
all, and ghostty’s pins are untouched by its insertChars/deleteChars —
so this is the convergent behaviour rather than an omission.
Sourcepub fn tracked_point(&self, id: TrackedId) -> Option<(usize, usize)>
pub fn tracked_point(&self, id: TrackedId) -> Option<(usize, usize)>
Where the point registered as id sits now, in the active screen’s
coordinates — or None (#691).
None covers three cases, and a caller does not need to tell them apart:
the content has left the buffer, the id is unknown or released, or the point
belongs to the other screen. The last one is not a limitation but the only
honest answer: the primary grid and the alt grid occupy the same absolute
indices, so a number alone cannot say which screen it means. All three say
do not move anything on account of this point.
An out-of-range coordinate is clamped rather than rejected, at both ends (ADR-0026 D2/D3): the line into the buffer’s range, the column to the grid width. That bound is applied here, at the read; a coordinate that was never in range to begin with is also resolved by a reflow (it maps to the top of the buffer), so “bounded once” holds for the site, not for the value.
Sourcepub fn untrack_point(&mut self, id: TrackedId)
pub fn untrack_point(&mut self, id: TrackedId)
Release a tracked point (#691). A no-op for an unknown or already-released id.
Sourcepub fn command_marks(&self) -> Vec<(MarkerId, usize, MarkerKind)>
pub fn command_marks(&self) -> Vec<(MarkerId, usize, MarkerKind)>
The OSC 133 shell-integration command marks in buffer order — (id, absolute line, kind) (#158). Excludes plain add_marker decorations.
The consumer pairs prompt/command/finished marks to drive prompt-to-prompt
navigation and command/exit announcements (#160); the engine only parses
the 133;A/B/C/D sequences and anchors the marks.
Sourcepub fn command_lines(&self) -> Vec<CommandLine>
pub fn command_lines(&self) -> Vec<CommandLine>
The executed shell commands recovered from OSC-133 marks, in buffer order
(#166) — the query behind screen-reader command navigation. Each
CommandLine carries the typed command text (prompt/output excluded via
the captured columns), its jump line (CommandStart), and the exit code.
This is a full-buffer query, wired to the frame-mode consumer over IPC like
Engine::accessible_text; the web side has no scrollback cells to derive
it (ADR-0017 — buffer-wide text is core’s).
Sourcepub fn marker_index(&self) -> MarkerIndex
pub fn marker_index(&self) -> MarkerIndex
Every live marker of the active buffer with its absolute buffer line, plus the basis that says how long the answer stays usable (#490).
The pull half of the marker surface, and the same shape as
Engine::command_lines: the consumer asks, keeps the answer, and rebases it
per frame by the evicted_total delta — rather than being handed every live
marker inside every frame, which is O(M) payload per frame for a quantity
unrelated to what changed (ADR-0020 R3).
Ask again when MarkerIndex::epoch differs from the one you hold. Drop an
entry when its TermEvent::MarkerDisposed arrives — a disposal deliberately
does not move the epoch, so it costs no re-pull.