From 060bdff117224ea467f2ade55f90dbc96a1b942f Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 2 Jan 2024 17:01:00 -0600 Subject: [PATCH] terminal: track palette color in cell state Rather than immediately converting a color palette index into an RGB value for a cell color, when a palette color is used track the palette color directly in the cell state and convert to an RGB value in the renderer. This causes palette color changes to take effect immediately instead of only for newly drawn cells. --- src/font/shaper/harfbuzz.zig | 16 ++- src/font/shaper/run.zig | 4 +- src/inspector/Inspector.zig | 103 ++++++++++++-------- src/inspector/cursor.zig | 102 +++++++++++-------- src/renderer/Metal.zig | 34 +++++-- src/renderer/OpenGL.zig | 34 +++++-- src/terminal/Screen.zig | 84 +++++++++------- src/terminal/Terminal.zig | 184 ++++++++++++++++------------------- 8 files changed, 320 insertions(+), 241 deletions(-) diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index 4a3613276..cc36bcec4 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -260,8 +260,7 @@ test "run iterator: empty cells with background set" { // Make a screen with some data var screen = try terminal.Screen.init(alloc, 3, 5, 0); defer screen.deinit(); - screen.cursor.pen.bg = try terminal.color.Name.cyan.default(); - screen.cursor.pen.attrs.has_bg = true; + screen.cursor.pen.bg = .{ .rgb = try terminal.color.Name.cyan.default() }; try screen.testWriteString("A"); // Get our first row @@ -836,10 +835,9 @@ test "shape cell attribute change" { { var screen = try terminal.Screen.init(alloc, 3, 10, 0); defer screen.deinit(); - screen.cursor.pen.attrs.has_fg = true; - screen.cursor.pen.fg = .{ .r = 1, .g = 2, .b = 3 }; + screen.cursor.pen.fg = .{ .rgb = .{ .r = 1, .g = 2, .b = 3 } }; try screen.testWriteString(">"); - screen.cursor.pen.fg = .{ .r = 3, .g = 2, .b = 1 }; + screen.cursor.pen.fg = .{ .rgb = .{ .r = 3, .g = 2, .b = 1 } }; try screen.testWriteString("="); var shaper = &testdata.shaper; @@ -856,10 +854,9 @@ test "shape cell attribute change" { { var screen = try terminal.Screen.init(alloc, 3, 10, 0); defer screen.deinit(); - screen.cursor.pen.attrs.has_bg = true; - screen.cursor.pen.bg = .{ .r = 1, .g = 2, .b = 3 }; + screen.cursor.pen.bg = .{ .rgb = .{ .r = 1, .g = 2, .b = 3 } }; try screen.testWriteString(">"); - screen.cursor.pen.bg = .{ .r = 3, .g = 2, .b = 1 }; + screen.cursor.pen.bg = .{ .rgb = .{ .r = 3, .g = 2, .b = 1 } }; try screen.testWriteString("="); var shaper = &testdata.shaper; @@ -876,8 +873,7 @@ test "shape cell attribute change" { { var screen = try terminal.Screen.init(alloc, 3, 10, 0); defer screen.deinit(); - screen.cursor.pen.attrs.has_bg = true; - screen.cursor.pen.bg = .{ .r = 1, .g = 2, .b = 3 }; + screen.cursor.pen.bg = .{ .rgb = .{ .r = 1, .g = 2, .b = 3 } }; try screen.testWriteString(">"); try screen.testWriteString("="); diff --git a/src/font/shaper/run.zig b/src/font/shaper/run.zig index 093ffaebe..dd5b93be6 100644 --- a/src/font/shaper/run.zig +++ b/src/font/shaper/run.zig @@ -87,8 +87,8 @@ pub const RunIterator = struct { const prev_attrs: Int = @bitCast(prev_cell.attrs.styleAttrs()); const attrs: Int = @bitCast(cell.attrs.styleAttrs()); if (prev_attrs != attrs) break; - if (cell.attrs.has_bg and !cell.bg.eql(prev_cell.bg)) break; - if (cell.attrs.has_fg and !cell.fg.eql(prev_cell.fg)) break; + if (!cell.bg.eql(prev_cell.bg)) break; + if (!cell.fg.eql(prev_cell.fg)) break; } // Text runs break when font styles change so we need to get diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 36d362e4c..6a4235a7e 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -299,7 +299,8 @@ fn renderScreenWindow(self: *Inspector) void { ); defer cimgui.c.igEndTable(); - inspector.cursor.renderInTable(&screen.cursor); + const palette = self.surface.io.terminal.color_palette.colors; + inspector.cursor.renderInTable(&screen.cursor, &palette); } // table cimgui.c.igTextDisabled("(Any styles not shown are not currently set)"); @@ -871,49 +872,66 @@ fn renderCellWindow(self: *Inspector) void { } // If we have a color then we show the color - color: { - cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); - _ = cimgui.c.igTableSetColumnIndex(0); - cimgui.c.igText("Foreground Color"); - _ = cimgui.c.igTableSetColumnIndex(1); - if (!selected.cell.attrs.has_fg) { - cimgui.c.igText("default"); - break :color; - } + cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); + _ = cimgui.c.igTableSetColumnIndex(0); + cimgui.c.igText("Foreground Color"); + _ = cimgui.c.igTableSetColumnIndex(1); + switch (selected.cell.fg) { + .none => cimgui.c.igText("default"), + else => { + const rgb = switch (selected.cell.fg) { + .none => unreachable, + .indexed => |idx| self.surface.io.terminal.color_palette.colors[idx], + .rgb => |rgb| rgb, + }; - var color: [3]f32 = .{ - @as(f32, @floatFromInt(selected.cell.fg.r)) / 255, - @as(f32, @floatFromInt(selected.cell.fg.g)) / 255, - @as(f32, @floatFromInt(selected.cell.fg.b)) / 255, - }; - _ = cimgui.c.igColorEdit3( - "color_fg", - &color, - cimgui.c.ImGuiColorEditFlags_NoPicker | - cimgui.c.ImGuiColorEditFlags_NoLabel, - ); + if (selected.cell.fg == .indexed) { + cimgui.c.igValue_Int("Palette", selected.cell.fg.indexed); + } + + var color: [3]f32 = .{ + @as(f32, @floatFromInt(rgb.r)) / 255, + @as(f32, @floatFromInt(rgb.g)) / 255, + @as(f32, @floatFromInt(rgb.b)) / 255, + }; + _ = cimgui.c.igColorEdit3( + "color_fg", + &color, + cimgui.c.ImGuiColorEditFlags_NoPicker | + cimgui.c.ImGuiColorEditFlags_NoLabel, + ); + }, } - color: { - cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); - _ = cimgui.c.igTableSetColumnIndex(0); - cimgui.c.igText("Background Color"); - _ = cimgui.c.igTableSetColumnIndex(1); - if (!selected.cell.attrs.has_bg) { - cimgui.c.igText("default"); - break :color; - } - var color: [3]f32 = .{ - @as(f32, @floatFromInt(selected.cell.bg.r)) / 255, - @as(f32, @floatFromInt(selected.cell.bg.g)) / 255, - @as(f32, @floatFromInt(selected.cell.bg.b)) / 255, - }; - _ = cimgui.c.igColorEdit3( - "color_bg", - &color, - cimgui.c.ImGuiColorEditFlags_NoPicker | - cimgui.c.ImGuiColorEditFlags_NoLabel, - ); + cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); + _ = cimgui.c.igTableSetColumnIndex(0); + cimgui.c.igText("Background Color"); + _ = cimgui.c.igTableSetColumnIndex(1); + switch (selected.cell.bg) { + .none => cimgui.c.igText("default"), + else => { + const rgb = switch (selected.cell.bg) { + .none => unreachable, + .indexed => |idx| self.surface.io.terminal.color_palette.colors[idx], + .rgb => |rgb| rgb, + }; + + if (selected.cell.bg == .indexed) { + cimgui.c.igValue_Int("Palette", selected.cell.bg.indexed); + } + + var color: [3]f32 = .{ + @as(f32, @floatFromInt(rgb.r)) / 255, + @as(f32, @floatFromInt(rgb.g)) / 255, + @as(f32, @floatFromInt(rgb.b)) / 255, + }; + _ = cimgui.c.igColorEdit3( + "color_bg", + &color, + cimgui.c.ImGuiColorEditFlags_NoPicker | + cimgui.c.ImGuiColorEditFlags_NoLabel, + ); + }, } // Boolean styles @@ -1109,7 +1127,8 @@ fn renderTermioWindow(self: *Inspector) void { 0, ); defer cimgui.c.igEndTable(); - inspector.cursor.renderInTable(&ev.cursor); + const palette = self.surface.io.terminal.color_palette.colors; + inspector.cursor.renderInTable(&ev.cursor, &palette); { cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); diff --git a/src/inspector/cursor.zig b/src/inspector/cursor.zig index 9991e331a..c1098a7c7 100644 --- a/src/inspector/cursor.zig +++ b/src/inspector/cursor.zig @@ -3,7 +3,10 @@ const cimgui = @import("cimgui"); const terminal = @import("../terminal/main.zig"); /// Render cursor information with a table already open. -pub fn renderInTable(cursor: *const terminal.Screen.Cursor) void { +pub fn renderInTable( + cursor: *const terminal.Screen.Cursor, + palette: *const terminal.color.Palette, +) void { { cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); { @@ -41,49 +44,66 @@ pub fn renderInTable(cursor: *const terminal.Screen.Cursor) void { } // If we have a color then we show the color - if (cursor.pen.attrs.has_fg) color: { - cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); - _ = cimgui.c.igTableSetColumnIndex(0); - cimgui.c.igText("Foreground Color"); - _ = cimgui.c.igTableSetColumnIndex(1); - if (!cursor.pen.attrs.has_fg) { - cimgui.c.igText("default"); - break :color; - } + cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); + _ = cimgui.c.igTableSetColumnIndex(0); + cimgui.c.igText("Foreground Color"); + _ = cimgui.c.igTableSetColumnIndex(1); + switch (cursor.pen.fg) { + .none => cimgui.c.igText("default"), + else => { + const rgb = switch (cursor.pen.fg) { + .none => unreachable, + .indexed => |idx| palette[idx], + .rgb => |rgb| rgb, + }; - var color: [3]f32 = .{ - @as(f32, @floatFromInt(cursor.pen.fg.r)) / 255, - @as(f32, @floatFromInt(cursor.pen.fg.g)) / 255, - @as(f32, @floatFromInt(cursor.pen.fg.b)) / 255, - }; - _ = cimgui.c.igColorEdit3( - "color_fg", - &color, - cimgui.c.ImGuiColorEditFlags_NoPicker | - cimgui.c.ImGuiColorEditFlags_NoLabel, - ); + if (cursor.pen.fg == .indexed) { + cimgui.c.igValue_Int("Palette", cursor.pen.fg.indexed); + } + + var color: [3]f32 = .{ + @as(f32, @floatFromInt(rgb.r)) / 255, + @as(f32, @floatFromInt(rgb.g)) / 255, + @as(f32, @floatFromInt(rgb.b)) / 255, + }; + _ = cimgui.c.igColorEdit3( + "color_fg", + &color, + cimgui.c.ImGuiColorEditFlags_NoPicker | + cimgui.c.ImGuiColorEditFlags_NoLabel, + ); + }, } - if (cursor.pen.attrs.has_bg) color: { - cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); - _ = cimgui.c.igTableSetColumnIndex(0); - cimgui.c.igText("Background Color"); - _ = cimgui.c.igTableSetColumnIndex(1); - if (!cursor.pen.attrs.has_bg) { - cimgui.c.igText("default"); - break :color; - } - var color: [3]f32 = .{ - @as(f32, @floatFromInt(cursor.pen.bg.r)) / 255, - @as(f32, @floatFromInt(cursor.pen.bg.g)) / 255, - @as(f32, @floatFromInt(cursor.pen.bg.b)) / 255, - }; - _ = cimgui.c.igColorEdit3( - "color_bg", - &color, - cimgui.c.ImGuiColorEditFlags_NoPicker | - cimgui.c.ImGuiColorEditFlags_NoLabel, - ); + cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); + _ = cimgui.c.igTableSetColumnIndex(0); + cimgui.c.igText("Background Color"); + _ = cimgui.c.igTableSetColumnIndex(1); + switch (cursor.pen.bg) { + .none => cimgui.c.igText("default"), + else => { + const rgb = switch (cursor.pen.bg) { + .none => unreachable, + .indexed => |idx| palette[idx], + .rgb => |rgb| rgb, + }; + + if (cursor.pen.bg == .indexed) { + cimgui.c.igValue_Int("Palette", cursor.pen.bg.indexed); + } + + var color: [3]f32 = .{ + @as(f32, @floatFromInt(rgb.r)) / 255, + @as(f32, @floatFromInt(rgb.g)) / 255, + @as(f32, @floatFromInt(rgb.b)) / 255, + }; + _ = cimgui.c.igColorEdit3( + "color_bg", + &color, + cimgui.c.ImGuiColorEditFlags_NoPicker | + cimgui.c.ImGuiColorEditFlags_NoLabel, + ); + }, } // Boolean styles diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index a606619ab..96b50dea4 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -579,6 +579,7 @@ pub fn updateFrame( mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_style: ?renderer.CursorStyle, + color_palette: terminal.color.Palette, }; // Update all our data as tightly as possible within the mutex. @@ -655,6 +656,7 @@ pub fn updateFrame( .mouse = state.mouse, .preedit = preedit, .cursor_style = cursor_style, + .color_palette = state.terminal.color_palette.colors, }; }; defer { @@ -669,6 +671,7 @@ pub fn updateFrame( critical.mouse, critical.preedit, critical.cursor_style, + &critical.color_palette, ); // Update our background color @@ -1424,6 +1427,7 @@ fn rebuildCells( mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_style_: ?renderer.CursorStyle, + color_palette: *const terminal.color.Palette, ) !void { // Bg cells at most will need space for the visible screen size self.cells_bg.clearRetainingCapacity(); @@ -1579,6 +1583,7 @@ fn rebuildCells( term_selection, screen, cell, + color_palette, shaper_cell, run, shaper_cell.x, @@ -1644,11 +1649,12 @@ fn rebuildCells( } } -pub fn updateCell( +fn updateCell( self: *Metal, selection: ?terminal.Selection, screen: *terminal.Screen, cell: terminal.Screen.Cell, + palette: *const terminal.color.Palette, shaper_cell: font.shape.Cell, shaper_run: font.shape.TextRun, x: usize, @@ -1684,14 +1690,30 @@ pub fn updateCell( const cell_res: BgFg = if (!cell.attrs.inverse) .{ // In normal mode, background and fg match the cell. We // un-optionalize the fg by defaulting to our fg color. - .bg = if (cell.attrs.has_bg) cell.bg else null, - .fg = if (cell.attrs.has_fg) cell.fg else self.foreground_color, + .bg = switch (cell.bg) { + .none => null, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, + .fg = switch (cell.fg) { + .none => self.foreground_color, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, } else .{ // In inverted mode, the background MUST be set to something // (is never null) so it is either the fg or default fg. The // fg is either the bg or default background. - .bg = if (cell.attrs.has_fg) cell.fg else self.foreground_color, - .fg = if (cell.attrs.has_bg) cell.bg else self.background_color, + .bg = switch (cell.fg) { + .none => self.foreground_color, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, + .fg = switch (cell.bg) { + .none => self.background_color, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, }; // If we are selected, we our colors are just inverted fg/bg @@ -1741,7 +1763,7 @@ pub fn updateCell( // If we have a background and its not the default background // then we apply background opacity - if (cell.attrs.has_bg and !std.meta.eql(rgb, self.background_color)) { + if (cell.bg != .none and !rgb.eql(self.background_color)) { break :bg_alpha default; } diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index fd9c1c9b3..699f51943 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -649,6 +649,7 @@ pub fn updateFrame( mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_style: ?renderer.CursorStyle, + color_palette: terminal.color.Palette, }; // Update all our data as tightly as possible within the mutex. @@ -730,6 +731,7 @@ pub fn updateFrame( .mouse = state.mouse, .preedit = preedit, .cursor_style = cursor_style, + .color_palette = state.terminal.color_palette.colors, }; }; defer { @@ -752,6 +754,7 @@ pub fn updateFrame( critical.mouse, critical.preedit, critical.cursor_style, + &critical.color_palette, ); } } @@ -943,6 +946,7 @@ pub fn rebuildCells( mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_style_: ?renderer.CursorStyle, + color_palette: *const terminal.color.Palette, ) !void { const t = trace(@src()); defer t.end(); @@ -1097,6 +1101,7 @@ pub fn rebuildCells( term_selection, screen, cell, + color_palette, shaper_cell, run, shaper_cell.x, @@ -1330,11 +1335,12 @@ fn addCursor( /// Update a single cell. The bool returns whether the cell was updated /// or not. If the cell wasn't updated, a full refreshCells call is /// needed. -pub fn updateCell( +fn updateCell( self: *OpenGL, selection: ?terminal.Selection, screen: *terminal.Screen, cell: terminal.Screen.Cell, + palette: *const terminal.color.Palette, shaper_cell: font.shape.Cell, shaper_run: font.shape.TextRun, x: usize, @@ -1373,14 +1379,30 @@ pub fn updateCell( const cell_res: BgFg = if (!cell.attrs.inverse) .{ // In normal mode, background and fg match the cell. We // un-optionalize the fg by defaulting to our fg color. - .bg = if (cell.attrs.has_bg) cell.bg else null, - .fg = if (cell.attrs.has_fg) cell.fg else self.foreground_color, + .bg = switch (cell.bg) { + .none => null, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, + .fg = switch (cell.fg) { + .none => self.foreground_color, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, } else .{ // In inverted mode, the background MUST be set to something // (is never null) so it is either the fg or default fg. The // fg is either the bg or default background. - .bg = if (cell.attrs.has_fg) cell.fg else self.foreground_color, - .fg = if (cell.attrs.has_bg) cell.bg else self.background_color, + .bg = switch (cell.fg) { + .none => self.foreground_color, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, + .fg = switch (cell.bg) { + .none => self.background_color, + .indexed => |i| palette[i], + .rgb => |rgb| rgb, + }, }; // If we are selected, we our colors are just inverted fg/bg @@ -1441,7 +1463,7 @@ pub fn updateCell( // If we have a background and its not the default background // then we apply background opacity - if (cell.attrs.has_bg and !std.meta.eql(rgb, self.background_color)) { + if (cell.bg != .none and !rgb.eql(self.background_color)) { break :bg_alpha default; } diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index fa0c04352..c7d94156f 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -211,6 +211,27 @@ pub const RowHeader = struct { }; }; +/// The color associated with a single cell's foreground or background. +const CellColor = union(enum) { + none, + indexed: u8, + rgb: color.RGB, + + pub fn eql(self: CellColor, other: CellColor) bool { + return switch (self) { + .none => other == .none, + .indexed => |i| switch (other) { + .indexed => other.indexed == i, + else => false, + }, + .rgb => |rgb| switch (other) { + .rgb => other.rgb.eql(rgb), + else => false, + }, + }; + } +}; + /// Cell is a single cell within the screen. pub const Cell = struct { /// The primary unicode codepoint for this cell. Most cells (almost all) @@ -224,10 +245,9 @@ pub const Cell = struct { /// waste memory for every cell, so we use a side lookup for it. char: u32 = 0, - /// Foreground and background color. attrs.has_{bg/fg} must be checked - /// to see if these are useful values. - fg: color.RGB = .{}, - bg: color.RGB = .{}, + /// Foreground and background color. + fg: CellColor = .none, + bg: CellColor = .none, /// Underline color. /// NOTE(mitchellh): This is very rarely set so ideally we wouldn't waste @@ -237,9 +257,6 @@ pub const Cell = struct { /// On/off attributes that can be set attrs: packed struct { - has_bg: bool = false, - has_fg: bool = false, - bold: bool = false, italic: bool = false, faint: bool = false, @@ -286,7 +303,10 @@ pub const Cell = struct { } }); // We're empty if we have no char AND we have no styling - return self.char == 0 and @as(AttrInt, @bitCast(self.attrs)) == 0; + return self.char == 0 and + self.fg == .none and + self.bg == .none and + @as(AttrInt, @bitCast(self.attrs)) == 0; } /// The width of the cell. @@ -1297,9 +1317,9 @@ pub fn scrollRegionUp(self: *Screen, top: RowIndex, bottom: RowIndex, count_req: // The pen we'll use for new cells (only the BG attribute is applied to new // cells) - const pen: Cell = if (!self.cursor.pen.attrs.has_bg) .{} else .{ - .bg = self.cursor.pen.bg, - .attrs = .{ .has_bg = true }, + const pen: Cell = switch (self.cursor.pen.bg) { + .none => .{}, + else => |bg| .{ .bg = bg }, }; // Fast-path is that we have a contiguous buffer in our circular buffer. @@ -2190,9 +2210,9 @@ fn scrollDelta(self: *Screen, delta: isize, viewport_only: bool) Allocator.Error const dst = slices[0]; // The pen we'll use for new cells (only the BG attribute is applied to new // cells) - const pen: Cell = if (!self.cursor.pen.attrs.has_bg) .{} else .{ - .bg = self.cursor.pen.bg, - .attrs = .{ .has_bg = true }, + const pen: Cell = switch (self.cursor.pen.bg) { + .none => .{}, + else => |bg| .{ .bg = bg }, }; @memset(dst, .{ .cell = pen }); @@ -3488,8 +3508,7 @@ test "Row: isEmpty with only styled cells" { const row = s.getRow(.{ .active = 0 }); for (0..s.cols) |x| { const cell = row.getCellPtr(x); - cell.*.bg = .{ .r = 0xAA, .g = 0xBB, .b = 0xCC }; - cell.*.attrs.has_bg = true; + cell.*.bg = .{ .rgb = .{ .r = 0xAA, .g = 0xBB, .b = 0xCC } }; } try testing.expect(row.isEmpty()); } @@ -3763,8 +3782,7 @@ test "Screen: scrolling" { var s = try init(alloc, 3, 5, 0); defer s.deinit(); - s.cursor.pen.bg = .{ .r = 155 }; - s.cursor.pen.attrs.has_bg = true; + s.cursor.pen.bg = .{ .rgb = .{ .r = 155 } }; try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); try testing.expect(s.viewportIsBottom()); @@ -3781,7 +3799,7 @@ test "Screen: scrolling" { { // Test that our new row has the correct background const cell = s.getCell(.active, 2, 0); - try testing.expectEqual(@as(u8, 155), cell.bg.r); + try testing.expectEqual(@as(u8, 155), cell.bg.rgb.r); } // Scrolling to the bottom does nothing @@ -5095,8 +5113,7 @@ test "Screen: scrollRegionUp single with pen" { try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD"); s.cursor.pen = .{ .char = 'X' }; - s.cursor.pen.bg = .{ .r = 155 }; - s.cursor.pen.attrs.has_bg = true; + s.cursor.pen.bg = .{ .rgb = .{ .r = 155 } }; s.cursor.pen.attrs.bold = true; s.scrollRegionUp(.{ .active = 1 }, .{ .active = 2 }, 1); { @@ -5105,7 +5122,7 @@ test "Screen: scrollRegionUp single with pen" { defer alloc.free(contents); try testing.expectEqualStrings("1ABCD\n3IJKL\n\n4ABCD", contents); const cell = s.getCell(.active, 2, 0); - try testing.expectEqual(@as(u8, 155), cell.bg.r); + try testing.expectEqual(@as(u8, 155), cell.bg.rgb.r); try testing.expect(!cell.attrs.bold); try testing.expect(s.cursor.pen.attrs.bold); } @@ -5170,8 +5187,7 @@ test "Screen: scrollRegionUp fills with pen" { try s.testWriteString("A\nB\nC\nD"); s.cursor.pen = .{ .char = 'X' }; - s.cursor.pen.bg = .{ .r = 155 }; - s.cursor.pen.attrs.has_bg = true; + s.cursor.pen.bg = .{ .rgb = .{ .r = 155 } }; s.cursor.pen.attrs.bold = true; s.scrollRegionUp(.{ .active = 0 }, .{ .active = 2 }, 1); { @@ -5180,7 +5196,7 @@ test "Screen: scrollRegionUp fills with pen" { defer alloc.free(contents); try testing.expectEqualStrings("B\nC\n\nD", contents); const cell = s.getCell(.active, 2, 0); - try testing.expectEqual(@as(u8, 155), cell.bg.r); + try testing.expectEqual(@as(u8, 155), cell.bg.rgb.r); try testing.expect(!cell.attrs.bold); try testing.expect(s.cursor.pen.attrs.bold); } @@ -5202,8 +5218,7 @@ test "Screen: scrollRegionUp buffer wrap" { // Scroll s.cursor.pen = .{ .char = 'X' }; - s.cursor.pen.bg = .{ .r = 155 }; - s.cursor.pen.attrs.has_bg = true; + s.cursor.pen.bg = .{ .rgb = .{ .r = 155 } }; s.cursor.pen.attrs.bold = true; s.scrollRegionUp(.{ .screen = 0 }, .{ .screen = 2 }, 1); @@ -5213,7 +5228,7 @@ test "Screen: scrollRegionUp buffer wrap" { defer alloc.free(contents); try testing.expectEqualStrings("3IJKL\n4ABCD", contents); const cell = s.getCell(.active, 2, 0); - try testing.expectEqual(@as(u8, 155), cell.bg.r); + try testing.expectEqual(@as(u8, 155), cell.bg.rgb.r); try testing.expect(!cell.attrs.bold); try testing.expect(s.cursor.pen.attrs.bold); } @@ -5235,8 +5250,7 @@ test "Screen: scrollRegionUp buffer wrap alternate" { // Scroll s.cursor.pen = .{ .char = 'X' }; - s.cursor.pen.bg = .{ .r = 155 }; - s.cursor.pen.attrs.has_bg = true; + s.cursor.pen.bg = .{ .rgb = .{ .r = 155 } }; s.cursor.pen.attrs.bold = true; s.scrollRegionUp(.{ .screen = 0 }, .{ .screen = 2 }, 2); @@ -5246,7 +5260,7 @@ test "Screen: scrollRegionUp buffer wrap alternate" { defer alloc.free(contents); try testing.expectEqualStrings("4ABCD", contents); const cell = s.getCell(.active, 2, 0); - try testing.expectEqual(@as(u8, 155), cell.bg.r); + try testing.expectEqual(@as(u8, 155), cell.bg.rgb.r); try testing.expect(!cell.attrs.bold); try testing.expect(s.cursor.pen.attrs.bold); } @@ -5964,8 +5978,7 @@ test "Screen: resize (no reflow) less rows trims blank lines" { const row = s.getRow(.{ .active = y }); for (0..s.cols) |x| { const cell = row.getCellPtr(x); - cell.*.bg = .{ .r = 0xFF, .g = 0, .b = 0 }; - cell.*.attrs.has_bg = true; + cell.*.bg = .{ .rgb = .{ .r = 0xFF, .g = 0, .b = 0 } }; } } @@ -6000,8 +6013,7 @@ test "Screen: resize (no reflow) more rows trims blank lines" { const row = s.getRow(.{ .active = y }); for (0..s.cols) |x| { const cell = row.getCellPtr(x); - cell.*.bg = .{ .r = 0xFF, .g = 0, .b = 0 }; - cell.*.attrs.has_bg = true; + cell.*.bg = .{ .rgb = .{ .r = 0xFF, .g = 0, .b = 0 } }; } } @@ -6934,7 +6946,7 @@ test "Screen: resize less cols trailing background colors" { const cursor = s.cursor; // Color our cells red - const pen: Cell = .{ .bg = .{ .r = 0xFF }, .attrs = .{ .has_bg = true } }; + const pen: Cell = .{ .bg = .{ .rgb = .{ .r = 0xFF } } }; for (s.cursor.x..s.cols) |x| { const row = s.getRow(.{ .active = s.cursor.y }); const cell = row.getCellPtr(x); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index af58f75da..a486144e3 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -474,8 +474,8 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { switch (attr) { .unset => { - self.screen.cursor.pen.attrs.has_fg = false; - self.screen.cursor.pen.attrs.has_bg = false; + self.screen.cursor.pen.fg = .none; + self.screen.cursor.pen.bg = .none; self.screen.cursor.pen.attrs = .{}; }, @@ -561,55 +561,51 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { }, .direct_color_fg => |rgb| { - self.screen.cursor.pen.attrs.has_fg = true; self.screen.cursor.pen.fg = .{ - .r = rgb.r, - .g = rgb.g, - .b = rgb.b, + .rgb = .{ + .r = rgb.r, + .g = rgb.g, + .b = rgb.b, + }, }; }, .direct_color_bg => |rgb| { - self.screen.cursor.pen.attrs.has_bg = true; self.screen.cursor.pen.bg = .{ - .r = rgb.r, - .g = rgb.g, - .b = rgb.b, + .rgb = .{ + .r = rgb.r, + .g = rgb.g, + .b = rgb.b, + }, }; }, .@"8_fg" => |n| { - self.screen.cursor.pen.attrs.has_fg = true; - self.screen.cursor.pen.fg = self.color_palette.colors[@intFromEnum(n)]; + self.screen.cursor.pen.fg = .{ .indexed = @intFromEnum(n) }; }, .@"8_bg" => |n| { - self.screen.cursor.pen.attrs.has_bg = true; - self.screen.cursor.pen.bg = self.color_palette.colors[@intFromEnum(n)]; + self.screen.cursor.pen.bg = .{ .indexed = @intFromEnum(n) }; }, - .reset_fg => self.screen.cursor.pen.attrs.has_fg = false, + .reset_fg => self.screen.cursor.pen.fg = .none, - .reset_bg => self.screen.cursor.pen.attrs.has_bg = false, + .reset_bg => self.screen.cursor.pen.bg = .none, .@"8_bright_fg" => |n| { - self.screen.cursor.pen.attrs.has_fg = true; - self.screen.cursor.pen.fg = self.color_palette.colors[@intFromEnum(n)]; + self.screen.cursor.pen.fg = .{ .indexed = @intFromEnum(n) }; }, .@"8_bright_bg" => |n| { - self.screen.cursor.pen.attrs.has_bg = true; - self.screen.cursor.pen.bg = self.color_palette.colors[@intFromEnum(n)]; + self.screen.cursor.pen.bg = .{ .indexed = @intFromEnum(n) }; }, .@"256_fg" => |idx| { - self.screen.cursor.pen.attrs.has_fg = true; - self.screen.cursor.pen.fg = self.color_palette.colors[idx]; + self.screen.cursor.pen.fg = .{ .indexed = idx }; }, .@"256_bg" => |idx| { - self.screen.cursor.pen.attrs.has_bg = true; - self.screen.cursor.pen.bg = self.color_palette.colors[idx]; + self.screen.cursor.pen.bg = .{ .indexed = idx }; }, .unknown => return error.InvalidAttribute, @@ -676,12 +672,26 @@ pub fn printAttributes(self: *Terminal, buf: []u8) ![]const u8 { try writer.print(";{c}", .{c}); } - if (pen.attrs.has_fg) { - try writer.print(";38:2::{[r]}:{[g]}:{[b]}", pen.fg); + switch (pen.fg) { + .none => {}, + .indexed => |idx| if (idx >= 16) + try writer.print(";38:5:{}", .{idx}) + else if (idx >= 8) + try writer.print(";9{}", .{idx - 8}) + else + try writer.print(";3{}", .{idx}), + .rgb => |rgb| try writer.print(";38:2::{[r]}:{[g]}:{[b]}", rgb), } - if (pen.attrs.has_bg) { - try writer.print(";48:2::{[r]}:{[g]}:{[b]}", pen.bg); + switch (pen.bg) { + .none => {}, + .indexed => |idx| if (idx >= 16) + try writer.print(";48:5:{}", .{idx}) + else if (idx >= 8) + try writer.print(";10{}", .{idx - 8}) + else + try writer.print(";4{}", .{idx}), + .rgb => |rgb| try writer.print(";48:2::{[r]}:{[g]}:{[b]}", rgb), } return stream.getWritten(); @@ -1080,8 +1090,6 @@ pub fn decaln(self: *Terminal) !void { .bg = self.screen.cursor.pen.bg, .fg = self.screen.cursor.pen.fg, .attrs = .{ - .has_bg = self.screen.cursor.pen.attrs.has_bg, - .has_fg = self.screen.cursor.pen.attrs.has_fg, .protected = self.screen.cursor.pen.attrs.protected, }, }; @@ -1229,9 +1237,9 @@ pub fn eraseDisplay( defer tracy.end(); // Erasing clears all attributes / colors _except_ the background - const pen: Screen.Cell = if (!self.screen.cursor.pen.attrs.has_bg) .{} else .{ - .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = true }, + const pen: Screen.Cell = switch (self.screen.cursor.pen.bg) { + .none => .{}, + else => |bg| .{ .bg = bg }, }; // We respect protected attributes if explicitly requested (probably @@ -1380,9 +1388,9 @@ pub fn eraseLine( defer tracy.end(); // We always fill with the background - const pen: Screen.Cell = if (!self.screen.cursor.pen.attrs.has_bg) .{} else .{ - .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = true }, + const pen: Screen.Cell = switch (self.screen.cursor.pen.bg) { + .none => .{}, + else => |bg| .{ .bg = bg }, }; // Get our start/end positions depending on mode. @@ -1470,7 +1478,6 @@ pub fn deleteChars(self: *Terminal, count: usize) !void { const pen: Screen.Cell = .{ .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, }; // If our X is a wide spacer tail then we need to erase the @@ -1529,7 +1536,6 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void { const pen: Screen.Cell = .{ .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, }; // If we never had a protection mode, then we can assume no cells @@ -1873,7 +1879,6 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { // Insert blanks. The blanks preserve the background color. row.fillSlice(.{ .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, }, start, pivot); } @@ -1939,7 +1944,6 @@ pub fn insertLines(self: *Terminal, count: usize) !void { const row = self.screen.getRow(.{ .active = y }); row.fillSlice(.{ .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, }, self.scrolling_region.left, self.scrolling_region.right + 1); } } @@ -2014,7 +2018,6 @@ pub fn deleteLines(self: *Terminal, count: usize) !void { row.setWrapped(false); row.fillSlice(.{ .bg = self.screen.cursor.pen.bg, - .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, }, self.scrolling_region.left, self.scrolling_region.right + 1); } } @@ -2247,13 +2250,13 @@ test "Terminal: fullReset with a non-empty pen" { var t = try init(testing.allocator, 80, 80); defer t.deinit(testing.allocator); - t.screen.cursor.pen.bg = .{ .r = 0xFF, .g = 0x00, .b = 0x7F }; - t.screen.cursor.pen.fg = .{ .r = 0xFF, .g = 0x00, .b = 0x7F }; + t.screen.cursor.pen.bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x7F } }; + t.screen.cursor.pen.fg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x7F } }; t.fullReset(testing.allocator); const cell = t.screen.getCell(.active, t.screen.cursor.y, t.screen.cursor.x); - try testing.expect(cell.bg.eql(.{})); - try testing.expect(cell.fg.eql(.{})); + try testing.expect(cell.bg == .none); + try testing.expect(cell.fg == .none); } test "Terminal: fullReset origin mode" { @@ -4165,8 +4168,7 @@ test "Terminal: index bottom of primary screen background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; t.setCursorPos(5, 1); @@ -4338,8 +4340,7 @@ test "Terminal: decaln preserves color" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; // Initial value @@ -4443,8 +4444,7 @@ test "Terminal: insertBlanks preserves background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; for ("ABC") |c| try t.print(c); @@ -4836,8 +4836,7 @@ test "Terminal: deleteChars background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; try t.printString("ABC123"); @@ -5001,8 +5000,7 @@ test "Terminal: eraseChars preserves background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; for ("ABC") |c| try t.print(c); @@ -5312,8 +5310,7 @@ test "Terminal: eraseLine right preserves background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; for ("ABCDE") |c| try t.print(c); @@ -5462,8 +5459,7 @@ test "Terminal: eraseLine left preserves background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; for ("ABCDE") |c| try t.print(c); @@ -5578,8 +5574,7 @@ test "Terminal: eraseLine complete preserves background sgr" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; for ("ABCDE") |c| try t.print(c); @@ -5707,8 +5702,7 @@ test "Terminal: eraseDisplay erase below preserves SGR bg" { t.setCursorPos(2, 2); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; t.screen.cursor.pen = pen; @@ -5878,8 +5872,7 @@ test "Terminal: eraseDisplay erase above preserves SGR bg" { t.setCursorPos(2, 2); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; t.screen.cursor.pen = pen; @@ -6018,16 +6011,16 @@ test "Terminal: eraseDisplay above" { const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F }; t.screen.cursor.pen = Screen.Cell{ .char = 'a', - .bg = pink, - .fg = pink, - .attrs = .{ .bold = true, .has_bg = true }, + .bg = .{ .rgb = pink }, + .fg = .{ .rgb = pink }, + .attrs = .{ .bold = true }, }; const cell_ptr = t.screen.getCellPtr(.active, 0, 0); cell_ptr.* = t.screen.cursor.pen; // verify the cell was set var cell = t.screen.getCell(.active, 0, 0); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(pink)); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg.rgb.eql(pink)); try testing.expect(cell.char == 'a'); try testing.expect(cell.attrs.bold); // move the cursor below it @@ -6037,18 +6030,17 @@ test "Terminal: eraseDisplay above" { t.eraseDisplay(testing.allocator, .above, false); // check it was erased cell = t.screen.getCell(.active, 0, 0); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(.{})); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg == .none); try testing.expect(cell.char == 0); try testing.expect(!cell.attrs.bold); - try testing.expect(cell.attrs.has_bg); // Check that our pen hasn't changed try testing.expect(t.screen.cursor.pen.attrs.bold); // check that another cell got the correct bg cell = t.screen.getCell(.active, 0, 1); - try testing.expect(cell.bg.eql(pink)); + try testing.expect(cell.bg.rgb.eql(pink)); } test "Terminal: eraseDisplay below" { @@ -6058,31 +6050,30 @@ test "Terminal: eraseDisplay below" { const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F }; t.screen.cursor.pen = Screen.Cell{ .char = 'a', - .bg = pink, - .fg = pink, - .attrs = .{ .bold = true, .has_bg = true }, + .bg = .{ .rgb = pink }, + .fg = .{ .rgb = pink }, + .attrs = .{ .bold = true }, }; const cell_ptr = t.screen.getCellPtr(.active, 60, 60); cell_ptr.* = t.screen.cursor.pen; // verify the cell was set var cell = t.screen.getCell(.active, 60, 60); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(pink)); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg.rgb.eql(pink)); try testing.expect(cell.char == 'a'); try testing.expect(cell.attrs.bold); // erase below the cursor t.eraseDisplay(testing.allocator, .below, false); // check it was erased cell = t.screen.getCell(.active, 60, 60); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(.{})); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg == .none); try testing.expect(cell.char == 0); try testing.expect(!cell.attrs.bold); - try testing.expect(cell.attrs.has_bg); // check that another cell got the correct bg cell = t.screen.getCell(.active, 0, 1); - try testing.expect(cell.bg.eql(pink)); + try testing.expect(cell.bg.rgb.eql(pink)); } test "Terminal: eraseDisplay complete" { @@ -6092,9 +6083,9 @@ test "Terminal: eraseDisplay complete" { const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F }; t.screen.cursor.pen = Screen.Cell{ .char = 'a', - .bg = pink, - .fg = pink, - .attrs = .{ .bold = true, .has_bg = true }, + .bg = .{ .rgb = pink }, + .fg = .{ .rgb = pink }, + .attrs = .{ .bold = true }, }; var cell_ptr = t.screen.getCellPtr(.active, 60, 60); cell_ptr.* = t.screen.cursor.pen; @@ -6102,14 +6093,14 @@ test "Terminal: eraseDisplay complete" { cell_ptr.* = t.screen.cursor.pen; // verify the cell was set var cell = t.screen.getCell(.active, 60, 60); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(pink)); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg.rgb.eql(pink)); try testing.expect(cell.char == 'a'); try testing.expect(cell.attrs.bold); // verify the cell was set cell = t.screen.getCell(.active, 0, 0); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(pink)); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg.rgb.eql(pink)); try testing.expect(cell.char == 'a'); try testing.expect(cell.attrs.bold); // position our cursor between the cells @@ -6118,17 +6109,15 @@ test "Terminal: eraseDisplay complete" { t.eraseDisplay(testing.allocator, .complete, false); // check they were erased cell = t.screen.getCell(.active, 60, 60); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(.{})); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg == .none); try testing.expect(cell.char == 0); try testing.expect(!cell.attrs.bold); - try testing.expect(cell.attrs.has_bg); cell = t.screen.getCell(.active, 0, 0); - try testing.expect(cell.bg.eql(pink)); - try testing.expect(cell.fg.eql(.{})); + try testing.expect(cell.bg.rgb.eql(pink)); + try testing.expect(cell.fg == .none); try testing.expect(cell.char == 0); try testing.expect(!cell.attrs.bold); - try testing.expect(cell.attrs.has_bg); } test "Terminal: eraseDisplay protected complete" { @@ -7041,8 +7030,7 @@ test "Terminal: DECCOLM preserves SGR bg" { defer t.deinit(alloc); const pen: Screen.Cell = .{ - .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, - .attrs = .{ .has_bg = true }, + .bg = .{ .rgb = .{ .r = 0xFF, .g = 0x00, .b = 0x00 } }, }; t.screen.cursor.pen = pen;