a73x

161124e4

Heap-allocate vt.Terminal

a73x   2026-04-08 13:21

Commit message
Heap-allocate vt.Terminal

src/main.zig
Old New
@@ -209,7 +209,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
209 } 209 }
210 if (ev.utf8_len > 0) { 210 if (ev.utf8_len > 0) {
211 _ = try p.write(ev.utf8[0..ev.utf8_len]); 211 _ = try p.write(ev.utf8[0..ev.utf8_len]);
212 } else if (try encodeKeyboardEvent(&term, ev, &key_buf)) |encoded| { 212 } else if (try encodeKeyboardEvent(term, ev, &key_buf)) |encoded| {
213 _ = try p.write(encoded); 213 _ = try p.write(encoded);
214 } 214 }
215 } 215 }
@@ -525,7 +525,7 @@ test "encodeKeyboardEvent encodes left arrow" {
525 defer term.deinit(); 525 defer term.deinit();
526 526
527 var buf: [32]u8 = undefined; 527 var buf: [32]u8 = undefined;
528 const encoded = (try encodeKeyboardEvent(&term, .{ 528 const encoded = (try encodeKeyboardEvent(term, .{
529 .keysym = c.XKB_KEY_Left, 529 .keysym = c.XKB_KEY_Left,
530 .modifiers = .{}, 530 .modifiers = .{},
531 .action = .press, 531 .action = .press,
src/vt.zig
Old New
@@ -3,8 +3,8 @@
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 //!
5 //! ### Terminal lifecycle 5 //! ### Terminal lifecycle
6 //! var t: Terminal = try .init(alloc, .{ .cols = N, .rows = N }); 6 //! const t = try Terminal.init(alloc, .{ .cols = N, .rows = N });
7 //! defer t.deinit(alloc); 7 //! defer t.deinit();
8 //! try t.printString("raw text"); // writes plain text 8 //! try t.printString("raw text"); // writes plain text
9 //! try t.resize(alloc, new_cols, new_rows); 9 //! try t.resize(alloc, new_cols, new_rows);
10 //! const s = try t.plainString(alloc); // heap copy of visible text 10 //! const s = try t.plainString(alloc); // heap copy of visible text
@@ -89,53 +89,43 @@ pub const Terminal = struct {
89 max_scrollback: u32 = 1000, 89 max_scrollback: u32 = 1000,
90 }; 90 };
91 91
92 pub fn init(alloc: std.mem.Allocator, opts: InitOptions) !Terminal { 92 pub fn init(alloc: std.mem.Allocator, opts: InitOptions) !*Terminal {
93 var inner = try ghostty_vt.Terminal.init(alloc, .{ 93 const self = try alloc.create(Terminal);
94 errdefer alloc.destroy(self);
95
96 const inner = try ghostty_vt.Terminal.init(alloc, .{
94 .cols = @intCast(opts.cols), 97 .cols = @intCast(opts.cols),
95 .rows = @intCast(opts.rows), 98 .rows = @intCast(opts.rows),
96 .max_scrollback = @intCast(opts.max_scrollback), 99 .max_scrollback = @intCast(opts.max_scrollback),
97 }); 100 });
98 errdefer inner.deinit(alloc); 101 errdefer inner.deinit(alloc);
99 102
100 // TerminalStream.init takes a Handler value. Handler contains 103 self.* = .{
101 // a pointer to the terminal, so we store inner first then set
102 // the pointer during return below — but we need a stable address.
103 // We initialise with a placeholder and fix up the pointer after
104 // the struct is placed in its final location by the caller.
105 const stream: ghostty_vt.TerminalStream = .init(.{
106 .terminal = &inner,
107 });
108
109 const render_state: ghostty_vt.RenderState = .empty;
110
111 return .{
112 .alloc = alloc, 104 .alloc = alloc,
113 .inner = inner, 105 .inner = inner,
114 .stream = stream, 106 .stream = .init(.{
115 .render_state = render_state, 107 .terminal = &self.inner,
108 }),
109 .render_state = .empty,
116 .hooks = .{}, 110 .hooks = .{},
117 }; 111 };
118 }
119
120 /// Fix up the internal stream pointer after the Terminal has been moved
121 /// to its final memory location. Must be called once before write().
122 fn fixupStreamPointer(self: *Terminal) void {
123 self.stream.handler.terminal = &self.inner;
124 self.stream.handler.effects.write_pty = &streamWritePty; 112 self.stream.handler.effects.write_pty = &streamWritePty;
125 self.stream.handler.effects.title_changed = &streamTitleChanged; 113 self.stream.handler.effects.title_changed = &streamTitleChanged;
126 self.stream.handler.effects.size = &streamSize; 114 self.stream.handler.effects.size = &streamSize;
127 self.stream.handler.effects.device_attributes = &streamDeviceAttributes; 115 self.stream.handler.effects.device_attributes = &streamDeviceAttributes;
128 self.stream.handler.effects.xtversion = &streamXtversion; 116 self.stream.handler.effects.xtversion = &streamXtversion;
129 self.stream.handler.effects.color_scheme = &streamColorScheme; 117 self.stream.handler.effects.color_scheme = &streamColorScheme;
118
119 return self;
130 } 120 }
131 121
132 pub fn deinit(self: *Terminal) void { 122 pub fn deinit(self: *Terminal) void {
133 self.render_state.deinit(self.alloc); 123 self.render_state.deinit(self.alloc);
134 self.inner.deinit(self.alloc); 124 self.inner.deinit(self.alloc);
125 self.alloc.destroy(self);
135 } 126 }
136 127
137 pub fn write(self: *Terminal, bytes: []const u8) void { 128 pub fn write(self: *Terminal, bytes: []const u8) void {
138 self.fixupStreamPointer();
139 self.stream.nextSlice(bytes); 129 self.stream.nextSlice(bytes);
140 } 130 }
141 131
@@ -146,7 +136,6 @@ pub const Terminal = struct {
146 ) void { 136 ) void {
147 self.hooks.write_pty_ctx = ctx; 137 self.hooks.write_pty_ctx = ctx;
148 self.hooks.write_pty = callback; 138 self.hooks.write_pty = callback;
149 self.fixupStreamPointer();
150 } 139 }
151 140
152 pub fn setTitleChangedCallback( 141 pub fn setTitleChangedCallback(
@@ -156,12 +145,10 @@ pub const Terminal = struct {
156 ) void { 145 ) void {
157 self.hooks.title_changed_ctx = ctx; 146 self.hooks.title_changed_ctx = ctx;
158 self.hooks.title_changed = callback; 147 self.hooks.title_changed = callback;
159 self.fixupStreamPointer();
160 } 148 }
161 149
162 pub fn setReportedSize(self: *Terminal, size: Size) void { 150 pub fn setReportedSize(self: *Terminal, size: Size) void {
163 self.hooks.reported_size = size; 151 self.hooks.reported_size = size;
164 self.fixupStreamPointer();
165 } 152 }
166 153
167 pub fn encodeKey( 154 pub fn encodeKey(
@@ -280,6 +267,8 @@ test "Terminal init/deinit" {
280 .rows = 24, 267 .rows = 24,
281 }); 268 });
282 defer term.deinit(); 269 defer term.deinit();
270
271 try std.testing.expectEqual(&term.inner, term.stream.handler.terminal);
283 } 272 }
284 273
285 test "Terminal.write feeds plain text" { 274 test "Terminal.write feeds plain text" {