aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/lib/packed.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/packed.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/lib/packed.zig')
-rw-r--r--vendor/libghostty-vt/src/lib/packed.zig290
1 files changed, 290 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/lib/packed.zig b/vendor/libghostty-vt/src/lib/packed.zig
new file mode 100644
index 00000000..b210242d
--- /dev/null
+++ b/vendor/libghostty-vt/src/lib/packed.zig
@@ -0,0 +1,290 @@
+const std = @import("std");
+const testing = std.testing;
+
+/// Metadata for a field in a packed layout.
+pub const FieldOptions = struct {
+ /// Public name for this field. The Zig field name is used by default.
+ name: ?[]const u8 = null,
+
+ /// Public type name for scalar fields. The Zig type is used by default.
+ type_name: ?[]const u8 = null,
+
+ /// Omit this field from public metadata. This is intended for padding.
+ omit: bool = false,
+
+ /// How the field is represented in public metadata.
+ encoding: Encoding = .scalar,
+
+ pub const Encoding = union(enum) {
+ scalar,
+ @"packed": type,
+ tagged_union: type,
+ };
+};
+
+/// Create metadata for a packed struct. Physical layout information is always
+/// reflected from `T`; options only provide public names and relationships.
+pub fn Packed(
+ comptime T: type,
+ comptime options: PackedOptions(T),
+) type {
+ const info = packedStructInfo(T);
+ const FieldT = std.meta.FieldEnum(T);
+
+ return struct {
+ pub const Zig = T;
+
+ // I know this is an insane amount of validation but getting
+ // this correct is critical to C APIs working so we go overboard.
+ comptime {
+ for (info.fields, 0..) |field, i| {
+ const field_options = @field(options.fields, field.name);
+ if (field_options.omit) {
+ if (field_options.name != null or
+ field_options.type_name != null or
+ field_options.encoding != .scalar)
+ {
+ @compileError("omitted packed field has other options: " ++ field.name);
+ }
+ continue;
+ }
+
+ if (field_options.name) |name| {
+ if (name.len == 0)
+ @compileError("packed field name cannot be empty: " ++ field.name);
+ }
+
+ switch (field_options.encoding) {
+ .scalar => switch (@typeInfo(field.type)) {
+ .bool, .int, .@"enum" => {},
+ else => @compileError("packed field requires an explicit encoding: " ++ field.name),
+ },
+ .@"packed" => |Layout| {
+ if (Layout.Zig != field.type)
+ @compileError("nested packed layout has the wrong Zig type: " ++ field.name);
+ if (field_options.type_name != null)
+ @compileError("nested packed field cannot have a scalar type name: " ++ field.name);
+ },
+ .tagged_union => |Layout| {
+ if (Layout.Owner != T or Layout.Union != field.type or
+ Layout.union_field != @field(FieldT, field.name))
+ {
+ @compileError("tagged union layout does not match packed field: " ++ field.name);
+ }
+ if (field_options.type_name != null)
+ @compileError("tagged union field cannot have a scalar type name: " ++ field.name);
+ },
+ }
+
+ const public_name = field_options.name orelse field.name;
+ for (info.fields[0..i]) |previous| {
+ const previous_options = @field(options.fields, previous.name);
+ if (previous_options.omit) continue;
+ const previous_name = previous_options.name orelse previous.name;
+ if (std.mem.eql(u8, public_name, previous_name))
+ @compileError("duplicate public packed field name: " ++ public_name);
+ }
+ }
+ }
+
+ pub const Backing = info.backing_integer.?;
+ pub const Field = std.meta.FieldEnum(T);
+
+ pub fn fieldOptions(comptime field: Field) FieldOptions {
+ return @field(options.fields, @tagName(field));
+ }
+
+ pub fn fieldName(comptime field: Field) ?[]const u8 {
+ const field_options = fieldOptions(field);
+ if (field_options.omit) return null;
+ return field_options.name orelse @tagName(field);
+ }
+
+ pub fn bitOffset(comptime field: Field) usize {
+ return @bitOffsetOf(T, @tagName(field));
+ }
+
+ pub fn bitWidth(comptime field: Field) usize {
+ return @bitSizeOf(@FieldType(T, @tagName(field)));
+ }
+ };
+}
+
+/// Options for a packed struct. A field is generated for every field in `T`
+/// so unknown field names are rejected by normal Zig type checking.
+pub fn PackedOptions(comptime T: type) type {
+ const fields = packedStructInfo(T).fields;
+ const default_options: FieldOptions = .{};
+
+ var names: [fields.len][]const u8 = undefined;
+ var types: [fields.len]type = undefined;
+ var attrs: [fields.len]std.builtin.Type.StructField.Attributes = undefined;
+
+ for (fields, 0..) |field, i| {
+ names[i] = field.name;
+ types[i] = FieldOptions;
+ attrs[i] = .{ .default_value_ptr = &default_options };
+ }
+
+ const Fields = @Struct(.auto, null, &names, &types, &attrs);
+ return struct {
+ fields: Fields = .{},
+ };
+}
+
+/// Describe a packed union field whose active arm is selected by another
+/// field in the containing packed struct. Each tag maps to a reflected packed
+/// arm layout. Multiple tags may map to the same source union field.
+pub fn PackedTaggedUnion(
+ comptime OwnerT: type,
+ comptime union_field_value: std.meta.FieldEnum(OwnerT),
+ comptime tag_field_value: std.meta.FieldEnum(OwnerT),
+ comptime options: PackedTaggedUnionOptions(
+ @FieldType(OwnerT, @tagName(union_field_value)),
+ @FieldType(OwnerT, @tagName(tag_field_value)),
+ ),
+) type {
+ _ = packedStructInfo(OwnerT);
+ const UnionT = @FieldType(OwnerT, @tagName(union_field_value));
+ const union_info = @typeInfo(UnionT).@"union";
+ if (union_info.layout != .@"packed")
+ @compileError("packed tagged union value must be a packed union");
+
+ const TagT = @FieldType(OwnerT, @tagName(tag_field_value));
+ const tag_info = @typeInfo(TagT).@"enum";
+ const ArmT = PackedTaggedUnionArm(UnionT);
+
+ return struct {
+ pub const Owner = OwnerT;
+ pub const Union = UnionT;
+ pub const Tag = TagT;
+ pub const Arm = ArmT;
+ pub const union_field = union_field_value;
+ pub const tag_field = tag_field_value;
+
+ comptime {
+ for (tag_info.fields) |tag| {
+ const arm_value = @field(options.arms, tag.name) orelse continue;
+ switch (arm_value) {
+ inline else => |Layout, source| {
+ const source_name = @tagName(source);
+ if (Layout.Zig != @FieldType(UnionT, source_name))
+ @compileError("packed union arm layout has the wrong Zig type for tag: " ++ tag.name);
+ if (@bitSizeOf(Layout.Zig) > @bitSizeOf(UnionT))
+ @compileError("packed union arm is wider than its union: " ++ tag.name);
+ },
+ }
+ }
+ }
+
+ pub fn arm(comptime tag: Tag) ?Arm {
+ return @field(options.arms, @tagName(tag));
+ }
+ };
+}
+
+/// Options for mapping tag values to packed union arms.
+pub fn PackedTaggedUnionOptions(comptime Union: type, comptime Tag: type) type {
+ const union_info = @typeInfo(Union).@"union";
+ if (union_info.layout != .@"packed")
+ @compileError("packed tagged union value must be a packed union");
+ const tag_fields = @typeInfo(Tag).@"enum".fields;
+ const Arm = PackedTaggedUnionArm(Union);
+ const default_arm: ?Arm = null;
+
+ var names: [tag_fields.len][]const u8 = undefined;
+ var types: [tag_fields.len]type = undefined;
+ var attrs: [tag_fields.len]std.builtin.Type.StructField.Attributes = undefined;
+
+ for (tag_fields, 0..) |field, i| {
+ names[i] = field.name;
+ types[i] = ?Arm;
+ attrs[i] = .{ .default_value_ptr = &default_arm };
+ }
+
+ const Arms = @Struct(.auto, null, &names, &types, &attrs);
+ return struct {
+ arms: Arms = .{},
+ };
+}
+
+fn PackedTaggedUnionArm(comptime Union: type) type {
+ const fields = @typeInfo(Union).@"union".fields;
+ const Tag = std.meta.FieldEnum(Union);
+
+ var names: [fields.len][]const u8 = undefined;
+ var types: [fields.len]type = undefined;
+ var attrs: [fields.len]std.builtin.Type.UnionField.Attributes = undefined;
+
+ for (fields, 0..) |field, i| {
+ names[i] = field.name;
+ types[i] = type;
+ attrs[i] = .{ .@"align" = 1 };
+ }
+
+ return @Union(.auto, Tag, &names, &types, &attrs);
+}
+
+fn packedStructInfo(comptime T: type) std.builtin.Type.Struct {
+ const info = @typeInfo(T).@"struct";
+ if (info.layout != .@"packed")
+ @compileError("Packed requires a packed struct");
+ if (info.backing_integer == null)
+ @compileError("Packed requires an integer-backed packed struct");
+ return info;
+}
+
+test "Packed: reflected fields and nested tagged union" {
+ const Tag = enum(u2) { first, second, third };
+ const Value = packed union {
+ number: packed struct(u4) {
+ data: u3,
+ _pad: u1,
+ },
+ flags: packed struct(u4) {
+ a: bool,
+ b: bool,
+ _pad: u2,
+ },
+ };
+ const Nested = packed struct(u1) { enabled: bool };
+ const Subject = packed struct(u8) {
+ tag: Tag,
+ value: Value,
+ nested: Nested,
+ _padding: u1,
+ };
+
+ const Number = Packed(@FieldType(Value, "number"), .{ .fields = .{
+ .data = .{ .name = "number" },
+ ._pad = .{ .omit = true },
+ } });
+ const Flags = Packed(@FieldType(Value, "flags"), .{ .fields = .{
+ ._pad = .{ .omit = true },
+ } });
+ const NestedLayout = Packed(Nested, .{});
+ const ValueLayout = PackedTaggedUnion(Subject, .value, .tag, .{ .arms = .{
+ .first = .{ .number = Number },
+ .second = .{ .number = Number },
+ .third = .{ .flags = Flags },
+ } });
+ const Layout = Packed(Subject, .{ .fields = .{
+ .value = .{ .encoding = .{ .tagged_union = ValueLayout } },
+ .nested = .{ .encoding = .{ .@"packed" = NestedLayout } },
+ ._padding = .{ .omit = true },
+ } });
+
+ try testing.expectEqual(@bitOffsetOf(Subject, "value"), Layout.bitOffset(.value));
+ try testing.expectEqual(@bitSizeOf(Value), Layout.bitWidth(.value));
+ try testing.expectEqualStrings("number", Number.fieldName(.data).?);
+ try testing.expect(Number.fieldName(._pad) == null);
+ switch (ValueLayout.arm(.first).?) {
+ .number => |ArmLayout| try testing.expect(ArmLayout == Number),
+ else => return error.TestUnexpectedResult,
+ }
+ switch (ValueLayout.arm(.second).?) {
+ .number => |ArmLayout| try testing.expect(ArmLayout == Number),
+ else => return error.TestUnexpectedResult,
+ }
+ try testing.expectEqual(@as(usize, 1), NestedLayout.bitWidth(.enabled));
+}