diff --git a/src/Cell.zig b/src/Cell.zig index e733223..75cc03e 100644 --- a/src/Cell.zig +++ b/src/Cell.zig @@ -1,9 +1,11 @@ +const std = @import("std"); const Image = @import("Image.zig"); char: Character = .{}, style: Style = .{}, link: Hyperlink = .{}, image: ?Image.Placement = null, +default: bool = false, /// Segment is a contiguous run of text that has a constant style pub const Segment = struct { @@ -59,6 +61,43 @@ pub const Style = struct { reverse: bool = false, invisible: bool = false, strikethrough: bool = false, + + pub fn eql(a: Style, b: Style) bool { + const SGRBits = packed struct { + bold: bool, + dim: bool, + italic: bool, + blink: bool, + reverse: bool, + invisible: bool, + strikethrough: bool, + }; + const a_sgr: SGRBits = .{ + .bold = a.bold, + .dim = a.dim, + .italic = a.italic, + .blink = a.blink, + .reverse = a.reverse, + .invisible = a.invisible, + .strikethrough = a.strikethrough, + }; + const b_sgr: SGRBits = .{ + .bold = b.bold, + .dim = b.dim, + .italic = b.italic, + .blink = b.blink, + .reverse = b.reverse, + .invisible = b.invisible, + .strikethrough = b.strikethrough, + }; + const a_cast: u7 = @bitCast(a_sgr); + const b_cast: u7 = @bitCast(b_sgr); + return a_cast == b_cast and + Color.eql(a.fg, b.fg) and + Color.eql(a.bg, b.bg) and + Color.eql(a.ul, b.ul) and + a.ul_style == b.ul_style; + } }; pub const Color = union(enum) { @@ -66,6 +105,19 @@ pub const Color = union(enum) { index: u8, rgb: [3]u8, + pub fn eql(a: Color, b: Color) bool { + if (a == .default and b == .default) + return true + else if (a == .index and b == .index) + return a.index == b.index + else if (a == .rgb and b == .rgb) + return a.rgb[0] == b.rgb[0] and + a.rgb[1] == b.rgb[1] and + a.rgb[2] == b.rgb[2] + else + return false; + } + pub fn rgbFromUint(val: u24) Color { const r_bits = val & 0b11111111_00000000_00000000; const g_bits = val & 0b00000000_11111111_00000000; diff --git a/src/InternalScreen.zig b/src/InternalScreen.zig index 6001306..8ea7007 100644 --- a/src/InternalScreen.zig +++ b/src/InternalScreen.zig @@ -16,12 +16,19 @@ pub const InternalCell = struct { uri_id: std.ArrayList(u8) = undefined, // if we got skipped because of a wide character skipped: bool = false, + default: bool = false, pub fn eql(self: InternalCell, cell: Cell) bool { - return std.mem.eql(u8, self.char.items, cell.char.grapheme) and - std.meta.eql(self.style, cell.style) and - std.mem.eql(u8, self.uri.items, cell.link.uri) and - std.mem.eql(u8, self.uri_id.items, cell.link.params); + // fastpath when both cells are default + if (self.default and cell.default) return true; + // this is actually faster than std.meta.eql on the individual items. + // Our strings are always small, usually less than 4 bytes so the simd + // usage in std.mem.eql has too much overhead vs looping the bytes + if (!std.mem.eql(u8, self.char.items, cell.char.grapheme)) return false; + if (!Style.eql(self.style, cell.style)) return false; + if (!std.mem.eql(u8, self.uri.items, cell.link.uri)) return false; + if (!std.mem.eql(u8, self.uri_id.items, cell.link.params)) return false; + return true; } }; @@ -94,6 +101,7 @@ pub fn writeCell( log.warn("couldn't write uri_id", .{}); }; self.buf[i].style = cell.style; + self.buf[i].default = cell.default; } pub fn readCell(self: *InternalScreen, col: usize, row: usize) ?Cell { diff --git a/src/Vaxis.zig b/src/Vaxis.zig index 44580be..6623416 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -323,7 +323,7 @@ pub fn render(self: *Vaxis) !void { // find out what // foreground - if (!std.meta.eql(cursor.fg, cell.style.fg)) { + if (!Cell.Color.eql(cursor.fg, cell.style.fg)) { const writer = tty.buffered_writer.writer(); switch (cell.style.fg) { .default => _ = try tty.write(ctlseqs.fg_reset), @@ -340,7 +340,7 @@ pub fn render(self: *Vaxis) !void { } } // background - if (!std.meta.eql(cursor.bg, cell.style.bg)) { + if (!Cell.Color.eql(cursor.bg, cell.style.bg)) { const writer = tty.buffered_writer.writer(); switch (cell.style.bg) { .default => _ = try tty.write(ctlseqs.bg_reset), @@ -357,7 +357,7 @@ pub fn render(self: *Vaxis) !void { } } // underline color - if (!std.meta.eql(cursor.ul, cell.style.ul)) { + if (!Cell.Color.eql(cursor.ul, cell.style.ul)) { const writer = tty.buffered_writer.writer(); switch (cell.style.bg) { .default => _ = try tty.write(ctlseqs.ul_reset), @@ -370,7 +370,7 @@ pub fn render(self: *Vaxis) !void { } } // underline style - if (!std.meta.eql(cursor.ul_style, cell.style.ul_style)) { + if (cursor.ul_style != cell.style.ul_style) { const seq = switch (cell.style.ul_style) { .off => ctlseqs.ul_off, .single => ctlseqs.ul_single, @@ -445,7 +445,7 @@ pub fn render(self: *Vaxis) !void { } // url - if (!std.meta.eql(link.uri, cell.link.uri)) { + if (!std.mem.eql(u8, link.uri, cell.link.uri)) { var ps = cell.link.params; if (cell.link.uri.len == 0) { // Empty out the params no matter what if we don't have diff --git a/src/Window.zig b/src/Window.zig index 2490567..b93ccb8 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -196,7 +196,7 @@ pub fn writeCell(self: Window, col: usize, row: usize, cell: Cell) void { /// fills the window with the default cell pub fn clear(self: Window) void { - self.fill(.{}); + self.fill(.{ .default = true }); } /// returns the width of the grapheme. This depends on the terminal capabilities