Merge pull request #1202 from gpanders/cell-color-refactor

terminal: track palette color in cell state
This commit is contained in:
Mitchell Hashimoto
2024-01-02 20:58:32 -08:00
committed by GitHub
8 changed files with 320 additions and 241 deletions

View File

@ -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("=");

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;