Skip to content

Engine

wormhole-vt, the engine under Wormhole

A terminal engine in Rust. Bytes from a program go in; the screen, the scrollback and the events the program caused come out. Wormhole runs on it, and it is a library of its own: MIT licensed, so other projects can build on it too.

Status

Version 0.1, beta. Until 1.0 the API may change between minor versions.

The crate

MIT · Rust 1.88 or later · depends on bitflags and memchr

Source on GitHub

Why

A terminal engine you can read and embed

It was developed inside Wormhole and moved to its own repository with its history, so anything that draws a terminal can use it.

01

Written from the specifications

The parser follows Paul Williams' DEC state machine; the sequences follow xterm's Control Sequences, the DEC manuals and ECMA-48. Each conformance case names the line of text it rests on. Nothing is copied or ported from another terminal.

02

Fast, and steady about it

Runs of printable ASCII are found a word at a time, and stored cells are eight bytes. With the same terminal and more output, most kinds of output allocate nothing, and counting tests fail when that changes.

03

Safe against hostile output

A terminal reads whatever a program prints. The parser is total, every string and table has a cap, and anything risky (the clipboard, window control, reading the screen back) is a request to the host, off until the host allows it.

04

Made to be embedded

The host owns the PTY, the threads and the drawing. The engine tells it what changed, so a frame copies only the rows that did.

05

No unsafe code, two dependencies

The engine forbids unsafe. It depends on bitflags and memchr, and nothing else: no PTY, no window, no threads, no I/O.

06

Tested from both ends

Every conformance case runs twice, with the bytes fed whole and a byte at a time. CI runs on Linux, macOS and Windows, with fuzzing, mutation testing and three outside suites as gates.

Conformance

Tested against the text, not against another terminal

Most conformance cases quote the manual or protocol document they follow; the rest are marked as conventions. Three suites the project didn't write check it from outside.
875
conformance cases, 752 quoting the published text they follow, 123 marked as conventions
544 / 567
esctest2 tests pass; 21 of the failures are Xcms colour forms, two want a printer port
15,012
of 15,012 ucs-detect sequences sized as expected, with grapheme mode 2027 on

Against other engines

The cases are the project's own, written from the same specifications as the engine. Passing all of them means the suite and the engine agree, not that the engine is complete; another engine's failure is a difference from this reading of the text.

EngineCases passed, of 758
wormhole-vt758 of 758
libghostty-vt (a commit of main)558
wezterm-term (a commit of main)484
alacritty_terminal 0.26.0447
vt100 0.16.2313 of the 581 it can be asked

Speed

Fast on the output terminals actually get

The engine alone, bytes in and screen updated, with no drawing. Megabytes per second over nine kinds of output; higher is better.
Outputwormhole-vtalacritty_terminallibghostty-vtwezterm-termvt100
ascii-flood2,7642331,80067257
log-lines1,5282481,20766241
sgr-churn37923216549253
unicode-mix33020741470212
tui-redraw75424446542210
scroll-region80921264857220
osc8-links4691912770299
payloads5694221,008291389
agent-session53432023171353

The fastest in each row is in colour; libghostty-vt leads on unicode-mix and payloads. One run on a laptop (Apple M5 Pro, 2026-10-15) with other work going on, so a guide and not a verdict: small differences prove nothing, the engines compute different things while they parse, and each runs behind an adapter the project wrote. The engine has been tuned since, and the table has not been measured again. Rerun it yourself with cargo run --release --bin vt-bench in tools/vt-compare.

Use it

A screen in a few lines

The host owns the PTY and the drawing: it feeds the engine the program's bytes, writes its replies back, and reads the screen when it draws.

Cargo.toml

Not on crates.io yet: until it is, depend on the repository. The crate has no features for users.

TOML
[dependencies]
wormhole-vt = { git = "https://github.com/wormhole-terminal/wormhole-vt" }

The quick start

Feed it bytes in any chunking, write its replies back to the PTY, and read what changed. examples/quickstart.rs in the repository is the whole program.

Rust
use wormhole_vt::{Event, Terminal};

let mut term = Terminal::new(80, 24);

// Bytes from the program, in any chunking: a sequence may be cut anywhere.
term.feed(b"\x1b[1;31mhello\x1b[0m\r\n");
term.feed(b"\x1b]2;my title\x07");
term.feed(b"\x1b[6n"); // the program asks where the cursor is

// What the program is waiting to read: write these bytes back to the PTY.
assert_eq!(term.take_replies(), b"\x1b[2;1R");

// What happened besides drawing.
let events: Vec<Event> = term.events().collect();
assert!(events.iter().any(|event| matches!(event, Event::Title)));
assert_eq!(term.title(), Some("my title"));

What it does

From VT100 to the kitty protocols

Not done, on purpose or for now, among others: the printer, pages, the VT500's Greek, Hebrew, Turkish and Cyrillic sets, ReGIS, soft fonts and the status line. It never reads files, runs programs or loads fonts.
Parser
The DEC/ANSI state machine, UTF-8, C0 and C1, CSI with sub-parameters, and OSC, DCS and APC strings streamed in pieces. Usable on its own, without a screen.
Conformance levels
VT100 to VT500, the device attributes and status reports, XTGETTCAP, XTVERSION and VT52 mode.
Screen editing
Scrolling regions and left/right margins, insert and delete, tab stops, origin mode, delayed wrap, selective erase, double-width and double-height lines, rectangular operations.
Text style and colour
Bold to overline, five underline styles and underline colour, 16, 256 and true colours, palette and dynamic colours with queries.
Strings and marks
Titles, folders (OSC 7), hyperlinks (OSC 8), shell marks (OSC 133), progress and notifications, the clipboard as a host request.
Images
The kitty graphics protocol with Unicode placeholders, Sixel decoded by the engine, and iTerm2 inline images. The engine places them; the host draws.
Unicode
Unicode 18 cell widths, extended grapheme clusters with mode 2027 on by default, emoji variation selectors.
Scrollback and reflow
History capped in bytes; soft-wrapped lines rewrap on resize, with the cursor and pins carried along.
Keyboard and mouse
The kitty keyboard protocol, modifyOtherKeys, bracketed paste and focus; mouse modes 9, 1000, 1002 and 1003 in five encodings, and the DEC locator.
Host side
Damage tracking and row copies into reusable buffers, selection and text extraction, pins and spans that follow text through scrolling and reflow.
Repaint writer
Serializes a terminal into the VT bytes that rebuild it in another, for attach and resync.
Policy
What a program may do beyond printing (window operations, reading the screen back) stays off until the host allows it.

The guide

Documentation in the repository

The crate's README is the start; its guide goes on chapter by chapter.

01

Concepts

The terminal as a state machine: cells, modes, replies, events, pins.

02

Embedding

The PTY loop, threads, resizing, scrollback, limits, policy, attach.

03

Rendering

Damage, rows and cells, the cursor, selection, links, pictures, colours.

04

Input

Keys, mouse, paste and focus as bytes for the program.

05

Security

The threat model, the guarantees, what the host must still do.

06

Conformance

How the engine is tested, and how to check it yourself.

07/Contribute

Bring the bytes

A bug report with the bytes that cause it is the most useful thing there is: a hex dump or a file, what the screen shows, and what another terminal (xterm, ideally) shows instead. Security problems go through the security policy, not a public issue.