aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/lib
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/lib
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/lib')
-rw-r--r--vendor/libghostty-vt/src/lib/allocator.zig264
-rw-r--r--vendor/libghostty-vt/src/lib/allocator/convenience.zig59
-rw-r--r--vendor/libghostty-vt/src/lib/enum.zig145
-rw-r--r--vendor/libghostty-vt/src/lib/main.zig19
-rw-r--r--vendor/libghostty-vt/src/lib/string.zig15
-rw-r--r--vendor/libghostty-vt/src/lib/struct.zig105
-rw-r--r--vendor/libghostty-vt/src/lib/target.zig6
-rw-r--r--vendor/libghostty-vt/src/lib/types.zig13
-rw-r--r--vendor/libghostty-vt/src/lib/union.zig171
9 files changed, 797 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/lib/allocator.zig b/vendor/libghostty-vt/src/lib/allocator.zig
new file mode 100644
index 00000000..fc56033a
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/allocator.zig
@@ -0,0 +1,264 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const testing = std.testing;
+
+/// Convenience functions
+pub const convenience = @import("allocator/convenience.zig");
+
+/// Useful alias since they're required to create Zig allocators
+pub const ZigVTable = std.mem.Allocator.VTable;
+
+/// The VTable required by the C interface.
+/// C: GhosttyAllocatorVtable
+pub const VTable = extern struct {
+ alloc: *const fn (*anyopaque, len: usize, alignment: u8, ret_addr: usize) callconv(.c) ?[*]u8,
+ resize: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, new_len: usize, ret_addr: usize) callconv(.c) bool,
+ remap: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, new_len: usize, ret_addr: usize) callconv(.c) ?[*]u8,
+ free: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, ret_addr: usize) callconv(.c) void,
+};
+
+/// Returns an allocator to use for the given possibly-null C allocator,
+/// ensuring some allocator is always returned.
+pub fn default(c_alloc_: ?*const Allocator) std.mem.Allocator {
+ // If we're given an allocator, use it.
+ if (c_alloc_) |c_alloc| return c_alloc.zig();
+
+ // Tests always use the test allocator so we can detect leaks.
+ if (comptime builtin.is_test) return testing.allocator;
+
+ // If we have libc, use that. We prefer libc if we have it because
+ // its generally fast but also lets the embedder easily override
+ // malloc/free with custom allocators like mimalloc or something.
+ if (comptime builtin.link_libc) return std.heap.c_allocator;
+
+ // Wasm
+ if (comptime builtin.target.cpu.arch.isWasm()) return std.heap.wasm_allocator;
+
+ // No libc, use the preferred allocator for releases which is the
+ // Zig SMP allocator.
+ return std.heap.smp_allocator;
+}
+
+/// The Allocator interface for custom memory allocation strategies
+/// within C libghostty APIs.
+///
+/// This -- purposely -- matches the Zig allocator interface. We do this
+/// for two reasons: (1) Zig's allocator interface is well proven in
+/// the real world to be flexible and useful, and (2) it allows us to
+/// easily convert C allocators to Zig allocators and vice versa, since
+/// we're written in Zig.
+///
+/// C: GhosttyAllocator
+pub const Allocator = extern struct {
+ ctx: *anyopaque,
+ vtable: *const VTable,
+
+ /// vtable for the Zig allocator interface to map our extern
+ /// allocator to Zig's allocator interface.
+ pub const zig_vtable: ZigVTable = .{
+ .alloc = alloc,
+ .resize = resize,
+ .remap = remap,
+ .free = free,
+ };
+
+ /// Create a C allocator from a Zig allocator. This requires that
+ /// the Zig allocator be pointer-stable for the lifetime of the
+ /// C allocator.
+ pub fn fromZig(zig_alloc: *const std.mem.Allocator) Allocator {
+ return .{
+ .ctx = @ptrCast(@constCast(zig_alloc)),
+ .vtable = &ZigAllocator.vtable,
+ };
+ }
+
+ /// Create a Zig allocator from this C allocator. This requires
+ /// a pointer to a Zig allocator vtable that we can populate with
+ /// our callbacks.
+ pub fn zig(self: *const Allocator) std.mem.Allocator {
+ return .{
+ .ptr = @ptrCast(@constCast(self)),
+ .vtable = &zig_vtable,
+ };
+ }
+
+ fn alloc(
+ ctx: *anyopaque,
+ len: usize,
+ alignment: std.mem.Alignment,
+ ra: usize,
+ ) ?[*]u8 {
+ const self: *Allocator = @ptrCast(@alignCast(ctx));
+ return self.vtable.alloc(
+ self.ctx,
+ len,
+ @intFromEnum(alignment),
+ ra,
+ );
+ }
+
+ fn resize(
+ ctx: *anyopaque,
+ old_mem: []u8,
+ alignment: std.mem.Alignment,
+ new_len: usize,
+ ra: usize,
+ ) bool {
+ const self: *Allocator = @ptrCast(@alignCast(ctx));
+ return self.vtable.resize(
+ self.ctx,
+ old_mem.ptr,
+ old_mem.len,
+ @intFromEnum(alignment),
+ new_len,
+ ra,
+ );
+ }
+
+ fn remap(
+ ctx: *anyopaque,
+ old_mem: []u8,
+ alignment: std.mem.Alignment,
+ new_len: usize,
+ ra: usize,
+ ) ?[*]u8 {
+ const self: *Allocator = @ptrCast(@alignCast(ctx));
+ return self.vtable.remap(
+ self.ctx,
+ old_mem.ptr,
+ old_mem.len,
+ @intFromEnum(alignment),
+ new_len,
+ ra,
+ );
+ }
+
+ fn free(
+ ctx: *anyopaque,
+ old_mem: []u8,
+ alignment: std.mem.Alignment,
+ ra: usize,
+ ) void {
+ const self: *Allocator = @ptrCast(@alignCast(ctx));
+ self.vtable.free(
+ self.ctx,
+ old_mem.ptr,
+ old_mem.len,
+ @intFromEnum(alignment),
+ ra,
+ );
+ }
+};
+
+/// An allocator implementation that wraps a Zig allocator so that
+/// it can be exposed to C.
+const ZigAllocator = struct {
+ const vtable: VTable = .{
+ .alloc = alloc,
+ .resize = resize,
+ .remap = remap,
+ .free = free,
+ };
+
+ fn alloc(
+ ctx: *anyopaque,
+ len: usize,
+ alignment: u8,
+ ra: usize,
+ ) callconv(.c) ?[*]u8 {
+ const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
+ return zig_alloc.vtable.alloc(
+ zig_alloc.ptr,
+ len,
+ @enumFromInt(alignment),
+ ra,
+ );
+ }
+
+ fn resize(
+ ctx: *anyopaque,
+ memory: [*]u8,
+ memory_len: usize,
+ alignment: u8,
+ new_len: usize,
+ ra: usize,
+ ) callconv(.c) bool {
+ const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
+ return zig_alloc.vtable.resize(
+ zig_alloc.ptr,
+ memory[0..memory_len],
+ @enumFromInt(alignment),
+ new_len,
+ ra,
+ );
+ }
+
+ fn remap(
+ ctx: *anyopaque,
+ memory: [*]u8,
+ memory_len: usize,
+ alignment: u8,
+ new_len: usize,
+ ra: usize,
+ ) callconv(.c) ?[*]u8 {
+ const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
+ return zig_alloc.vtable.remap(
+ zig_alloc.ptr,
+ memory[0..memory_len],
+ @enumFromInt(alignment),
+ new_len,
+ ra,
+ );
+ }
+
+ fn free(
+ ctx: *anyopaque,
+ memory: [*]u8,
+ memory_len: usize,
+ alignment: u8,
+ ra: usize,
+ ) callconv(.c) void {
+ const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
+ return zig_alloc.vtable.free(
+ zig_alloc.ptr,
+ memory[0..memory_len],
+ @enumFromInt(alignment),
+ ra,
+ );
+ }
+};
+
+/// libc Allocator, requires linking libc
+pub const c_allocator: Allocator = .fromZig(&std.heap.c_allocator);
+
+/// Allocator that can be sent to the C API that does full
+/// leak checking within Zig tests. This should only be used from
+/// Zig tests.
+pub const test_allocator: Allocator = b: {
+ if (!builtin.is_test) @compileError("test_allocator can only be used in tests");
+ break :b .fromZig(&testing.allocator);
+};
+
+test "c allocator" {
+ if (!comptime builtin.link_libc) return error.SkipZigTest;
+
+ const alloc = c_allocator.zig();
+ const str = try alloc.alloc(u8, 10);
+ defer alloc.free(str);
+ try testing.expectEqual(10, str.len);
+}
+
+test "fba allocator" {
+ var buf: [1024]u8 = undefined;
+ var fba: std.heap.FixedBufferAllocator = .init(&buf);
+ const zig_alloc = fba.allocator();
+
+ // Convert the Zig allocator to a C interface
+ const c_alloc: Allocator = .fromZig(&zig_alloc);
+
+ // Convert back to Zig so we can test it.
+ const alloc = c_alloc.zig();
+ const str = try alloc.alloc(u8, 10);
+ defer alloc.free(str);
+ try testing.expectEqual(10, str.len);
+}
diff --git a/vendor/libghostty-vt/src/lib/allocator/convenience.zig b/vendor/libghostty-vt/src/lib/allocator/convenience.zig
new file mode 100644
index 00000000..0f5f88d2
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/allocator/convenience.zig
@@ -0,0 +1,59 @@
+//! This contains convenience functions for allocating various types.
+//!
+//! The primary use case for this is Wasm builds. Ghostty relies a lot on
+//! pointers to various types for ABI compatibility and creating those pointers
+//! in Wasm is tedious. This file contains a purely additive set of functions
+//! that can be exposed to the Wasm module without changing the API from the
+//! C library.
+//!
+//! Given these are convenience methods, they always use the default allocator.
+//! If a caller is using a custom allocator, they have the expertise to
+//! allocate these types manually using their custom allocator.
+
+// Get our default allocator at comptime since it is known.
+const default = @import("../allocator.zig").default;
+const alloc = default(null);
+
+pub const Opaque = *anyopaque;
+
+pub fn allocOpaque() callconv(.c) ?*Opaque {
+ return alloc.create(*anyopaque) catch return null;
+}
+
+pub fn freeOpaque(ptr: ?*Opaque) callconv(.c) void {
+ if (ptr) |p| alloc.destroy(p);
+}
+
+pub fn allocU8Array(len: usize) callconv(.c) ?[*]u8 {
+ const slice = alloc.alloc(u8, len) catch return null;
+ return slice.ptr;
+}
+
+pub fn freeU8Array(ptr: ?[*]u8, len: usize) callconv(.c) void {
+ if (ptr) |p| alloc.free(p[0..len]);
+}
+
+pub fn allocU16Array(len: usize) callconv(.c) ?[*]u16 {
+ const slice = alloc.alloc(u16, len) catch return null;
+ return slice.ptr;
+}
+
+pub fn freeU16Array(ptr: ?[*]u16, len: usize) callconv(.c) void {
+ if (ptr) |p| alloc.free(p[0..len]);
+}
+
+pub fn allocU8() callconv(.c) ?*u8 {
+ return alloc.create(u8) catch return null;
+}
+
+pub fn freeU8(ptr: ?*u8) callconv(.c) void {
+ if (ptr) |p| alloc.destroy(p);
+}
+
+pub fn allocUsize() callconv(.c) ?*usize {
+ return alloc.create(usize) catch return null;
+}
+
+pub fn freeUsize(ptr: ?*usize) callconv(.c) void {
+ if (ptr) |p| alloc.destroy(p);
+}
diff --git a/vendor/libghostty-vt/src/lib/enum.zig b/vendor/libghostty-vt/src/lib/enum.zig
new file mode 100644
index 00000000..76e05fa9
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/enum.zig
@@ -0,0 +1,145 @@
+const std = @import("std");
+const Target = @import("target.zig").Target;
+
+/// Create an enum type with the given keys that is C ABI compatible
+/// if we're targeting C, otherwise a Zig enum with smallest possible
+/// backing type.
+///
+/// In all cases, the enum keys will be created in the order given.
+/// For C ABI, this means that the order MUST NOT be changed in order
+/// to preserve ABI compatibility. You can set a key to null to
+/// remove it from the Zig enum while keeping the "hole" in the C enum
+/// to preserve ABI compatibility.
+///
+/// C detection is up to the caller, since there are multiple ways
+/// to do that. We rely on the `target` parameter to determine whether we
+/// should create a C compatible enum or a Zig enum.
+///
+/// For the Zig enum, the enum value is not guaranteed to be stable, so
+/// it shouldn't be relied for things like serialization.
+pub fn Enum(
+ target: Target,
+ keys: []const ?[:0]const u8,
+) type {
+ var fields: [keys.len]std.builtin.Type.EnumField = undefined;
+ var fields_i: usize = 0;
+ var holes: usize = 0;
+ for (keys) |key_| {
+ const key: [:0]const u8 = key_ orelse {
+ switch (target) {
+ // For Zig we don't track holes because the enum value
+ // isn't guaranteed to be stable and we want to use the
+ // smallest possible backing type.
+ .zig => {},
+
+ // For C we must track holes to preserve ABI compatibility
+ // with subsequent values.
+ .c => holes += 1,
+ }
+ continue;
+ };
+
+ fields[fields_i] = .{
+ .name = key,
+ .value = fields_i + holes,
+ };
+ fields_i += 1;
+ }
+
+ // Assigned to var so that the type name is nicer in stack traces.
+ const Result = @Type(.{ .@"enum" = .{
+ .tag_type = switch (target) {
+ .c => c_int,
+ .zig => std.math.IntFittingRange(0, fields_i - 1),
+ },
+ .fields = fields[0..fields_i],
+ .decls = &.{},
+ .is_exhaustive = true,
+ } });
+ return Result;
+}
+
+test "zig" {
+ const testing = std.testing;
+ const T = Enum(.zig, &.{ "a", "b", "c", "d" });
+ const info = @typeInfo(T).@"enum";
+ try testing.expectEqual(u2, info.tag_type);
+}
+
+test "c" {
+ const testing = std.testing;
+ const T = Enum(.c, &.{ "a", "b", "c", "d" });
+ const info = @typeInfo(T).@"enum";
+ try testing.expectEqual(c_int, info.tag_type);
+}
+
+test "abi by removing a key" {
+ const testing = std.testing;
+ // C
+ {
+ const T = Enum(.c, &.{ "a", "b", null, "d" });
+ const info = @typeInfo(T).@"enum";
+ try testing.expectEqual(c_int, info.tag_type);
+ try testing.expectEqual(3, @intFromEnum(T.d));
+ }
+
+ // Zig
+ {
+ const T = Enum(.zig, &.{ "a", "b", null, "d" });
+ const info = @typeInfo(T).@"enum";
+ try testing.expectEqual(u2, info.tag_type);
+ try testing.expectEqual(2, @intFromEnum(T.d));
+ }
+}
+
+/// Verify that for every key in enum T, there is a matching declaration in
+/// `ghostty.h` with the correct value. This should only ever be called inside a `test`
+/// because the `ghostty.h` module is only available then.
+pub fn checkGhosttyHEnum(
+ comptime T: type,
+ comptime prefix: []const u8,
+) !void {
+ const info = @typeInfo(T);
+
+ try std.testing.expect(info == .@"enum");
+ try std.testing.expect(info.@"enum".tag_type == c_int);
+ try std.testing.expect(info.@"enum".is_exhaustive == true);
+
+ @setEvalBranchQuota(100_000);
+
+ const c = @import("ghostty.h");
+
+ var set: std.EnumSet(T) = .initFull();
+
+ const enum_fields = info.@"enum".fields;
+
+ inline for (enum_fields) |field| {
+ const expected_name: *const [prefix.len + field.name.len]u8 = comptime e: {
+ var buf: [prefix.len + field.name.len]u8 = undefined;
+ @memcpy(buf[0..prefix.len], prefix);
+ for (buf[prefix.len..], field.name) |*d, s| {
+ d.* = std.ascii.toUpper(s);
+ }
+ break :e &buf;
+ };
+
+ if (@hasDecl(c, expected_name)) {
+ std.testing.expectEqual(field.value, @field(c, expected_name)) catch |e| {
+ std.log.err(@typeName(T) ++ " key " ++ field.name ++ " does not have the same backing int as " ++ expected_name, .{});
+ return e;
+ };
+
+ set.remove(@enumFromInt(field.value));
+ }
+ }
+
+ std.testing.expect(set.count() == 0) catch |e| {
+ var it = set.iterator();
+ while (it.next()) |v| {
+ var buf: [128]u8 = undefined;
+ const upper_string = std.ascii.upperString(&buf, @tagName(v));
+ std.log.err("ghostty.h is missing value for {s}{s}", .{ prefix, upper_string });
+ }
+ return e;
+ };
+}
diff --git a/vendor/libghostty-vt/src/lib/main.zig b/vendor/libghostty-vt/src/lib/main.zig
new file mode 100644
index 00000000..05ebe9bd
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/main.zig
@@ -0,0 +1,19 @@
+const std = @import("std");
+const enumpkg = @import("enum.zig");
+const structpkg = @import("struct.zig");
+const types = @import("types.zig");
+const unionpkg = @import("union.zig");
+
+pub const allocator = @import("allocator.zig");
+pub const Enum = enumpkg.Enum;
+pub const checkGhosttyHEnum = enumpkg.checkGhosttyHEnum;
+pub const String = types.String;
+pub const Struct = structpkg.Struct;
+pub const structSizedFieldFits = structpkg.sizedFieldFits;
+pub const Target = @import("target.zig").Target;
+pub const TaggedUnion = unionpkg.TaggedUnion;
+pub const cutPrefix = @import("string.zig").cutPrefix;
+
+test {
+ std.testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/src/lib/string.zig b/vendor/libghostty-vt/src/lib/string.zig
new file mode 100644
index 00000000..795823c2
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/string.zig
@@ -0,0 +1,15 @@
+const std = @import("std");
+
+// This is a copy of std.mem.cutPrefix from 0.16. Once Ghostty has been ported
+// to 0.16 this can be removed.
+
+/// If slice starts with prefix, returns the rest of slice starting at
+/// prefix.len.
+pub fn cutPrefix(comptime T: type, slice: []const T, prefix: []const T) ?[]const T {
+ return if (std.mem.startsWith(T, slice, prefix)) slice[prefix.len..] else null;
+}
+
+test cutPrefix {
+ try std.testing.expectEqualStrings("foo", cutPrefix(u8, "--example=foo", "--example=").?);
+ try std.testing.expectEqual(null, cutPrefix(u8, "--example=foo", "-example="));
+}
diff --git a/vendor/libghostty-vt/src/lib/struct.zig b/vendor/libghostty-vt/src/lib/struct.zig
new file mode 100644
index 00000000..bbf07ece
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/struct.zig
@@ -0,0 +1,105 @@
+const std = @import("std");
+const testing = std.testing;
+const Target = @import("target.zig").Target;
+
+/// Create a struct type that is C ABI compatible from a Zig struct type.
+///
+/// When the target is `.zig`, the original struct type is returned as-is.
+/// When the target is `.c`, the struct is recreated with an `extern` layout,
+/// ensuring a stable, C-compatible memory layout.
+///
+/// This handles packed structs by resolving zero alignments to the natural
+/// alignment of each field's type, since extern structs require explicit
+/// alignment. This means packed struct fields like `bool` will take up
+/// their full size (1 byte) rather than being bit-packed.
+pub fn Struct(
+ comptime target: Target,
+ comptime Zig: type,
+) type {
+ return switch (target) {
+ .zig => Zig,
+ .c => c: {
+ const info = @typeInfo(Zig).@"struct";
+ var fields: [info.fields.len]std.builtin.Type.StructField = undefined;
+ for (info.fields, 0..) |field, i| {
+ fields[i] = .{
+ .name = field.name,
+ .type = field.type,
+ .default_value_ptr = field.default_value_ptr,
+ .is_comptime = field.is_comptime,
+ .alignment = if (field.alignment > 0) field.alignment else @alignOf(field.type),
+ };
+ }
+
+ break :c @Type(.{ .@"struct" = .{
+ .layout = .@"extern",
+ .fields = &fields,
+ .decls = &.{},
+ .is_tuple = info.is_tuple,
+ } });
+ },
+ };
+}
+
+/// Returns true if a struct of type `T` with size `size` can set
+/// field `field` (if it fits within the size). This is used for ABI
+/// compatibility for structs that have an explicit size field.
+pub fn sizedFieldFits(
+ comptime T: type,
+ size: usize,
+ comptime field: []const u8,
+) bool {
+ const offset = @offsetOf(T, field);
+ const field_size = @sizeOf(@FieldType(T, field));
+ return size >= offset + field_size;
+}
+
+test "sizedFieldFits boundary checks" {
+ const Sized = extern struct {
+ size: usize,
+ a: u8,
+ b: u32,
+ };
+
+ const size_required = @offsetOf(Sized, "size") + @sizeOf(@FieldType(Sized, "size"));
+ const a_required = @offsetOf(Sized, "a") + @sizeOf(@FieldType(Sized, "a"));
+ const b_required = @offsetOf(Sized, "b") + @sizeOf(@FieldType(Sized, "b"));
+
+ try testing.expect(sizedFieldFits(Sized, size_required, "size"));
+ try testing.expect(!sizedFieldFits(Sized, size_required - 1, "size"));
+
+ try testing.expect(sizedFieldFits(Sized, a_required, "a"));
+ try testing.expect(!sizedFieldFits(Sized, a_required - 1, "a"));
+
+ try testing.expect(sizedFieldFits(Sized, b_required, "b"));
+ try testing.expect(!sizedFieldFits(Sized, b_required - 1, "b"));
+}
+
+test "sizedFieldFits respects alignment padding" {
+ const Sized = extern struct {
+ size: usize,
+ a: u8,
+ b: u32,
+ };
+
+ const up_to_padding = @offsetOf(Sized, "b");
+ try testing.expect(sizedFieldFits(Sized, up_to_padding, "a"));
+ try testing.expect(!sizedFieldFits(Sized, up_to_padding, "b"));
+}
+
+test "packed struct converts to extern with full-size bools" {
+ const Packed = packed struct {
+ flag1: bool,
+ flag2: bool,
+ value: u8,
+ };
+
+ const C = Struct(.c, Packed);
+ const info = @typeInfo(C).@"struct";
+
+ try testing.expectEqual(.@"extern", info.layout);
+ try testing.expectEqual(@as(usize, 1), @sizeOf(@FieldType(C, "flag1")));
+ try testing.expectEqual(@as(usize, 1), @sizeOf(@FieldType(C, "flag2")));
+ try testing.expectEqual(@as(usize, 1), @sizeOf(@FieldType(C, "value")));
+ try testing.expectEqual(@as(usize, 3), @sizeOf(C));
+}
diff --git a/vendor/libghostty-vt/src/lib/target.zig b/vendor/libghostty-vt/src/lib/target.zig
new file mode 100644
index 00000000..8d7a7fb8
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/target.zig
@@ -0,0 +1,6 @@
+/// The target for ABI generation. The detection of this is left to the
+/// caller since there are multiple ways to do that.
+pub const Target = union(enum) {
+ c,
+ zig,
+};
diff --git a/vendor/libghostty-vt/src/lib/types.zig b/vendor/libghostty-vt/src/lib/types.zig
new file mode 100644
index 00000000..758540d1
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/types.zig
@@ -0,0 +1,13 @@
+pub const String = extern struct {
+ ptr: [*]const u8,
+ len: usize,
+
+ pub fn init(zig: anytype) String {
+ return switch (@TypeOf(zig)) {
+ []u8, []const u8 => .{
+ .ptr = zig.ptr,
+ .len = zig.len,
+ },
+ };
+ }
+};
diff --git a/vendor/libghostty-vt/src/lib/union.zig b/vendor/libghostty-vt/src/lib/union.zig
new file mode 100644
index 00000000..924d0e86
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/union.zig
@@ -0,0 +1,171 @@
+const std = @import("std");
+const testing = std.testing;
+const Target = @import("target.zig").Target;
+
+/// Create a tagged union type that supports a C ABI and maintains
+/// C ABI compatibility when adding new tags. This returns a set of types
+/// and functions to augment the given Union type, not create a wholly new
+/// union type.
+///
+/// The C ABI compatible types and functions are only available when the
+/// target produces C values.
+///
+/// The `Union` type should be a standard Zig tagged union. The tag type
+/// should be explicit (i.e. not `union(enum)`) and the tag type should
+/// be an enum created with the `Enum` function in this library, so that
+/// automatic C ABI compatibility is ensured.
+///
+/// The `Padding` type is a type that is always added to the C union
+/// with the key `_padding`. This should be set to a type that has the size
+/// and alignment needed to pad the C union to the expected size. This
+/// should never change to ensure ABI compatibility.
+pub fn TaggedUnion(
+ comptime target: Target,
+ comptime Union: type,
+ comptime Padding: type,
+) type {
+ return struct {
+ comptime {
+ switch (target) {
+ .zig => {},
+
+ // For ABI compatibility, we expect that this is our union size.
+ .c => if (@sizeOf(CValue) != @sizeOf(Padding)) {
+ @compileLog(@sizeOf(CValue));
+ @compileError("TaggedUnion CValue size does not match expected fixed size");
+ },
+ }
+ }
+
+ /// The tag type.
+ pub const Tag = @typeInfo(Union).@"union".tag_type.?;
+
+ /// The Zig union.
+ pub const Zig = Union;
+
+ /// The C ABI compatible tagged union type.
+ pub const C = switch (target) {
+ .zig => struct {},
+ .c => extern struct {
+ tag: Tag,
+ value: CValue,
+ },
+ };
+
+ /// The C ABI compatible union value type.
+ pub const CValue = cvalue: {
+ switch (target) {
+ .zig => break :cvalue extern struct {},
+ .c => {},
+ }
+
+ const tag_fields = @typeInfo(Tag).@"enum".fields;
+ var union_fields: [tag_fields.len + 1]std.builtin.Type.UnionField = undefined;
+ for (tag_fields, 0..) |field, i| {
+ const action = @unionInit(Union, field.name, undefined);
+ const Type = t: {
+ const Type = @TypeOf(@field(action, field.name));
+ // Types can provide custom types for their CValue.
+ switch (@typeInfo(Type)) {
+ .@"enum", .@"struct", .@"union" => if (@hasDecl(Type, "C")) break :t Type.C,
+ else => {},
+ }
+
+ break :t Type;
+ };
+
+ union_fields[i] = .{
+ .name = field.name,
+ .type = Type,
+ .alignment = @alignOf(Type),
+ };
+ }
+
+ union_fields[tag_fields.len] = .{
+ .name = "_padding",
+ .type = Padding,
+ .alignment = @alignOf(Padding),
+ };
+
+ break :cvalue @Type(.{ .@"union" = .{
+ .layout = .@"extern",
+ .tag_type = null,
+ .fields = &union_fields,
+ .decls = &.{},
+ } });
+ };
+
+ /// Convert to C union.
+ pub fn cval(self: Union) C {
+ const value: CValue = switch (self) {
+ inline else => |v, tag| @unionInit(
+ CValue,
+ @tagName(tag),
+ value: {
+ switch (@typeInfo(@TypeOf(v))) {
+ .@"enum", .@"struct", .@"union" => if (@hasDecl(@TypeOf(v), "cval")) break :value v.cval(),
+ else => {},
+ }
+
+ break :value v;
+ },
+ ),
+ };
+
+ return .{
+ .tag = @as(Tag, self),
+ .value = value,
+ };
+ }
+
+ /// Returns the value type for the given tag.
+ pub fn Value(comptime tag: Tag) type {
+ @setEvalBranchQuota(10000);
+ inline for (@typeInfo(Union).@"union".fields) |field| {
+ const field_tag = @field(Tag, field.name);
+ if (field_tag == tag) return field.type;
+ }
+
+ unreachable;
+ }
+ };
+}
+
+test "TaggedUnion: matching size" {
+ const Tag = enum(c_int) { a, b };
+ const U = TaggedUnion(
+ .c,
+ union(Tag) {
+ a: u32,
+ b: u64,
+ },
+ u64,
+ );
+
+ try testing.expectEqual(8, @sizeOf(U.CValue));
+}
+
+test "TaggedUnion: padded size" {
+ const Tag = enum(c_int) { a };
+ const U = TaggedUnion(
+ .c,
+ union(Tag) {
+ a: u32,
+ },
+ u64,
+ );
+
+ try testing.expectEqual(8, @sizeOf(U.CValue));
+}
+
+test "TaggedUnion: c conversion" {
+ const Tag = enum(c_int) { a, b };
+ const U = TaggedUnion(.c, union(Tag) {
+ a: u32,
+ b: u64,
+ }, u64);
+
+ const c = U.cval(.{ .a = 42 });
+ try testing.expectEqual(Tag.a, c.tag);
+ try testing.expectEqual(42, c.value.a);
+}