vt: add format_alloc to C API formatter

Rename the existing format function to format_buf to clarify that it
writes into a caller-provided buffer. Add a new format_alloc variant
that allocates the output buffer internally using the provided
allocator (or the default if NULL). The caller receives the allocated
pointer and length and is responsible for freeing it.

This is useful for consumers that do not know the required buffer size
ahead of time and want to avoid the two-pass query-then-format pattern
needed with format_buf.
This commit is contained in:
Mitchell Hashimoto
2026-03-14 14:49:31 -07:00
parent 7c12d6e35d
commit 3c8feda118
4 changed files with 62 additions and 16 deletions

View File

@@ -144,7 +144,8 @@ comptime {
@export(&c.sgr_attribute_tag, .{ .name = "ghostty_sgr_attribute_tag" });
@export(&c.sgr_attribute_value, .{ .name = "ghostty_sgr_attribute_value" });
@export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" });
@export(&c.formatter_format, .{ .name = "ghostty_formatter_format" });
@export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" });
@export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" });
@export(&c.formatter_free, .{ .name = "ghostty_formatter_free" });
@export(&c.terminal_new, .{ .name = "ghostty_terminal_new" });
@export(&c.terminal_free, .{ .name = "ghostty_terminal_free" });

View File

@@ -146,7 +146,7 @@ fn terminal_new_(
return ptr;
}
pub fn format(
pub fn format_buf(
formatter_: Formatter,
out_: ?[*]u8,
out_len: usize,
@@ -176,6 +176,28 @@ pub fn format(
return .success;
}
pub fn format_alloc(
formatter_: Formatter,
alloc_: ?*const CAllocator,
out_ptr: *?[*]u8,
out_len: *usize,
) callconv(.c) Result {
const wrapper = formatter_ orelse return .invalid_value;
const alloc = lib_alloc.default(alloc_);
var aw: std.Io.Writer.Allocating = .init(alloc);
defer aw.deinit();
switch (wrapper.kind) {
.terminal => |*t| t.format(&aw.writer) catch return .out_of_memory,
}
const buf = aw.toOwnedSlice() catch return .out_of_memory;
out_ptr.* = buf.ptr;
out_len.* = buf.len;
return .success;
}
pub fn free(formatter_: Formatter) callconv(.c) void {
const wrapper = formatter_ orelse return;
const alloc = wrapper.alloc;
@@ -239,7 +261,7 @@ test "format plain" {
var buf: [1024]u8 = undefined;
var written: usize = 0;
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
try testing.expectEqualStrings("Hello", buf[0..written]);
}
@@ -265,13 +287,13 @@ test "format reflects terminal changes" {
var buf: [1024]u8 = undefined;
var written: usize = 0;
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
try testing.expectEqualStrings("Hello", buf[0..written]);
// Write more data and re-format
terminal_c.vt_write(t, "\r\nWorld", 7);
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
try testing.expectEqualStrings("Hello\nWorld", buf[0..written]);
}
@@ -297,13 +319,13 @@ test "format null returns required size" {
// Pass null buffer to query required size
var required: usize = 0;
try testing.expectEqual(Result.out_of_space, format(f, null, 0, &required));
try testing.expectEqual(Result.out_of_space, format_buf(f, null, 0, &required));
try testing.expect(required > 0);
// Now allocate and format
var buf: [1024]u8 = undefined;
var written: usize = 0;
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
try testing.expectEqual(required, written);
}
@@ -330,14 +352,14 @@ test "format buffer too small" {
// Buffer too small
var buf: [2]u8 = undefined;
var written: usize = 0;
try testing.expectEqual(Result.out_of_space, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.out_of_space, format_buf(f, &buf, buf.len, &written));
// written contains the required size
try testing.expectEqual(@as(usize, 5), written);
}
test "format null formatter" {
var written: usize = 0;
try testing.expectEqual(Result.invalid_value, format(null, null, 0, &written));
try testing.expectEqual(Result.invalid_value, format_buf(null, null, 0, &written));
}
test "format vt" {
@@ -362,7 +384,7 @@ test "format vt" {
var buf: [65536]u8 = undefined;
var written: usize = 0;
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
try testing.expect(written > 0);
try testing.expect(std.mem.indexOf(u8, buf[0..written], "Test") != null);
}
@@ -389,7 +411,7 @@ test "format html" {
var buf: [65536]u8 = undefined;
var written: usize = 0;
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
try testing.expect(written > 0);
try testing.expect(std.mem.indexOf(u8, buf[0..written], "Html") != null);
}

View File

@@ -19,7 +19,8 @@ pub const osc_command_data = osc.commandData;
pub const color_rgb_get = color.rgb_get;
pub const formatter_terminal_new = formatter.terminal_new;
pub const formatter_format = formatter.format;
pub const formatter_format_buf = formatter.format_buf;
pub const formatter_format_alloc = formatter.format_alloc;
pub const formatter_free = formatter.free;
pub const sgr_new = sgr.new;