terminal/new: introduce content tags and bg color cells

This commit is contained in:
Mitchell Hashimoto
2024-02-25 19:58:08 -08:00
parent e7bf9dc53c
commit e114a106f1
3 changed files with 165 additions and 92 deletions

View File

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

View File

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

View File

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