>_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.
| Rust | pyte | Notes |
|---|---|---|
| control.rs | control.py | C0/C1 control-char constants |
| escape.rs | escape.py | non-CSI + CSI opcode constants |
| modes.rs | modes.py | terminal mode switches (private modes <<5) |
| graphics.rs | graphics.py | SGR color / attribute maps + 256-color table |
| charsets.rs | charsets.py | LAT1 / VT100 / IBMPC / VAX42 translation tables |
| wcwidth.rs | (wcwidth lib) | cell width + combining-class, via unicode-width |
| screens.rs | screens.py | Char / Cursor / Screen + HistoryScreen + dis() |
| streams.rs | streams.py | Stream / ByteStream parser (generator → state machine) |
Design decisions
- Parser: generator → state machine. pyte's
_parser_fsmis a Python coroutine driven byyield. It is reified into an explicitenum 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'sStaticDefaultDict: reading an empty cell returns the default without storing it, so a mostly-blank screen stays cheap. - History folded in. pyte splits
HistoryScreeninto a subclass whose__getattribute__wraps every event with before/after hooks. Rust has no such dynamic dispatch, so scrollback is an optional component onScreen; 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_vboundsclamp it. - Device reports captured. pyte's
write_process_inputis a no-op; here it buffers DA / DSR / cursor-position replies so a script can forward them to the child withpty_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.
| Suite | Cases | Checks |
|---|---|---|
| golden screen (vs pyte) | 41 | display + cursor + full styled buffer + title + device reports + scrollback |
| golden disassembler (vs DebugScreen) | 6 | event stream = pyte's dis() |
| Rust unit (JSON interop) | 4 | control-byte sanitize / raw-emit round-trip |
| stryke assertions (t/) | 37 | end-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.