aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/lib/enum.zig
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/enum.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/lib/enum.zig')
-rw-r--r--vendor/libghostty-vt/src/lib/enum.zig145
1 files changed, 145 insertions, 0 deletions
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;
+ };
+}