// STRYKE-TERMINAL — ENGINEERING REPORT

faithful port of pyte 0.8.2 · VT100 / VT220 / TERM=linux · v0.1.0

Docs GitHub
// Color scheme

>_ENGINEERING REPORT

How stryke-terminal ports pyte 0.8.2 to Rust and proves the port faithful. Every module maps 1:1 to a pyte module; the one structural transform is the parser (a Python generator-coroutine becomes an explicit state machine).

Module map

The 2,420-line pyte package (8 modules) ports module-for-module. Every handler keeps its pyte name.

RustpyteNotes
control.rscontrol.pyC0/C1 control-char constants
escape.rsescape.pynon-CSI + CSI opcode constants
modes.rsmodes.pyterminal mode switches (private modes <<5)
graphics.rsgraphics.pySGR color / attribute maps + 256-color table
charsets.rscharsets.pyLAT1 / VT100 / IBMPC / VAX42 translation tables
wcwidth.rs(wcwidth lib)cell width + combining-class, via unicode-width
screens.rsscreens.pyChar / Cursor / Screen + HistoryScreen + dis()
streams.rsstreams.pyStream / ByteStream parser (generator → state machine)

Design decisions

  • Parser: generator → state machine. pyte's _parser_fsm is a Python coroutine driven by yield. It is reified into an explicit enum State { Ground, Escape, Csi, Osc, … } stepped one character at a time. Every branch corresponds to a branch of the Python FSM, including the plain-text fast path that draws runs of printable characters in one call.
  • Sparse styled buffer. Each line is a HashMap<col, Char> with a static default, mirroring pyte's StaticDefaultDict: reading an empty cell returns the default without storing it, so a mostly-blank screen stays cheap.
  • History folded in. pyte splits HistoryScreen into a subclass whose __getattribute__ wraps every event with before/after hooks. Rust has no such dynamic dispatch, so scrollback is an optional component on Screen; the before/after hooks run around each event and are no-ops when history is absent.
  • i64 coordinates. Cursor position and counts are signed, matching Python integer arithmetic that transiently goes negative before ensure_hbounds / ensure_vbounds clamp it.
  • Device reports captured. pyte's write_process_input is a no-op; here it buffers DA / DSR / cursor-position replies so a script can forward them to the child with pty_send.

The FFI boundary

Args and results cross the boundary as JSON. Terminal data is full of control bytes (ESC, CR, LF, BEL), and stryke's to_json / from_json use raw control bytes in strings rather than \uXXXX escapes. The cdylib matches that convention on both sides: incoming payloads have their raw control bytes escaped so a strict parser accepts them, and outgoing JSON is written with a custom formatter that emits control bytes raw. This is lossless and is what makes take_input data round-trip byte-for-byte.

Fidelity testing

Correctness is pinned against the reference implementation, not asserted by hand. tests/gen_golden.py feeds pyte 0.8.2 a battery of escape / CSI / OSC inputs and records the resulting display, cursor, styled buffer, titles, device reports, and scrollback. The committed tests/golden.json is replayed through the Rust port, which must reproduce every field exactly — so CI needs no Python.

SuiteCasesChecks
golden screen (vs pyte)41display + cursor + full styled buffer + title + device reports + scrollback
golden disassembler (vs DebugScreen)6event stream = pyte's dis()
Rust unit (JSON interop)4control-byte sanitize / raw-emit round-trip
stryke assertions (t/)37end-to-end through the installed cdylib

Coverage spans plain text and auto-wrap, cursor movement, erase-in-line/display, SGR (16-color, 256-color, truecolor, attributes), scrolling with margins, index / reverse-index, tab stops, insert/delete lines and characters, insert mode, charsets (including VT100 line-drawing in 8-bit mode), wide (CJK) and combining characters, OSC titles, device status / attribute reports, and scrollback paging.

Provenance & license

MIT. This is a port of pyte (LGPL upstream). Per pyte's own notes, the VT100 / IBMPC charset tables are reproduced verbatim from the Linux kernel and the 256-color table from Pygments, for emulation parity. One upstream typo in an aixterm color name is preserved verbatim so SGR output matches the reference byte-for-byte.