mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
terminal/new: introduce content tags and bg color cells
This commit is contained in:
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
Reference in New Issue
Block a user