From e114a106f1e2952c5c4e790918ea6e70d9b1688f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 25 Feb 2024 19:58:08 -0800 Subject: [PATCH] terminal/new: introduce content tags and bg color cells --- src/terminal/new/Screen.zig | 26 +++++--- src/terminal/new/Terminal.zig | 119 ++++++++++++++++++---------------- src/terminal/new/page.zig | 112 ++++++++++++++++++++++++-------- 3 files changed, 165 insertions(+), 92 deletions(-) diff --git a/src/terminal/new/Screen.zig b/src/terminal/new/Screen.zig index e9fe40b14..54a5e726c 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -251,7 +251,7 @@ pub fn dumpString( break :cells cells[0..self.pages.cols]; }; - if (!pagepkg.Cell.hasText(cells)) { + if (!pagepkg.Cell.hasTextAny(cells)) { blank_rows += 1; continue; } @@ -274,7 +274,7 @@ pub fn dumpString( // If we have a zero value, then we accumulate a counter. We // only want to turn zero values into spaces if we have a non-zero // char sometime later. - if (cell.codepoint == 0) { + if (!cell.hasText()) { blank_cells += 1; continue; } @@ -283,13 +283,20 @@ pub fn dumpString( blank_cells = 0; } - try writer.print("{u}", .{cell.codepoint}); + switch (cell.content_tag) { + .codepoint => { + try writer.print("{u}", .{cell.content.codepoint}); + }, - if (cell.grapheme) { - const cps = row_offset.page.data.lookupGrapheme(cell).?; - for (cps) |cp| { - try writer.print("{u}", .{cp}); - } + .codepoint_grapheme => { + try writer.print("{u}", .{cell.content.codepoint}); + const cps = row_offset.page.data.lookupGrapheme(cell).?; + for (cps) |cp| { + try writer.print("{u}", .{cp}); + } + }, + + else => unreachable, } } } @@ -322,7 +329,8 @@ fn testWriteString(self: *Screen, text: []const u8) !void { assert(width == 1 or width == 2); switch (width) { 1 => { - self.cursor.page_cell.codepoint = c; + self.cursor.page_cell.content_tag = .codepoint; + self.cursor.page_cell.content = .{ .codepoint = c }; self.cursor.x += 1; if (self.cursor.x < self.pages.cols) { const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell); diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index 31dc76188..334680806 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -245,7 +245,7 @@ pub fn print(self: *Terminal, c: u21) !void { // column. Otherwise, we need to check if there is text to // figure out if we're attaching to the prev or current. if (self.screen.cursor.x != right_limit - 1) break :left 1; - break :left @intFromBool(self.screen.cursor.page_cell.codepoint == 0); + break :left @intFromBool(!self.screen.cursor.page_cell.hasText()); }; // If the previous cell is a wide spacer tail, then we actually @@ -263,12 +263,12 @@ pub fn print(self: *Terminal, c: u21) !void { // If our cell has no content, then this is a new cell and // necessarily a grapheme break. - if (prev.cell.codepoint == 0) break :grapheme; + if (!prev.cell.hasText()) break :grapheme; const grapheme_break = brk: { var state: unicode.GraphemeBreakState = .{}; - var cp1: u21 = prev.cell.codepoint; - if (prev.cell.grapheme) { + var cp1: u21 = prev.cell.content.codepoint; + if (prev.cell.hasGrapheme()) { const cps = self.screen.cursor.page_offset.page.data.lookupGrapheme(prev.cell).?; for (cps) |cp2| { // log.debug("cp1={x} cp2={x}", .{ cp1, cp2 }); @@ -289,7 +289,7 @@ pub fn print(self: *Terminal, c: u21) !void { // VS15 makes it narrow. if (c == 0xFE0F or c == 0xFE0E) { // This only applies to emoji - const prev_props = unicode.getProperties(prev.cell.codepoint); + const prev_props = unicode.getProperties(prev.cell.content.codepoint); const emoji = prev_props.grapheme_boundary_class == .extended_pictographic; if (!emoji) return; @@ -310,7 +310,7 @@ pub fn print(self: *Terminal, c: u21) !void { try self.printWrap(); } - self.printCell(prev.cell.codepoint, .wide); + self.printCell(prev.cell.content.codepoint, .wide); // Write our spacer self.screen.cursorRight(1); @@ -385,9 +385,15 @@ pub fn print(self: *Terminal, c: u21) !void { break :prev self.screen.cursorCellLeft(2); }; + // If our previous cell has no text, just ignore the zero-width character + if (!prev.hasText()) { + log.warn("zero-width character with no prior character, ignoring", .{}); + return; + } + // If this is a emoji variation selector, prev must be an emoji if (c == 0xFE0F or c == 0xFE0E) { - const prev_props = unicode.getProperties(prev.codepoint); + const prev_props = unicode.getProperties(prev.content.codepoint); const emoji = prev_props.grapheme_boundary_class == .extended_pictographic; if (!emoji) return; } @@ -513,7 +519,7 @@ fn printCell( } // If the prior value had graphemes, clear those - if (cell.grapheme) { + if (cell.hasGrapheme()) { self.screen.cursor.page_offset.page.data.clearGrapheme( self.screen.cursor.page_row, cell, @@ -522,8 +528,9 @@ fn printCell( // Write cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = c }, .style_id = self.screen.cursor.style_id, - .codepoint = c, .wide = wide, }; @@ -1055,7 +1062,7 @@ test "Terminal: print wide char" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x1F600), cell.codepoint); + try testing.expectEqual(@as(u21, 0x1F600), cell.content.codepoint); try testing.expectEqual(Cell.Wide.wide, cell.wide); } { @@ -1077,7 +1084,7 @@ test "Terminal: print wide char in single-width terminal" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, ' '), cell.codepoint); + try testing.expectEqual(@as(u21, ' '), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1096,13 +1103,13 @@ test "Terminal: print over wide char at 0,0" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'A'), cell.codepoint); + try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0), cell.codepoint); + try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1118,13 +1125,13 @@ test "Terminal: print over wide spacer tail" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0), cell.codepoint); + try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'X'), cell.codepoint); + try testing.expectEqual(@as(u21, 'X'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } @@ -1156,8 +1163,8 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x1F468), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1165,16 +1172,16 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, ' '), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, ' '), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x1F469), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x1F469), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1182,24 +1189,24 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, ' '), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, ' '), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x1F467), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, 0x1F467), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 5, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, ' '), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, ' '), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); } @@ -1224,8 +1231,8 @@ test "Terminal: VS16 doesn't make character with 2027 disabled" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x2764), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1249,14 +1256,14 @@ test "Terminal: print invalid VS16 non-grapheme" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'x'), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, 'x'), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0), cell.codepoint); + try testing.expectEqual(@as(u21, 0), cell.content.codepoint); } } @@ -1284,8 +1291,8 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x1F468), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 4), cps.len); @@ -1293,8 +1300,8 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, ' '), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, ' '), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); } } @@ -1318,8 +1325,8 @@ test "Terminal: VS15 to make narrow character" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x26C8), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x26C8), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1345,8 +1352,8 @@ test "Terminal: VS16 to make wide character with mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x2764), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1374,8 +1381,8 @@ test "Terminal: VS16 repeated with mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x2764), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1383,8 +1390,8 @@ test "Terminal: VS16 repeated with mode 2027" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0x2764), cell.codepoint); - try testing.expect(cell.grapheme); + try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); + try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); const cps = list_cell.page.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); @@ -1411,14 +1418,14 @@ test "Terminal: print invalid VS16 grapheme" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'x'), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, 'x'), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0), cell.codepoint); + try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1444,16 +1451,16 @@ test "Terminal: print invalid VS16 with second char" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'x'), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, 'x'), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'y'), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, 'y'), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1479,8 +1486,8 @@ test "Terminal: overwrite grapheme should clear grapheme data" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'A'), cell.codepoint); - try testing.expect(!cell.grapheme); + try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); + try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1560,7 +1567,7 @@ test "Terminal: disabled wraparound with wide char and one space" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 0), cell.codepoint); + try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1587,7 +1594,7 @@ test "Terminal: disabled wraparound with wide char and no space" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, 'A'), cell.codepoint); + try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } @@ -1616,7 +1623,7 @@ test "Terminal: disabled wraparound with wide grapheme and half space" { { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; - try testing.expectEqual(@as(u21, '❤'), cell.codepoint); + try testing.expectEqual(@as(u21, '❤'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } } diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig index 95456f67b..03e4e26d1 100644 --- a/src/terminal/new/page.zig +++ b/src/terminal/new/page.zig @@ -204,12 +204,14 @@ pub const Page = struct { /// Append a codepoint to the given cell as a grapheme. pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) !void { + if (comptime std.debug.runtime_safety) assert(cell.hasText()); + const cell_offset = getOffset(Cell, self.memory, cell); var map = self.grapheme_map.map(self.memory); // If this cell has no graphemes, we can go faster by knowing we // need to allocate a new grapheme slice and update the map. - if (!cell.grapheme) { + if (cell.content_tag != .codepoint_grapheme) { const cps = try self.grapheme_alloc.alloc(u21, self.memory, 1); errdefer self.grapheme_alloc.free(self.memory, cps); cps[0] = cp; @@ -220,7 +222,7 @@ pub const Page = struct { }); errdefer map.remove(cell_offset); - cell.grapheme = true; + cell.content_tag = .codepoint_grapheme; row.grapheme = true; return; @@ -270,7 +272,7 @@ pub const Page = struct { /// Clear the graphemes for a given cell. pub fn clearGrapheme(self: *Page, row: *Row, cell: *Cell) void { - assert(cell.grapheme); + if (comptime std.debug.runtime_safety) assert(cell.hasGrapheme()); // Get our entry in the map, which must exist const cell_offset = getOffset(Cell, self.memory, cell); @@ -286,9 +288,9 @@ pub const Page = struct { // Mark that we no longer have graphemes, also search the row // to make sure its state is correct. - cell.grapheme = false; + cell.content_tag = .codepoint; const cells = row.cells.ptr(self.memory)[0..self.size.cols]; - for (cells) |c| if (c.grapheme) return; + for (cells) |c| if (c.hasGrapheme()) return; row.grapheme = false; } @@ -444,26 +446,55 @@ pub const Row = packed struct(u64) { /// The zero value of this struct must be a valid cell representing empty, /// since we zero initialize the backing memory for a page. pub const Cell = packed struct(u64) { - /// The codepoint that this cell contains. If `grapheme` is false, - /// then this is the only codepoint in the cell. If `grapheme` is - /// true, then this is the first codepoint in the grapheme cluster. - codepoint: u21 = 0, + /// The content tag dictates the active tag in content and possibly + /// some other behaviors. + content_tag: ContentTag = .codepoint, + + /// The content of the cell. This is a union based on content_tag. + content: packed union { + /// The codepoint that this cell contains. If `grapheme` is false, + /// then this is the only codepoint in the cell. If `grapheme` is + /// true, then this is the first codepoint in the grapheme cluster. + codepoint: u21, + + /// The content is an empty cell with a background color. + color_palette: u8, + color_rgb: RGB, + } = .{ .codepoint = 0 }, /// The style ID to use for this cell within the style map. Zero /// is always the default style so no lookup is required. style_id: style.Id = 0, - /// This is true if there are additional codepoints in the grapheme - /// map for this cell to build a multi-codepoint grapheme. - grapheme: bool = false, - /// The wide property of this cell, for wide characters. Characters in /// a terminal grid can only be 1 or 2 cells wide. A wide character /// is always next to a spacer. This is used to determine both the width /// and spacer properties of a cell. wide: Wide = .narrow, - _padding: u24 = 0, + _padding: u20 = 0, + + pub const ContentTag = enum(u2) { + /// A single codepoint, could be zero to be empty cell. + codepoint = 0, + + /// A codepoint that is part of a multi-codepoint grapheme cluster. + /// The codepoint tag is active in content, but also expect more + /// codepoints in the grapheme data. + codepoint_grapheme = 1, + + /// The cell has no text but only a background color. This is an + /// optimization so that cells with only backgrounds don't take up + /// style map space and also don't require a style map lookup. + bg_color_palette = 2, + bg_color_rgb = 3, + }; + + pub const RGB = packed struct { + r: u8, + g: u8, + b: u8, + }; pub const Wide = enum(u2) { /// Not a wide character, cell width 1. @@ -480,10 +511,34 @@ pub const Cell = packed struct(u64) { spacer_head = 3, }; + /// Helper to make a cell that just has a codepoint. + pub fn init(codepoint: u21) Cell { + return .{ + .content_tag = .codepoint, + .content = .{ .codepoint = codepoint }, + }; + } + + pub fn hasText(self: Cell) bool { + return switch (self.content_tag) { + .codepoint, + .codepoint_grapheme, + => self.content.codepoint != 0, + + .bg_color_palette, + .bg_color_rgb, + => false, + }; + } + + pub fn hasGrapheme(self: Cell) bool { + return self.content_tag == .codepoint_grapheme; + } + /// Returns true if the set of cells has text in it. - pub fn hasText(cells: []const Cell) bool { + pub fn hasTextAny(cells: []const Cell) bool { for (cells) |cell| { - if (cell.codepoint != 0) return true; + if (cell.hasText()) return true; } return false; @@ -569,13 +624,16 @@ test "Page read and write cells" { for (0..page.capacity.rows) |y| { const rac = page.getRowAndCell(1, y); - rac.cell.codepoint = @intCast(y); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = @intCast(y) }, + }; } // Read it again for (0..page.capacity.rows) |y| { const rac = page.getRowAndCell(1, y); - try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.codepoint); + try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.content.codepoint); } } @@ -588,24 +646,24 @@ test "Page appendGrapheme small" { defer page.deinit(); const rac = page.getRowAndCell(0, 0); - rac.cell.codepoint = 0x09; + rac.cell.* = Cell.init(0x09); // One try page.appendGrapheme(rac.row, rac.cell, 0x0A); try testing.expect(rac.row.grapheme); - try testing.expect(rac.cell.grapheme); + try testing.expect(rac.cell.hasGrapheme()); try testing.expectEqualSlices(u21, &.{0x0A}, page.lookupGrapheme(rac.cell).?); // Two try page.appendGrapheme(rac.row, rac.cell, 0x0B); try testing.expect(rac.row.grapheme); - try testing.expect(rac.cell.grapheme); + try testing.expect(rac.cell.hasGrapheme()); try testing.expectEqualSlices(u21, &.{ 0x0A, 0x0B }, page.lookupGrapheme(rac.cell).?); // Clear it page.clearGrapheme(rac.row, rac.cell); try testing.expect(!rac.row.grapheme); - try testing.expect(!rac.cell.grapheme); + try testing.expect(!rac.cell.hasGrapheme()); } test "Page appendGrapheme larger than chunk" { @@ -617,7 +675,7 @@ test "Page appendGrapheme larger than chunk" { defer page.deinit(); const rac = page.getRowAndCell(0, 0); - rac.cell.codepoint = 0x09; + rac.cell.* = Cell.init(0x09); const count = grapheme_chunk_len * 10; for (0..count) |i| { @@ -640,16 +698,16 @@ test "Page clearGrapheme not all cells" { defer page.deinit(); const rac = page.getRowAndCell(0, 0); - rac.cell.codepoint = 0x09; + rac.cell.* = Cell.init(0x09); try page.appendGrapheme(rac.row, rac.cell, 0x0A); const rac2 = page.getRowAndCell(1, 0); - rac2.cell.codepoint = 0x09; + rac2.cell.* = Cell.init(0x09); try page.appendGrapheme(rac2.row, rac2.cell, 0x0A); // Clear it page.clearGrapheme(rac.row, rac.cell); try testing.expect(rac.row.grapheme); - try testing.expect(!rac.cell.grapheme); - try testing.expect(rac2.cell.grapheme); + try testing.expect(!rac.cell.hasGrapheme()); + try testing.expect(rac2.cell.hasGrapheme()); }