perf: small sgr parser improvements

pull/9645/head
Qwerasd 2025-11-17 13:17:31 -07:00
parent 6d5b4a3426
commit 9ab9bc8e19
2 changed files with 143 additions and 89 deletions

View File

@ -1772,10 +1772,6 @@ pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void {
self.cursor.style.flags.underline = v;
},
.reset_underline => {
self.cursor.style.flags.underline = .none;
},
.underline_color => |rgb| {
self.cursor.style.underline_color = .{ .rgb = .{
.r = rgb.r,

View File

@ -32,7 +32,6 @@ pub const Attribute = union(Tag) {
/// Underline the text
underline: Underline,
reset_underline,
underline_color: color.RGB,
@"256_underline_color": u8,
reset_underline_color,
@ -92,7 +91,6 @@ pub const Attribute = union(Tag) {
"reset_italic",
"faint",
"underline",
"reset_underline",
"underline_color",
"256_underline_color",
"reset_underline_color",
@ -186,15 +184,16 @@ pub const Parser = struct {
/// Next returns the next attribute or null if there are no more attributes.
pub fn next(self: *Parser) ?Attribute {
if (self.idx >= self.params.len) {
// If we're at index zero it means we must have an empty
// list and an empty list implicitly means unset.
if (self.idx == 0) {
// Add one to ensure we don't loop on unset
self.idx += 1;
return .unset;
}
// We're more likely to not be done than to be done.
@branchHint(.unlikely);
return null;
// Add one to ensure we don't loop on unset
defer self.idx += 1;
// If we're at index zero it means we must have an empty list
// and an empty list implicitly means unset, otherwise we're
// done and return null.
return if (self.idx == 0) .unset else null;
}
const slice = self.params[self.idx..self.params.len];
@ -206,20 +205,30 @@ pub const Parser = struct {
// If we have a colon separator then we need to ensure we're
// parsing a value that allows it.
if (colon) switch (slice[0]) {
4, 38, 48, 58 => {},
if (colon) {
// Colons are fairly rare in the wild.
@branchHint(.unlikely);
else => {
// Consume all the colon separated values.
const start = self.idx;
while (self.params_sep.isSet(self.idx)) self.idx += 1;
self.idx += 1;
return .{ .unknown = .{
.full = self.params,
.partial = slice[0..@min(self.idx - start + 1, slice.len)],
} };
},
};
switch (slice[0]) {
4, 38, 48, 58 => {},
else => {
// In real world use it's very rare
// that we receive an invalid sequence.
@branchHint(.cold);
// Consume all the colon separated
// values and return them as unknown.
const start = self.idx;
while (self.params_sep.isSet(self.idx)) self.idx += 1;
self.idx += 1;
return .{ .unknown = .{
.full = self.params,
.partial = slice[0..@min(self.idx - start + 1, slice.len)],
} };
},
}
}
switch (slice[0]) {
0 => return .unset,
@ -232,25 +241,37 @@ pub const Parser = struct {
4 => underline: {
if (colon) {
// Colons are fairly rare in the wild.
@branchHint(.unlikely);
assert(slice.len >= 2);
if (self.isColon()) {
// Invalid/unknown SGRs are just not very likely.
@branchHint(.cold);
self.consumeUnknownColon();
break :underline;
}
self.idx += 1;
switch (slice[1]) {
0 => return .reset_underline,
1 => return .{ .underline = .single },
2 => return .{ .underline = .double },
3 => return .{ .underline = .curly },
4 => return .{ .underline = .dotted },
5 => return .{ .underline = .dashed },
return .{
.underline = switch (slice[1]) {
0 => .none,
1 => .single,
2 => .double,
3 => .curly,
4 => .dotted,
5 => .dashed,
// For unknown underline styles, just render
// a single underline.
else => return .{ .underline = .single },
}
// For unknown underline styles,
// just render a single underline.
else => single: {
// This is quite a rare condition.
@branchHint(.cold);
break :single .single;
},
},
};
}
return .{ .underline = .single };
@ -272,7 +293,7 @@ pub const Parser = struct {
23 => return .reset_italic,
24 => return .reset_underline,
24 => return .{ .underline = .none },
25 => return .reset_blink,
@ -286,23 +307,32 @@ pub const Parser = struct {
.@"8_fg" = @enumFromInt(slice[0] - 30),
},
38 => if (slice.len >= 2) switch (slice[1]) {
// `2` indicates direct-color (r, g, b).
// We need at least 3 more params for this to make sense.
2 => if (self.parseDirectColor(
.direct_color_fg,
slice,
colon,
)) |v| return v,
38 => if (slice.len >= 2) {
// We are very likely to have enough parameters.
@branchHint(.likely);
// `5` indicates indexed color.
5 => if (slice.len >= 3) {
self.idx += 2;
return .{
.@"256_fg" = @truncate(slice[2]),
};
},
else => {},
switch (slice[1]) {
// `2` indicates direct-color (r, g, b).
// We need at least 3 more params for this to make sense.
2 => if (self.parseDirectColor(
.direct_color_fg,
slice,
colon,
)) |v| return v,
// `5` indicates indexed color.
5 => if (slice.len >= 3) {
// We are very likely to have enough parameters.
@branchHint(.likely);
self.idx += 2;
return .{
.@"256_fg" = @truncate(slice[2]),
};
},
else => {},
}
},
39 => return .reset_fg,
@ -311,23 +341,32 @@ pub const Parser = struct {
.@"8_bg" = @enumFromInt(slice[0] - 40),
},
48 => if (slice.len >= 2) switch (slice[1]) {
// `2` indicates direct-color (r, g, b).
// We need at least 3 more params for this to make sense.
2 => if (self.parseDirectColor(
.direct_color_bg,
slice,
colon,
)) |v| return v,
48 => if (slice.len >= 2) {
// We are very likely to have enough parameters.
@branchHint(.likely);
// `5` indicates indexed color.
5 => if (slice.len >= 3) {
self.idx += 2;
return .{
.@"256_bg" = @truncate(slice[2]),
};
},
else => {},
switch (slice[1]) {
// `2` indicates direct-color (r, g, b).
// We need at least 3 more params for this to make sense.
2 => if (self.parseDirectColor(
.direct_color_bg,
slice,
colon,
)) |v| return v,
// `5` indicates indexed color.
5 => if (slice.len >= 3) {
// We are very likely to have enough parameters.
@branchHint(.likely);
self.idx += 2;
return .{
.@"256_bg" = @truncate(slice[2]),
};
},
else => {},
}
},
49 => return .reset_bg,
@ -335,23 +374,31 @@ pub const Parser = struct {
53 => return .overline,
55 => return .reset_overline,
58 => if (slice.len >= 2) switch (slice[1]) {
// `2` indicates direct-color (r, g, b).
// We need at least 3 more params for this to make sense.
2 => if (self.parseDirectColor(
.underline_color,
slice,
colon,
)) |v| return v,
58 => if (slice.len >= 2) {
// We are very likely to have enough parameters.
@branchHint(.likely);
// `5` indicates indexed color.
5 => if (slice.len >= 3) {
self.idx += 2;
return .{
.@"256_underline_color" = @truncate(slice[2]),
};
},
else => {},
switch (slice[1]) {
// `2` indicates direct-color (r, g, b).
// We need at least 3 more params for this to make sense.
2 => if (self.parseDirectColor(
.underline_color,
slice,
colon,
)) |v| return v,
// `5` indicates indexed color.
5 => if (slice.len >= 3) {
// We are very likely to have enough parameters.
@branchHint(.likely);
self.idx += 2;
return .{
.@"256_underline_color" = @truncate(slice[2]),
};
},
else => {},
}
},
59 => return .reset_underline_color,
@ -389,6 +436,9 @@ pub const Parser = struct {
// If we don't have a colon, then we expect exactly 3 semicolon
// separated values.
if (!colon) {
// Semicolons are much more common than colons.
@branchHint(.likely);
self.idx += 4;
return @unionInit(Attribute, @tagName(tag), .{
.r = @truncate(slice[2]),
@ -402,6 +452,9 @@ pub const Parser = struct {
const count = self.countColon();
switch (count) {
3 => {
// This is the much more common case in the wild.
@branchHint(.likely);
self.idx += 4;
return @unionInit(Attribute, @tagName(tag), .{
.r = @truncate(slice[2]),
@ -420,6 +473,9 @@ pub const Parser = struct {
},
else => {
// Invalid/unknown SGRs just don't happen very often at all.
@branchHint(.cold);
self.consumeUnknownColon();
return null;
},
@ -560,7 +616,8 @@ test "sgr: underline" {
{
const v = testParse(&[_]u16{24});
try testing.expect(v == .reset_underline);
try testing.expect(v == .underline);
try testing.expect(v.underline == .none);
}
}
@ -573,7 +630,8 @@ test "sgr: underline styles" {
{
const v = testParseColon(&[_]u16{ 4, 0 });
try testing.expect(v == .reset_underline);
try testing.expect(v == .underline);
try testing.expect(v.underline == .none);
}
{