5a01c1f3
feat(vt): Terminal facade wrapping ghostty-vt
a73x 2026-04-08 06:08
Commit message
src/vt.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,4 +1,4 @@ | |||
| 1 | //! Thin wrapper around the ghostty-vt Zig module. | 1 | //! Thin facade over the ghostty-vt Zig module. |
| 2 | //! | 2 | //! |
| 3 | //! ## ghostty-vt API notes (as of ghostty-1.3.2-dev) | 3 | //! ## ghostty-vt API notes (as of ghostty-1.3.2-dev) |
| 4 | //! | 4 | //! |
| @@ -50,7 +50,111 @@ | |||
| 50 | const std = @import("std"); | 50 | const std = @import("std"); |
| 51 | const ghostty_vt = @import("ghostty-vt"); | 51 | const ghostty_vt = @import("ghostty-vt"); |
| 52 | 52 | ||
| 53 | test "ghostty-vt module imports" { | 53 | pub const Terminal = struct { |
| 54 | // Smoke test — just reference the module so the import is verified | 54 | alloc: std.mem.Allocator, |
| 55 | _ = ghostty_vt; | 55 | inner: ghostty_vt.Terminal, |
| 56 | stream: ghostty_vt.TerminalStream, | ||
| 57 | render_state: ghostty_vt.RenderState, | ||
| 58 | |||
| 59 | pub const InitOptions = struct { | ||
| 60 | cols: u16, | ||
| 61 | rows: u16, | ||
| 62 | max_scrollback: u32 = 1000, | ||
| 63 | }; | ||
| 64 | |||
| 65 | pub fn init(alloc: std.mem.Allocator, opts: InitOptions) !Terminal { | ||
| 66 | var inner = try ghostty_vt.Terminal.init(alloc, .{ | ||
| 67 | .cols = @intCast(opts.cols), | ||
| 68 | .rows = @intCast(opts.rows), | ||
| 69 | .max_scrollback = @intCast(opts.max_scrollback), | ||
| 70 | }); | ||
| 71 | errdefer inner.deinit(alloc); | ||
| 72 | |||
| 73 | // TerminalStream.init takes a Handler value. Handler contains | ||
| 74 | // a pointer to the terminal, so we store inner first then set | ||
| 75 | // the pointer during return below — but we need a stable address. | ||
| 76 | // We initialise with a placeholder and fix up the pointer after | ||
| 77 | // the struct is placed in its final location by the caller. | ||
| 78 | const stream: ghostty_vt.TerminalStream = .init(.{ | ||
| 79 | .terminal = &inner, | ||
| 80 | }); | ||
| 81 | |||
| 82 | const render_state: ghostty_vt.RenderState = .empty; | ||
| 83 | |||
| 84 | return .{ | ||
| 85 | .alloc = alloc, | ||
| 86 | .inner = inner, | ||
| 87 | .stream = stream, | ||
| 88 | .render_state = render_state, | ||
| 89 | }; | ||
| 90 | } | ||
| 91 | |||
| 92 | /// Fix up the internal stream pointer after the Terminal has been moved | ||
| 93 | /// to its final memory location. Must be called once before write(). | ||
| 94 | fn fixupStreamPointer(self: *Terminal) void { | ||
| 95 | self.stream.handler.terminal = &self.inner; | ||
| 96 | } | ||
| 97 | |||
| 98 | pub fn deinit(self: *Terminal) void { | ||
| 99 | self.render_state.deinit(self.alloc); | ||
| 100 | self.inner.deinit(self.alloc); | ||
| 101 | } | ||
| 102 | |||
| 103 | pub fn write(self: *Terminal, bytes: []const u8) void { | ||
| 104 | self.stream.handler.terminal = &self.inner; | ||
| 105 | self.stream.nextSlice(bytes); | ||
| 106 | } | ||
| 107 | |||
| 108 | pub fn resize(self: *Terminal, new_cols: u16, new_rows: u16) !void { | ||
| 109 | try self.inner.resize(self.alloc, @intCast(new_cols), @intCast(new_rows)); | ||
| 110 | } | ||
| 111 | |||
| 112 | pub fn snapshot(self: *Terminal) !void { | ||
| 113 | try self.render_state.update(self.alloc, &self.inner); | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Number of columns in the terminal grid. | ||
| 117 | pub fn cols(self: *const Terminal) u16 { | ||
| 118 | return @intCast(self.render_state.cols); | ||
| 119 | } | ||
| 120 | |||
| 121 | /// Number of rows in the terminal grid. | ||
| 122 | pub fn rows(self: *const Terminal) u16 { | ||
| 123 | return @intCast(self.render_state.rows); | ||
| 124 | } | ||
| 125 | }; | ||
| 126 | |||
| 127 | test "Terminal init/deinit" { | ||
| 128 | var term = try Terminal.init(std.testing.allocator, .{ | ||
| 129 | .cols = 80, | ||
| 130 | .rows = 24, | ||
| 131 | }); | ||
| 132 | defer term.deinit(); | ||
| 133 | } | ||
| 134 | |||
| 135 | test "Terminal.write feeds plain text" { | ||
| 136 | var term = try Terminal.init(std.testing.allocator, .{ | ||
| 137 | .cols = 80, | ||
| 138 | .rows = 24, | ||
| 139 | }); | ||
| 140 | defer term.deinit(); | ||
| 141 | |||
| 142 | term.write("Hello"); | ||
| 143 | try term.snapshot(); | ||
| 144 | |||
| 145 | // The test just verifies the call sequence works without crashing. | ||
| 146 | // We don't yet have a way to read back individual cells in this task. | ||
| 147 | } | ||
| 148 | |||
| 149 | test "Terminal.resize" { | ||
| 150 | var term = try Terminal.init(std.testing.allocator, .{ | ||
| 151 | .cols = 80, | ||
| 152 | .rows = 24, | ||
| 153 | }); | ||
| 154 | defer term.deinit(); | ||
| 155 | |||
| 156 | try term.resize(120, 40); | ||
| 157 | try term.snapshot(); | ||
| 158 | try std.testing.expectEqual(@as(u16, 120), term.cols()); | ||
| 159 | try std.testing.expectEqual(@as(u16, 40), term.rows()); | ||
| 56 | } | 160 | } |