diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/terminal/style.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/style.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/terminal/style.zig | 222 |
1 files changed, 177 insertions, 45 deletions
diff --git a/vendor/libghostty-vt/src/terminal/style.zig b/vendor/libghostty-vt/src/terminal/style.zig index 720789bb..701356e9 100644 --- a/vendor/libghostty-vt/src/terminal/style.zig +++ b/vendor/libghostty-vt/src/terminal/style.zig @@ -1,5 +1,7 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = @import("../quirks.zig").inlineAssert; +const fastprint = @import("../fastprint.zig"); const color = @import("color.zig"); const sgr = @import("sgr.zig"); const page = @import("page.zig"); @@ -88,11 +90,29 @@ pub const Style = struct { /// True if the style is the default style. pub inline fn default(self: Style) bool { + // On wasm, eql converts both sides to packed form; the default + // side is comptime-known, so bake it and convert only self. + // This is called on every SGR change so it's worth it. + if (comptime builtin.cpu.arch.isWasm()) { + const d: u128 = comptime @bitCast(PackedStyle.fromStyle(.{})); + return @as(u128, @bitCast(PackedStyle.fromStyle(self))) == d; + } + return self.eql(.{}); } /// True if the style is equal to another style. pub fn eql(self: Style, other: Style) bool { + // On wasm, comparing packed forms wins (~13% on SGR-heavy streams). + // On native, the branchy early-exit field compare below measures + // ~5% faster (unequal styles usually differ in the first + // field or two), so each target keeps its own strategy. + if (comptime builtin.cpu.arch.isWasm()) { + const a: u128 = @bitCast(PackedStyle.fromStyle(self)); + const b: u128 = @bitCast(PackedStyle.fromStyle(other)); + return a == b; + } + return self.flags == other.flags and self.fg_color.eql(other.fg_color) and self.bg_color.eql(other.bg_color) and @@ -111,7 +131,7 @@ pub const Style = struct { palette: *const color.Palette, ) ?color.RGB { return switch (cell.content_tag) { - .bg_color_palette => palette[cell.content.color_palette], + .bg_color_palette => palette[cell.content.color_palette.data], .bg_color_rgb => rgb: { const rgb = cell.content.color_rgb; break :rgb .{ .r = rgb.r, .g = rgb.g, .b = rgb.b }; @@ -212,7 +232,7 @@ pub const Style = struct { .none => null, .palette => |idx| .{ .content_tag = .bg_color_palette, - .content = .{ .color_palette = idx }, + .content = .{ .color_palette = .{ .data = idx } }, }, .rgb => |rgb| .{ .content_tag = .bg_color_rgb, @@ -331,63 +351,141 @@ pub const Style = struct { self: VTFormatter, writer: *std.Io.Writer, ) !void { + // Style emission is a hot path when formatting styled terminal + // contents, so all of the sequences are assembled in a buffer + // and written in one call rather than going through the + // (slower) format string machinery with a write per sequence. + // + // Worst case: `\x1b[0m` (4) + 7 flags (28) + `\x1b[53m` (5) + + // `\x1b[4:2m` (6) + 3 RGB colors (19 each = 57) = 100. + var buf: [128]u8 = undefined; + // Always reset the style. Styles are fully self-contained. // Even if this style is empty, then that means we want to go // back to the default. - try writer.writeAll("\x1b[0m"); + buf[0..4].* = "\x1b[0m".*; + var len: usize = 4; // Our flags - if (self.style.flags.bold) try writer.writeAll("\x1b[1m"); - if (self.style.flags.faint) try writer.writeAll("\x1b[2m"); - if (self.style.flags.italic) try writer.writeAll("\x1b[3m"); - if (self.style.flags.blink) try writer.writeAll("\x1b[5m"); - if (self.style.flags.inverse) try writer.writeAll("\x1b[7m"); - if (self.style.flags.invisible) try writer.writeAll("\x1b[8m"); - if (self.style.flags.strikethrough) try writer.writeAll("\x1b[9m"); - if (self.style.flags.overline) try writer.writeAll("\x1b[53m"); + if (self.style.flags.bold) { + buf[len..][0..4].* = "\x1b[1m".*; + len += 4; + } + if (self.style.flags.faint) { + buf[len..][0..4].* = "\x1b[2m".*; + len += 4; + } + if (self.style.flags.italic) { + buf[len..][0..4].* = "\x1b[3m".*; + len += 4; + } + if (self.style.flags.blink) { + buf[len..][0..4].* = "\x1b[5m".*; + len += 4; + } + if (self.style.flags.inverse) { + buf[len..][0..4].* = "\x1b[7m".*; + len += 4; + } + if (self.style.flags.invisible) { + buf[len..][0..4].* = "\x1b[8m".*; + len += 4; + } + if (self.style.flags.strikethrough) { + buf[len..][0..4].* = "\x1b[9m".*; + len += 4; + } + if (self.style.flags.overline) { + buf[len..][0..5].* = "\x1b[53m".*; + len += 5; + } switch (self.style.flags.underline) { .none => {}, - .single => try writer.writeAll("\x1b[4m"), - .double => try writer.writeAll("\x1b[4:2m"), - .curly => try writer.writeAll("\x1b[4:3m"), - .dotted => try writer.writeAll("\x1b[4:4m"), - .dashed => try writer.writeAll("\x1b[4:5m"), + .single => { + buf[len..][0..4].* = "\x1b[4m".*; + len += 4; + }, + .double => { + buf[len..][0..6].* = "\x1b[4:2m".*; + len += 6; + }, + .curly => { + buf[len..][0..6].* = "\x1b[4:3m".*; + len += 6; + }, + .dotted => { + buf[len..][0..6].* = "\x1b[4:4m".*; + len += 6; + }, + .dashed => { + buf[len..][0..6].* = "\x1b[4:5m".*; + len += 6; + }, } - // Various RGB colors. - try self.formatColor(writer, 38, self.style.fg_color); - try self.formatColor(writer, 48, self.style.bg_color); - try self.formatColor(writer, 58, self.style.underline_color); + // Various colors. + len += self.appendColor(buf[len..], 38, self.style.fg_color); + len += self.appendColor(buf[len..], 48, self.style.bg_color); + len += self.appendColor(buf[len..], 58, self.style.underline_color); + + try writer.writeAll(buf[0..len]); } - fn formatColor( + /// Appends a standalone `\x1b[{prefix};5;{idx}m` or + /// `\x1b[{prefix};2;{r};{g};{b}m` sequence to buf, returning the + /// length written. + fn appendColor( self: VTFormatter, - writer: *std.Io.Writer, + buf: []u8, prefix: u8, value: Color, - ) !void { + ) usize { switch (value) { - .none => {}, + .none => return 0, + .palette => |idx| { - if (self.palette) |p| { - const rgb = p[idx]; - try writer.print( - "\x1b[{d};2;{d};{d};{d}m", - .{ prefix, rgb.r, rgb.g, rgb.b }, - ); - } else { - try writer.print( - "\x1b[{d};5;{d}m", - .{ prefix, idx }, - ); - } + // Direct RGB: `\x1b[{prefix};2;{r};{g};{b}m` + if (self.palette) |p| return appendColorRgb( + buf, + prefix, + p[idx], + ); + + // Palette reference: `\x1b[{prefix};5;{idx}m` + buf[0..2].* = "\x1b[".*; + var len: usize = 2; + len += fastprint.printDecimal(u8, buf[len..], prefix); + buf[len..][0..3].* = ";5;".*; + len += 3; + len += fastprint.printDecimal(u8, buf[len..], idx); + buf[len] = 'm'; + len += 1; + return len; }, - .rgb => |rgb| try writer.print( - "\x1b[{d};2;{d};{d};{d}m", - .{ prefix, rgb.r, rgb.g, rgb.b }, - ), + + .rgb => |rgb| return appendColorRgb(buf, prefix, rgb), } } + + /// Appends `\x1b[{prefix};2;{r};{g};{b}m` to buf, returning the + /// length written. + fn appendColorRgb(buf: []u8, prefix: u8, rgb: color.RGB) usize { + buf[0..2].* = "\x1b[".*; + var len: usize = 2; + len += fastprint.printDecimal(u8, buf[len..], prefix); + buf[len..][0..3].* = ";2;".*; + len += 3; + len += fastprint.printDecimal(u8, buf[len..], rgb.r); + buf[len] = ';'; + len += 1; + len += fastprint.printDecimal(u8, buf[len..], rgb.g); + buf[len] = ';'; + len += 1; + len += fastprint.printDecimal(u8, buf[len..], rgb.b); + buf[len] = 'm'; + len += 1; + return len; + } }; const HtmlFormatter = struct { @@ -444,18 +542,52 @@ pub const Style = struct { property: []const u8, c: Color, ) !void { + // Style emission is a hot path when formatting styled terminal + // contents, so the values are assembled in a buffer and written + // in one call rather than going through the (slower) format + // string machinery. + var buf: [32]u8 = undefined; + var len: usize = 0; switch (c) { - .none => {}, + .none => return, + + // `{property}: rgb({r}, {g}, {b});` .palette => |idx| { if (self.palette) |p| { - const rgb = p[idx]; - try writer.print("{s}: rgb({d}, {d}, {d});", .{ property, rgb.r, rgb.g, rgb.b }); + len = formatColorRgb(&buf, p[idx]); } else { - try writer.print("{s}: var(--vt-palette-{d});", .{ property, idx }); + // `{property}: var(--vt-palette-{idx});` + const prefix = ": var(--vt-palette-"; + buf[0..prefix.len].* = prefix.*; + len = prefix.len; + len += fastprint.printDecimal(u8, buf[len..], idx); + buf[len..][0..2].* = ");".*; + len += 2; } }, - .rgb => |rgb| try writer.print("{s}: rgb({d}, {d}, {d});", .{ property, rgb.r, rgb.g, rgb.b }), + + .rgb => |rgb| len = formatColorRgb(&buf, rgb), } + + try writer.writeAll(property); + try writer.writeAll(buf[0..len]); + } + + /// Writes `: rgb({r}, {g}, {b});` into buf, returning the length + /// written. + fn formatColorRgb(buf: *[32]u8, rgb: color.RGB) usize { + buf[0..6].* = ": rgb(".*; + var len: usize = 6; + len += fastprint.printDecimal(u8, buf[len..], rgb.r); + buf[len..][0..2].* = ", ".*; + len += 2; + len += fastprint.printDecimal(u8, buf[len..], rgb.g); + buf[len..][0..2].* = ", ".*; + len += 2; + len += fastprint.printDecimal(u8, buf[len..], rgb.b); + buf[len..][0..2].* = ");".*; + len += 2; + return len; } }; |
