Ports ZIG/src/lib_vt.zig and terminal/c/* as crates/ghostty-vt-capi (cdylib + staticlib + rlib, soname libghostty-vt.so) implementing the unchanged include/ghostty/vt.h + vt/*.h ABI: OSC parser, SGR parser, key events and key encoding, paste safety, colors, result codes - all 38 native symbols, plus the ghostty_wasm_* helpers on wasm32. Verification: - tests/abi.rs generates a C probe from the headers, compiled with cc, that checks sizeof/alignof/offsetof of every struct/union, every enumerator and #define against Rust, and links every declared symbol; nm -D exports equal the declared functions. - Ghostty's example/c-vt* programs compile unchanged against the Rust library (shared and static) and produce the expected output. - The wasm32 build runs Ghostty's wasm-key-encode/wasm-sgr example scripts unmodified under node (upstream's wasm-sgr tag table is stale vs sgr.h; a regenerated copy checks full output). Test Plan: - cargo test -p ghostty-vt-capi (22 unit + 2 ABI + 5 C + 3 wasm tests)
5.2 KiB
libghostty-vt C API (Phase 8, agent L2)
Crate crates/ghostty-vt-capi (crate types cdylib + staticlib + rlib).
Depends only on ghostty-vt and ghostty-input (no ghostty-terminal), so it
builds for wasm32-unknown-unknown.
Mapping
| Zig | Rust |
|---|---|
lib_vt.zig @export list |
#[unsafe(no_mangle)] extern "C" fns (38 native symbols) |
terminal/c/color.zig |
color.rs (ghostty_color_rgb_get, GhosttyColorRgb) |
terminal/c/osc.zig |
osc.rs |
terminal/c/sgr.zig |
sgr.rs (+ wasm attribute helpers in wasm.rs) |
terminal/c/key_event.zig |
key_event.rs |
terminal/c/key_encode.zig |
key_encode.rs |
terminal/c/paste.zig |
paste.rs |
terminal/c/result.zig |
result.rs |
lib/allocator.zig (C allocator) |
alloc.rs |
lib/allocator/convenience.zig |
wasm.rs (exported only on wasm32) |
Headers are copied unchanged to include/ghostty/ (diff -r against
ZIG/include/ghostty is empty). The C examples (examples/c/*.c) and wasm
examples (examples/wasm/*.html) are unchanged copies of
ZIG/example/{c-vt,c-vt-key-encode,c-vt-paste,c-vt-sgr,wasm-key-encode,wasm-sgr}.
Library file names
Cargo forbids hyphens in [lib] name, so the artifacts are
libghostty_vt_capi.{so,a} / ghostty_vt_capi.wasm. build.rs sets the
soname to libghostty-vt.so; just vt-lib [profile] (and the integration
tests) install copies as libghostty-vt.{so,a} and ghostty-vt.wasm
(upstream's names).
Tests
- unit tests: ports of the Zig
testblocks interminal/c/*.zig(22 tests; thelib_alloc.test_allocatorleak-check variants map to plain alloc/free round trips). tests/abi.rs: generates a C probe from the headers (enum typedefs and#defines are parsed from the header text, every declared function's address is taken), compiles it withcc, links it againstlibghostty-vt.so, and compares sizeof/alignof/offsetof of every struct/union, sizeof of every enum and typedef, the value of every enumerator and#defineagainst the Rust definitions (size_of/align_of/offset_of!,Key::ALL, discriminants). A second test checksnm -Dexports == declared functions.tests/c_examples.rs: compiles the four C examples withccand checks stdout (upstream has no expected-output files; the strings were derived by reading the example sources), plus a static-link run againstlibghostty-vt.a.tests/wasm_examples.rs+tests/wasm/run.mjs: builds--target wasm32-unknown-unknown, checks every header function (including the__wasm__-onlyghostty_wasm_*) is exported and that the module has no imports, then runs the two wasm example pages' unmodified<script>under node with a DOM stub; key-encode output is compared against the native Rust encoder, SGR output against literals. Skipped (with a note) ifnodeor the wasm target is missing.
cargo test -p ghostty-vt-capi runs all of it (just vt-test). The tests run a
nested cargo build of the crate (and the wasm target) into the same target dir.
Deviations / notes
unsafe_code = "deny"for this crate (lints table copied from the workspace becauseworkspace = truecannot be partially overridden); FFI modules carry#![allow(unsafe_code)]and SAFETY comments.- Enum parameters from C (
action,key,option,data) are taken asc_intand validated (Zig only validates in safe builds); invalid values are ignored/rejected like Zig'sruntime_safetypath. Null handles are no-ops / return defaults instead of Zig's.?panic. - Wrapper objects and the SGR parameter copy use the caller's
GhosttyAllocator(copied by value at*_new); interior Rust collections (OSC buffer) always use the Rust global allocator. ghostty_osc_endreturns a pointer to a command slot owned by the parser (Zig returns a pointer to the parser's command); the window title is re-materialized NUL-terminated there. Valid until the nextend/free.GhosttyOscCommandTypecan also yield 23/24 (kitty_clipboard_protocol,context_signal) which exist in the Rust/Zig parser but not inosc.h.- Key event
utf8is a Rust&stringhostty-input; bytes that are not valid UTF-8 are truncated to the valid prefix when encoding.get_utf8returns the caller's original pointer/len. Unknown mod bits (padding) are dropped onset_mods. ghostty_sgr_set_paramswithseparators != NULLandlen > 24returnsINVALID_VALUEafter replacing the params (same order as Zig). Non-NULL requiredparamswithlen > 0and NULL pointer returnsINVALID_VALUE(Zig: UB).- Upstream wasm builds import
env.logforstd.log; the Rust build has no host imports (thelogcrate has no logger installed). - Upstream finding:
example/wasm-sgr/index.htmlhas a staleSGR_ATTR_TAGStable (header changed in upstream commita384af5e2, example not updated), so the stock page mislabels e.g. direct colors. The test runs the stock page (checks only the still-correct underline line) and a copy whose table is regenerated fromsgr.h(checks full output). - The Zig reference could not be built here (needs Zig 0.15; 0.16 installed), so outputs were validated against the Zig sources and the protocol rather than a Zig-built library.