mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-05 15:18:40 +00:00
terminal: support nested field paths and eqlAny in Mask
Two small extensions to the Mask helper, both motivated by the reflow bulk run scan in the next commits. fieldMask now accepts dot-separated field paths so a mask can cover a nested field of a packed struct or packed union member, e.g. "content.codepoint.data" covers exactly the codepoint bits of a cell without its padding. Packed union members all share bit offset zero. Mask gains eqlAny, the "any" counterpart to eql: it returns whether any value in a group has masked fields equal to the expected pattern. This supports run scans that must stop when a sentinel value appears anywhere in a group, such as the Kitty virtual placeholder codepoint which requires slow-path handling.
This commit is contained in:
@@ -2239,6 +2239,12 @@ pub const Cell = packed struct(u64) {
|
||||
/// struct T, used for masked compares of raw backing-integer values
|
||||
/// (e.g. `Row.Backing`, `Cell.Backing`). This is an implementation
|
||||
/// detail of `Mask`, which is the public API built on top of this.
|
||||
///
|
||||
/// A field may be a dot-separated path (e.g. "content.codepoint.data")
|
||||
/// to cover only a nested field of a packed struct or packed union
|
||||
/// member. This allows a mask to be more precise than a whole
|
||||
/// top-level field, e.g. covering the codepoint bits of a cell without
|
||||
/// its padding.
|
||||
fn fieldMask(
|
||||
comptime T: type,
|
||||
comptime fields: []const []const u8,
|
||||
@@ -2246,16 +2252,33 @@ fn fieldMask(
|
||||
// Backing int of the packed struct
|
||||
const Int = @typeInfo(T).@"struct".backing_integer.?;
|
||||
|
||||
var mask: Int = 0;
|
||||
inline for (fields) |field| {
|
||||
comptime var mask: Int = 0;
|
||||
inline for (fields) |path| {
|
||||
// Walk the path to find the total bit offset and the type of
|
||||
// the (possibly nested) field.
|
||||
comptime var offset = 0;
|
||||
comptime var Field = T;
|
||||
comptime var it = std.mem.splitScalar(u8, path, '.');
|
||||
inline while (comptime it.next()) |name| {
|
||||
offset += switch (@typeInfo(Field)) {
|
||||
.@"struct" => @bitOffsetOf(Field, name),
|
||||
|
||||
// Packed union members all share bit offset zero.
|
||||
.@"union" => |u| offset: {
|
||||
comptime assert(u.layout == .@"packed");
|
||||
break :offset 0;
|
||||
},
|
||||
|
||||
else => @compileError("invalid field path: " ++ path),
|
||||
};
|
||||
Field = @FieldType(Field, name);
|
||||
}
|
||||
|
||||
// The type that fits all the bits we need to set.
|
||||
const Ones = std.meta.Int(
|
||||
.unsigned,
|
||||
@bitSizeOf(@FieldType(T, field)),
|
||||
);
|
||||
const Ones = std.meta.Int(.unsigned, @bitSizeOf(Field));
|
||||
|
||||
// Mask out the ones
|
||||
mask |= @as(Int, std.math.maxInt(Ones)) << @bitOffsetOf(T, field);
|
||||
mask |= @as(Int, std.math.maxInt(Ones)) << offset;
|
||||
}
|
||||
|
||||
return mask;
|
||||
@@ -2378,6 +2401,21 @@ pub fn Mask(
|
||||
return pattern(v) == expected;
|
||||
}
|
||||
|
||||
/// Returns true if any value in the group of group_len values
|
||||
/// starting at index i has masked fields equal to the expected
|
||||
/// pattern (see `pattern`). This is the "any" counterpart to
|
||||
/// `eql`: use it to detect the presence of a specific value
|
||||
/// within a group, e.g. a run scan that must stop when it
|
||||
/// encounters a sentinel codepoint anywhere in the group.
|
||||
pub inline fn eqlAny(
|
||||
values: []const T,
|
||||
i: usize,
|
||||
expected: Backing,
|
||||
) bool {
|
||||
const masked = load(values, i) & @as(Group, @splat(mask));
|
||||
return @reduce(.Or, masked == @as(Group, @splat(expected)));
|
||||
}
|
||||
|
||||
/// Like `eql` but returns the number of leading values whose
|
||||
/// masked fields equal the expected pattern, i.e. group_len if
|
||||
/// the entire group matches. This is useful for early-exit run
|
||||
@@ -2497,6 +2535,59 @@ test "Mask" {
|
||||
try testing.expectEqual(M.strip(styled), M.strip(styled_other));
|
||||
try testing.expect(M.strip(styled) != M.strip(styled2));
|
||||
}
|
||||
|
||||
// eqlAny: presence of a matching value anywhere in the group
|
||||
{
|
||||
const expected = M.pattern(styled);
|
||||
var cells: [4]Cell = .{ plain, plain, plain, plain };
|
||||
try testing.expect(!M.eqlAny(&cells, 0, expected));
|
||||
|
||||
cells[2] = styled;
|
||||
try testing.expect(M.eqlAny(&cells, 0, expected));
|
||||
|
||||
// Masked compare: same masked fields with a different
|
||||
// codepoint still matches.
|
||||
cells[2] = styled2;
|
||||
try testing.expect(M.eqlAny(&cells, 0, expected));
|
||||
}
|
||||
}
|
||||
|
||||
test "Mask nested field path" {
|
||||
// Mask only the codepoint data bits of the content field, not
|
||||
// the padding next to it or any other field.
|
||||
const M = Mask(Cell, &.{"content.codepoint.data"}, 4);
|
||||
|
||||
const a: Cell = .init('A');
|
||||
var b: Cell = .init('A');
|
||||
b.style_id = 5;
|
||||
b.wide = .wide;
|
||||
const c: Cell = .init('C');
|
||||
|
||||
// Same codepoint matches regardless of other fields.
|
||||
const expected = M.pattern(a);
|
||||
try testing.expect(M.eqlScalar(b, expected));
|
||||
try testing.expect(!M.eqlScalar(c, expected));
|
||||
|
||||
// The mask must cover exactly the codepoint data bits.
|
||||
const cp_offset = @bitOffsetOf(Cell, "content");
|
||||
try testing.expectEqual(
|
||||
@as(u64, std.math.maxInt(u21)) << cp_offset,
|
||||
comptime fieldMask(Cell, &.{"content.codepoint.data"}),
|
||||
);
|
||||
|
||||
// Group variants
|
||||
{
|
||||
var cells: [4]Cell = .{ a, b, a, b };
|
||||
try testing.expect(M.eql(&cells, 0, expected));
|
||||
try testing.expect(M.eqlAny(&cells, 0, expected));
|
||||
|
||||
cells[1] = c;
|
||||
try testing.expect(!M.eql(&cells, 0, expected));
|
||||
try testing.expect(M.eqlAny(&cells, 0, expected));
|
||||
|
||||
const none: [4]Cell = .{ c, c, c, c };
|
||||
try testing.expect(!M.eqlAny(&none, 0, expected));
|
||||
}
|
||||
}
|
||||
|
||||
// Uncomment this when you want to do some math.
|
||||
|
||||
Reference in New Issue
Block a user