Skip to content

Commit 5492423

Browse files
vt: own ghostty libghostty-vt via ix-vt (#332)
Draft. Owns the real terminal-emulation engine: ghostty's libghostty-vt, wrapped in our own public `ix-vt` crate. New files only; does not touch the vt100 path (the companion dashboard PR owns that). The vt100 to ix-vt swap is a later third PR. ## What this does - **`packages/vt/libghostty-vt`** (+ `lib/libghostty-vt.nix`): Nix derivation building ghostty's `libghostty-vt` static lib + self-contained dylib via `zig build -Demit-lib-vt=true` against a pinned ghostty source, with zig deps vendored for a network-free build. zig 0.15.x only. - **`packages/vt/ix-vt-sys`**: raw FFI, bindgen-generated `bindings.rs` (495 symbols) over the ghostty C headers; `build.rs` links the Nix-built lib. - **`packages/vt/ix-vt`**: safe wrapper. `Terminal::new` / `vt_write` / `resize` / a render-state snapshot exposing styled cells (fg/bg palette + RGB, bold/italic/underline/inverse), scrollback, and the cursor (position, visible, blinking, visual style bar/block/underline/hollow). Round-trip test in `tests/round_trip.rs`. ## Why own it instead of the community crate The community `libghostty-vt` crate's build.rs runs native `zig build` and fetches deps over the network, which fails the Nix purity and darwin link gates. The capability is identical, so we own the build, not the API. Proven by a spike: the lib built in ~57s and round-tripped a DECSCUSR bar cursor, cursor position, and `#cc6666` styled cells through the C API. ## Status - Committed and pushed. Left: confirm `nix build .#libghostty-vt` + `nix build .#ix-vt` + the round-trip test green under Nix, then `nix run .#lint`, then ready for review. ## Notes for the reviewer - zig 0.15.x exactly (requireZig is a comptime gate; 0.16 is rejected). - The static `.a` does not bundle its C++ deps (needs sibling `libhighway`/`libsimdutf`/`libutfcpp` + `-lc++`); the dylib is self-contained, so `build.rs` prefers it. - zig deps vendored via zon2nix. - Minor: this branch also touched `packages/mcp/src/server_instructions.txt` (+2 lines), which the companion PR also edits. Resolve at merge (let the dashboard PR win). Made with Claude (claude-opus-4-8). --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dcbac81 commit 5492423

23 files changed

Lines changed: 2615 additions & 10 deletions

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ members = [
2222
"packages/tui-dashboard",
2323
"packages/tui-node",
2424
"packages/tui-py",
25+
"packages/vt/ix-vt",
26+
"packages/vt/ix-vt-sys",
2527
]
2628
resolver = "3"
2729

flake.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@
5050
inputs.nixpkgs.follows = "nixpkgs";
5151
inputs.rust-overlay.follows = "rust-overlay";
5252
};
53+
54+
# Ghostty's terminal VT engine, consumed as a source tree (not a flake) so
55+
# `packages/vt/libghostty-vt` owns the build. Pinned to the commit the
56+
# local clone validated against; `requireZig` in `build.zig.zon` is exact
57+
# minor (0.15.x), so the build uses `pkgs.zig_0_15`. The pinned tree ships
58+
# `build.zig.zon.nix` (zon2nix output), which vendors every lazy Zig
59+
# dependency with SRI hashes for a network-free build. Bump by repointing
60+
# this rev and running `nix flake update ghostty`.
61+
ghostty = {
62+
url = "github:ghostty-org/ghostty/fd49716ea2084108aa098db390931c007495a1ab";
63+
flake = false;
64+
};
5365
};
5466

