From 41dd733ca7123c9ac4bfc6c781697a660e95b052 Mon Sep 17 00:00:00 2001 From: Can Celik Date: Sat, 4 Apr 2026 19:22:04 +0300 Subject: feat: finish ghostty pane backend integration --- vendor/libghostty-vt/src/lib/enum.zig | 145 ++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 vendor/libghostty-vt/src/lib/enum.zig (limited to 'vendor/libghostty-vt/src/lib/enum.zig') 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; + }; +} -- cgit v1.2.3