161124e4
Heap-allocate vt.Terminal
a73x 2026-04-08 13:21
Commit message
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" { |