5567
outputs =
@@ -60,6 +72,7 @@
6072
hermes-agent,
6173
symphony,
6274
clippy-fork,
75+
ghostty,
6376
...
6477
}:
6578
let
@@ -104,6 +117,7 @@
104117
hermes-agent
105118
symphony
106119
clippy-fork
120+
ghostty
107121
;
108122
};
109123
devSystems = [

lib/cargo-unit.nix

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -459,17 +459,84 @@ let
459459
meta ? { },
460460
passthru ? { },
461461
}:
462+
selectRootWithTests workspace {
463+
rootDrv = workspace.binaries.${binary} or workspace.default;
464+
inherit
465+
packageName
466+
testTargets
467+
doctestTargets
468+
includeTestCases
469+
meta
470+
passthru
471+
;
472+
defaultTestTargets = [ binary ];
473+
};
474+
475+
/**
476+
Pick a library target from a pre-built `buildWorkspace` plus its test and
477+
doctest derivations, ready for `passthru.tests` consumption.
478+
479+
The library version of `selectBinaryWithTests`, for crates that ship a
480+
`lib` target rather than a binary. `library` is the crate's library unit
481+
key (Cargo's underscored name, e.g. `ix_vt`); `packageName` is the Cargo
482+
package name used to look up test targets (e.g. `ix-vt`).
483+
*/
484+
selectLibraryWithTests =
485+
workspace:
486+
{
487+
library,
488+
packageName,
489+
testTargets ? null,
490+
doctestTargets ? null,
491+
includeTestCases ? true,
492+
meta ? { },
493+
passthru ? { },
494+
}:
495+
selectRootWithTests workspace {
496+
rootDrv =
497+
workspace.libraries.${library}
498+
or (throw "selectLibraryWithTests: no library `${library}` in workspace; available: ${
499+
lib.concatStringsSep ", " (builtins.attrNames (workspace.libraries or { }))
500+
}");
501+
inherit
502+
packageName
503+
testTargets
504+
doctestTargets
505+
includeTestCases
506+
meta
507+
passthru
508+
;
509+
defaultTestTargets = [ packageName ];
510+
};
511+
512+
# Shared core for `selectBinaryWithTests` / `selectLibraryWithTests`: take a
513+
# selected root derivation and assemble its `passthru.tests` from the shared
514+
# workspace's test/doctest targets and policy checks.
515+
selectRootWithTests =
516+
workspace:
517+
{
518+
rootDrv,
519+
packageName,
520+
defaultTestTargets,
521+
testTargets ? null,
522+
doctestTargets ? null,
523+
includeTestCases ? true,
524+
meta ? { },
525+
passthru ? { },
526+
}:
462527
let
463-
binaryDrv = workspace.binaries.${binary} or workspace.default;
464-
uncheckedBinary = binaryDrv.passthru.unchecked or binaryDrv;
528+
uncheckedRoot = rootDrv.passthru.unchecked or rootDrv;
465529
namesForPackage =
466530
attrName: fallback:
467531
if builtins.hasAttr attrName workspace && builtins.hasAttr packageName workspace.${attrName} then
468532
workspace.${attrName}.${packageName}
469533
else
470534
fallback;
471535
selectedTestTargets =
472-
if testTargets == null then namesForPackage "testTargetNamesByPackage" [ binary ] else testTargets;
536+
if testTargets == null then
537+
namesForPackage "testTargetNamesByPackage" defaultTestTargets
538+
else
539+
testTargets;
473540
selectedDoctestTargets =
474541
if doctestTargets == null then
475542
namesForPackage "doctestTargetNamesByPackage" [ ]
@@ -494,20 +561,20 @@ let
494561
flattenCaseTargets "" selectedTestTargets (workspace.tests or { })
495562
// flattenCaseTargets "doctest-" selectedDoctestTargets (workspace.doctests or { });
496563
tests = {
497-
package = uncheckedBinary;
564+
package = uncheckedRoot;
498565
}
499566
// flattenAllTargets "" selectedTestTargets (workspace.tests or { })
500567
// flattenAllTargets "doctest-" selectedDoctestTargets (workspace.doctests or { })
501568
// lib.optionalAttrs includeTestCases testCases;
502569
in
503-
binaryDrv
570+
rootDrv
504571
// {
505-
meta = (binaryDrv.meta or { }) // meta;
572+
meta = (rootDrv.meta or { }) // meta;
506573
passthru =
507-
(binaryDrv.passthru or { })
574+
(rootDrv.passthru or { })
508575
// passthru
509576
// {
510-
tests = (binaryDrv.passthru.tests or { }) // policyChecks // (passthru.tests or { }) // tests;
577+
tests = (rootDrv.passthru.tests or { }) // policyChecks // (passthru.tests or { }) // tests;
511578
inherit policyChecks;
512579
inherit (workspace) policy;
513580
};
@@ -581,6 +648,7 @@ in
581648
buildBinaries
582649
buildWorkspace
583650
selectBinaryWithTests
651+
selectLibraryWithTests
584652
auditCargoLock
585653
generateUnitGraph
586654
generateUnitsNix

lib/default.nix

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
hermes-agent,
99
symphony,
1010
clippy-fork,
11+
ghostty,
1112
cliArtifacts ? { },
1213
}:
1314
let
@@ -79,6 +80,7 @@ let
7980
};
8081
buildNpmVitest = import ./build-npm-vitest.nix;
8182
buildZigPackage = import ./build-zig-package.nix { };
83+
buildLibghosttyVt = import ./libghostty-vt.nix { inherit lib writeNushellApplication; };
8284
uvLockFor =
8385
pkgs:
8486
import ./uv-lock.nix {
@@ -237,6 +239,7 @@ let
237239
rustWorkspaceFor
238240
cliArtifacts
239241
clippy-fork
242+
ghostty
240243
;
241244
};
242245

@@ -257,6 +260,8 @@ let
257260
paths
258261
packageRegistry
259262
cargoUnitFor
263+
ghostty
264+
writeNushellApplication
260265
;
261266
};
262267
rustWorkspace = rustWorkspaceFor pkgs;
@@ -277,6 +282,7 @@ let
277282
buildSvelteSite
278283
buildUvApplication
279284
buildZigPackage
285+
buildLibghosttyVt
280286
cargoUnit
281287
goUnit
282288
languages
@@ -361,6 +367,7 @@ let
361367
buildSvelteSite
362368
buildUvApplication
363369
buildZigPackage
370+
buildLibghosttyVt
364371
bunLockFor
365372
cargoUnit
366373
cargoUnitFor

0 commit comments

Comments
 (0)