a73x

161124e4

Heap-allocate vt.Terminal

a73x   2026-04-08 13:21


diff --git a/src/main.zig b/src/main.zig
index ede23cc..6082879 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -209,7 +209,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
            }
            if (ev.utf8_len > 0) {
                _ = try p.write(ev.utf8[0..ev.utf8_len]);
            } else if (try encodeKeyboardEvent(&term, ev, &key_buf)) |encoded| {
            } else if (try encodeKeyboardEvent(term, ev, &key_buf)) |encoded| {
                _ = try p.write(encoded);
            }
        }
@@ -525,7 +525,7 @@ test "encodeKeyboardEvent encodes left arrow" {
    defer term.deinit();

    var buf: [32]u8 = undefined;
    const encoded = (try encodeKeyboardEvent(&term, .{
    const encoded = (try encodeKeyboardEvent(term, .{
        .keysym = c.XKB_KEY_Left,
        .modifiers = .{},
        .action = .press,
diff --git a/src/vt.zig b/src/vt.zig
index f50bb2e..c216316 100644
--- a/src/vt.zig
+++ b/src/vt.zig
@@ -3,8 +3,8 @@
//! ## ghostty-vt API notes (as of ghostty-1.3.2-dev)
//!
//! ### Terminal lifecycle
//!   var t: Terminal = try .init(alloc, .{ .cols = N, .rows = N });
//!   defer t.deinit(alloc);
//!   const t = try Terminal.init(alloc, .{ .cols = N, .rows = N });
//!   defer t.deinit();
//!   try t.printString("raw text");          // writes plain text
//!   try t.resize(alloc, new_cols, new_rows);
//!   const s = try t.plainString(alloc);     // heap copy of visible text
@@ -89,53 +89,43 @@ pub const Terminal = struct {
        max_scrollback: u32 = 1000,
    };

    pub fn init(alloc: std.mem.Allocator, opts: InitOptions) !Terminal {
        var inner = try ghostty_vt.Terminal.init(alloc, .{
    pub fn init(alloc: std.mem.Allocator, opts: InitOptions) !*Terminal {
        const self = try alloc.create(Terminal);
        errdefer alloc.destroy(self);

        const inner = try ghostty_vt.Terminal.init(alloc, .{
            .cols = @intCast(opts.cols),
            .rows = @intCast(opts.rows),
            .max_scrollback = @intCast(opts.max_scrollback),
        });
        errdefer inner.deinit(alloc);

        // TerminalStream.init takes a Handler value. Handler contains
        // a pointer to the terminal, so we store inner first then set
        // the pointer during return below — but we need a stable address.
        // We initialise with a placeholder and fix up the pointer after
        // the struct is placed in its final location by the caller.
        const stream: ghostty_vt.TerminalStream = .init(.{
            .terminal = &inner,
        });

        const render_state: ghostty_vt.RenderState = .empty;

        return .{
        self.* = .{
            .alloc = alloc,
            .inner = inner,
            .stream = stream,
            .render_state = render_state,
            .stream = .init(.{
                .terminal = &self.inner,
            }),
            .render_state = .empty,
            .hooks = .{},
        };
    }

    /// Fix up the internal stream pointer after the Terminal has been moved
    /// to its final memory location. Must be called once before write().
    fn fixupStreamPointer(self: *Terminal) void {
        self.stream.handler.terminal = &self.inner;
        self.stream.handler.effects.write_pty = &streamWritePty;
        self.stream.handler.effects.title_changed = &streamTitleChanged;
        self.stream.handler.effects.size = &streamSize;
        self.stream.handler.effects.device_attributes = &streamDeviceAttributes;
        self.stream.handler.effects.xtversion = &streamXtversion;
        self.stream.handler.effects.color_scheme = &streamColorScheme;

        return self;
    }

    pub fn deinit(self: *Terminal) void {
        self.render_state.deinit(self.alloc);
        self.inner.deinit(self.alloc);
        self.alloc.destroy(self);
    }

    pub fn write(self: *Terminal, bytes: []const u8) void {
        self.fixupStreamPointer();
        self.stream.nextSlice(bytes);
    }

@@ -146,7 +136,6 @@ pub const Terminal = struct {
    ) void {
        self.hooks.write_pty_ctx = ctx;
        self.hooks.write_pty = callback;
        self.fixupStreamPointer();
    }

    pub fn setTitleChangedCallback(
@@ -156,12 +145,10 @@ pub const Terminal = struct {
    ) void {
        self.hooks.title_changed_ctx = ctx;
        self.hooks.title_changed = callback;
        self.fixupStreamPointer();
    }

    pub fn setReportedSize(self: *Terminal, size: Size) void {
        self.hooks.reported_size = size;
        self.fixupStreamPointer();
    }

    pub fn encodeKey(
@@ -280,6 +267,8 @@ test "Terminal init/deinit" {
        .rows = 24,
    });
    defer term.deinit();

    try std.testing.expectEqual(&term.inner, term.stream.handler.terminal);
}

test "Terminal.write feeds plain text" {