hone-terminal (@honeide/terminal) is a cross-platform terminal emulator component for Perry applications. It follows the same architecture as ../hone-editor — TypeScript core logic with platform-native rendering via Rust FFI crates.
bun test # Run all 163 tests (10 files, ~44ms)
bun test --watch # Watch mode
npx tsc --noEmit # Type check
# Rust FFI crate (macOS)
cd native/macos
cargo check # Type check Rust
cargo build --release # Build libhone_terminal_macos.a
cargo run --example demo_terminal # Standalone Rust demo
# Rust FFI crate (Windows)
cd native/windows
cargo check # Type check Rust
cargo build --release # Build hone_terminal_windows.lib
cargo run --example demo_terminal # Standalone Rust demo
# Perry demo app (macOS)
./examples/standalone-terminal/build.sh # Full build (Rust + Perry + link)
./examples/standalone-terminal/hone-terminal-demo # Run
# Rust FFI crate (Linux)
cd native/linux
cargo check # Type check Rust
cargo build --release # Build libhone_terminal_linux.a
cargo run --example demo_terminal # Standalone Rust demo
# Perry demo app (Windows)
examples\standalone-terminal\build-windows.bat
examples\standalone-terminal\hone-terminal-demo.exe
# Perry demo app (Linux)
./examples/standalone-terminal/build-linux.sh # Full build (Rust + Perry + link)
./examples/standalone-terminal/hone-terminal-demo # Runcore/— Terminal engine.TerminalEmulatororchestrates VT parser, screen buffer, scrollback, PTY, and input encoding. Zero external dependencies for parsing.core/vt-parser/— 14-state VT100/xterm parser. Uses plain numeric constants (notconst enum) for Perry compatibility.core/buffer/—ScreenBuffer(active grid) +Scrollback(ring buffer). Dirty-line tracking for incremental rendering.core/input/— Key encoder (xterm sequences) and mouse encoder (SGR mode).view-model/—CellGridtranslates buffer state toRenderCellJSON for FFI. Theme, cursor, selection, search state.perry/— Perry component API.Terminalclass wraps emulator + render coordinator.declare functionFFI bindings resolved by Perry codegen.native/—NativeTerminalFFIinterface contract +NativeRenderCoordinator(dirty-tracking bridge from emulator to FFI calls).NoOpFFIfor testing.
native/macos/src/lib.rs— 10#[no_mangle] extern "C"functions +hone_terminal_show_demodemo function. Perry expects__wrapper_<name>symbol convention.native/macos/src/terminal_view.rs—TerminalViewstruct: cell grid, cursor, selection, theme.draw()renders via Core Graphics (backgrounds → selection → text → cursor).native/macos/src/grid_renderer.rs— Core Text font rendering.FontSetwith normal/bold/italic/bold-italic variants viaclone_with_symbolic_traits.native/macos/src/view.rs—HoneTerminalViewNSView subclass registered via objc runtime. Handles drawRect, keyboard, mouse, scroll input.
native/windows/src/lib.rs— 10#[no_mangle] extern "C"functions +hone_terminal_show_demodemo function. Same FFI contract as macOS. Default font: Consolas.native/windows/src/terminal_view.rs—TerminalViewstruct: same data model as macOS.draw()renders viaID2D1RenderTarget(Direct2D). UsesInvalidateRectfor redraw.native/windows/src/grid_renderer.rs— DirectWrite font rendering.FontSetwith 4IDWriteTextFormatvariants (weight/style params). Fallback: Consolas → Courier New.native/windows/src/view.rs—HoneTerminalViewWin32WNDCLASSEXW+WndProc. Handles WM_PAINT (Direct2D), WM_SIZE (render target resize), WM_CHAR/KEYDOWN, WM_LBUTTONDOWN, WM_MOUSEWHEEL.
native/linux/src/lib.rs— 10#[no_mangle] extern "C"functions +hone_terminal_show_demodemo function. Same FFI contract as macOS/Windows. Default font: DejaVu Sans Mono.native/linux/src/terminal_view.rs—TerminalViewstruct: same data model as macOS/Windows.draw()renders via Cairo context (backgrounds → selection → text → cursor). UsesXSendEventExpose for redraw.native/linux/src/grid_renderer.rs— Pango font rendering.FontSetwith 4pango::FontDescriptionvariants (weight/style). Font fallback handled by Pango + fontconfig automatically.native/linux/src/view.rs— X11 window viaXCreateSimpleWindow. Blocking event loop handles Expose (Cairo XlibSurface), ConfigureNotify, KeyPress, ButtonPress, ClientMessage (WM_DELETE_WINDOW).
- No
const enum— Perry doesn't support TypeScriptconst enum. Use plain numeric constants with a companion object (State_Ground = 0; const State = { Ground: State_Ground } as const). - Perry closure limitations — Perry has codegen bugs with many closures/callbacks. The emulator's
setupParser()uses.bind(this)methods instead of arrow functions. Some modules can't be imported in Perry demos due to closure ID off-by-one errors. - FFI string passing — Perry passes strings as
i64pointers. Indeclare function, string params usenumbertype and are cast withas any. Rust receives*const c_char. - FFI wrapper symbols — Perry calls
__wrapper_<function_name>(double underscore prefix). The Rust crate must export both<function_name>and__wrapper_<function_name>. - Heap allocation for FFI views — When creating NSView-backed objects in FFI functions, use
Box::leak(Box::new(...))to keep the allocation alive. Stack-allocated views cause use-after-free in draw callbacks. - JSON for complex data — Cell grids, selections, themes, and tokens are serialized as JSON strings across the FFI boundary. Rust uses
serde_jsonto deserialize.
Perry doesn't yet auto-link FFI crates. The manual process is:
macOS:
cargo build --releaseinnative/macos/→ produceslibhone_terminal_macos.aperry compile main.ts --no-link --keep-intermediates→ producesmain_ts.oclang++ main_ts.o -lhone_terminal_macos -lperry_runtime -lperry_stdlib -lperry_ui_macos -framework AppKit ...→ linked binary
The examples/standalone-terminal/build.sh script automates this.
Windows:
cargo build --releaseinnative/windows/→ produceshone_terminal_windows.libperry compile main.ts --no-link --keep-intermediates→ producesmain_ts.objlink.exe main_ts.obj hone_terminal_windows.lib perry_runtime.lib ... d2d1.lib dwrite.lib user32.lib ...→ linked binary
The examples/standalone-terminal/build-windows.bat script automates this.
Linux:
cargo build --releaseinnative/linux/→ produceslibhone_terminal_linux.aperry compile main.ts --no-link --keep-intermediates→ producesmain_ts.oclang++ main_ts.o -lhone_terminal_linux -lperry_runtime -lperry_stdlib -lperry_ui_linux $(pkg-config --libs pango pangocairo cairo x11) ...→ linked binary
The examples/standalone-terminal/build-linux.sh script automates this.
Tests use Bun's built-in test runner. All tests are in tests/ mirroring core/ structure:
tests/buffer/— cell, screen-buffer, scrollbacktests/vt-parser/— parser state machine, CSI dispatch, OSC dispatch, SGR attributestests/input/— key encoding, mouse encodingtests/emulator.test.ts— integration tests for the full emulator
- COM initialization — Direct2D/DirectWrite require
CoInitializeEx(COINIT_APARTMENTTHREADED)before use. windowscrate features —Foundation_Numericsis required forCreateSolidColorBrushand other D2D rendering methods. Parent interface methods (e.g.ID2D1RenderTargetmethods) are not inherited via Deref in v0.58; useInterface::cast()to access them fromID2D1HwndRenderTarget.- Render target recreation — Handle
D2DERR_RECREATE_TARGETfromEndDraw()by discarding and recreating on next WM_PAINT. BeginPaint/EndPaint— Must call in WM_PAINT even though D2D renders independently, otherwise WM_PAINT re-posts infinitely.
- System libraries — Requires cairo, pango, and X11 development headers. Install:
libcairo2-dev libpango1.0-dev libx11-dev(Debian/Ubuntu),cairo-devel pango-devel libX11-devel(Fedora),cairo pango libx11(Arch). - Cairo XlibSurface — Created per-Expose event from the X11 display/window/visual. Surface size updated on ConfigureNotify.
- Pango font metrics — Use
pango::SCALE(1024) when setting font sizes. Metrics fromContext::metrics()are in Pango units; divide bySCALEfor pixels. - Font fallback — Pango delegates to fontconfig, which handles system-wide font substitution. No manual fallback chain needed.
- Coordinate system — Cairo + X11 both use top-left origin with Y-down, so no coordinate flipping is needed (unlike macOS Core Graphics).
- TypeScript: zero runtime dependencies (Bun for testing only)
- Rust (macOS): core-text, core-graphics, cocoa, objc, serde_json, core-foundation-sys
- Rust (Windows): windows (v0.58, Direct2D + DirectWrite + Win32), serde_json
- Rust (Linux): cairo-rs (0.20, xlib feature), pango (0.20), pangocairo (0.20), x11 (2.21, xlib feature), serde_json