aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/lib/enum.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/lib/enum.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/lib/enum.zig')
-rw-r--r--vendor/libghostty-vt/src/lib/enum.zig99
1 files changed, 56 insertions, 43 deletions
diff --git a/vendor/libghostty-vt/src/lib/enum.zig b/vendor/libghostty-vt/src/lib/enum.zig
index 76e05fa9..3c7a3a41 100644
--- a/vendor/libghostty-vt/src/lib/enum.zig
+++ b/vendor/libghostty-vt/src/lib/enum.zig
@@ -5,58 +5,50 @@ const Target = @import("target.zig").Target;
/// 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.
+/// In all cases, each enum value is its index in `keys`. The order MUST NOT
+/// be changed when the integer values are part of an ABI or serialized format.
+/// A null key removes that field while preserving its integer hole.
///
/// 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.
+/// C enums use `c_int`. Zig enums use the smallest unsigned integer that can
+/// represent every key index, including holes.
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,
+ var names_raw: [keys.len][]const u8 = undefined;
+ var values_raw: [keys.len]comptime_int = undefined;
+ const names_actual, const values_actual = kv: {
+ // Remove null fields while preserving their positions as integer holes.
+ var to: comptime_int = 0;
+ for (0..keys.len) |from| {
+ if (keys[from]) |key| {
+ names_raw[to] = key;
+ values_raw[to] = from;
+ to += 1;
}
- continue;
- };
+ }
- fields[fields_i] = .{
- .name = key,
- .value = fields_i + holes,
- };
- fields_i += 1;
- }
+ break :kv .{ names_raw[0..to], values_raw[0..to] };
+ };
- // 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;
+ const TagInt = switch (target) {
+ .c => c_int,
+ .zig => std.math.IntFittingRange(0, keys.len - 1),
+ };
+
+ return @Enum(TagInt, .exhaustive, names_actual, &(values: {
+ // We have to transform our comptime_int values into the actual int
+ // we're creating the enum as.
+ var result_int_values: [values_actual.len]TagInt = undefined;
+ for (0..values_actual.len) |idx| {
+ result_int_values[idx] = values_actual[idx];
+ }
+ break :values result_int_values;
+ }));
}
test "zig" {
@@ -73,7 +65,7 @@ test "c" {
try testing.expectEqual(c_int, info.tag_type);
}
-test "abi by removing a key" {
+test "stable values when removing a key" {
const testing = std.testing;
// C
{
@@ -88,10 +80,28 @@ test "abi by removing a key" {
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));
+ try testing.expectEqual(3, @intFromEnum(T.d));
}
}
+test "zig backing integer includes trailing holes" {
+ const testing = std.testing;
+ const T = Enum(.zig, &.{ "a", null, null, null, null });
+ const info = @typeInfo(T).@"enum";
+ try testing.expectEqual(u3, info.tag_type);
+ try testing.expectEqual(0, @intFromEnum(T.a));
+}
+
+test "zig values remain stable across multiple holes" {
+ const testing = std.testing;
+ const T = Enum(.zig, &.{ null, "b", null, "d", null, "f" });
+ const info = @typeInfo(T).@"enum";
+ try testing.expectEqual(u3, info.tag_type);
+ try testing.expectEqual(1, @intFromEnum(T.b));
+ try testing.expectEqual(3, @intFromEnum(T.d));
+ try testing.expectEqual(5, @intFromEnum(T.f));
+}
+
/// 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.
@@ -125,7 +135,10 @@ pub fn checkGhosttyHEnum(
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, .{});
+ std.log.err(
+ "{s} key {s} does not have the same backing int as " ++ expected_name,
+ .{ @typeName(T), field.name },
+ );
return e;
};