From eb3afae57eae06b00189096b7718afed7d0a7aa7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 23 Feb 2024 22:26:59 -0800 Subject: [PATCH] terminal/new: clear graphemes on overwrite --- src/terminal/new/Terminal.zig | 36 ++++++++++++++++++++++-- src/terminal/new/page.zig | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index b30440931..ad423921f 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -513,10 +513,15 @@ fn printCell( } // If the prior value had graphemes, clear those - if (cell.grapheme) @panic("TODO: clear graphemes"); + if (cell.grapheme) { + self.screen.cursor.page_offset.page.data.clearGrapheme( + self.screen.cursor.page_row, + cell, + ); + } // Write - self.screen.cursor.page_cell.* = .{ + cell.* = .{ .style_id = self.screen.cursor.style_id, .codepoint = c, .wide = wide, @@ -1362,6 +1367,33 @@ test "Terminal: print invalid VS16 with second char" { } } +test "Terminal: overwrite grapheme should clear grapheme data" { + var t = try init(testing.allocator, 5, 5); + defer t.deinit(testing.allocator); + + // Enable grapheme clustering + t.modes.set(.grapheme_cluster, true); + + try t.print(0x26C8); // Thunder cloud and rain + try t.print(0xFE0E); // VS15 to make narrow + t.setCursorPos(1, 1); + try t.print('A'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A", str); + } + + { + 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(Cell.Wide.narrow, cell.wide); + } +} + test "Terminal: soft wrap" { var t = try init(testing.allocator, 3, 80); defer t.deinit(testing.allocator); diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig index e460553dd..95456f67b 100644 --- a/src/terminal/new/page.zig +++ b/src/terminal/new/page.zig @@ -268,6 +268,30 @@ pub const Page = struct { return slice.offset.ptr(self.memory)[0..slice.len]; } + /// Clear the graphemes for a given cell. + pub fn clearGrapheme(self: *Page, row: *Row, cell: *Cell) void { + assert(cell.grapheme); + + // Get our entry in the map, which must exist + const cell_offset = getOffset(Cell, self.memory, cell); + var map = self.grapheme_map.map(self.memory); + const entry = map.getEntry(cell_offset).?; + + // Free our grapheme data + const cps = entry.value_ptr.offset.ptr(self.memory)[0..entry.value_ptr.len]; + self.grapheme_alloc.free(self.memory, cps); + + // Remove the entry + map.removeByPtr(entry.key_ptr); + + // Mark that we no longer have graphemes, also search the row + // to make sure its state is correct. + cell.grapheme = false; + const cells = row.cells.ptr(self.memory)[0..self.size.cols]; + for (cells) |c| if (c.grapheme) return; + row.grapheme = false; + } + pub const Layout = struct { total_size: usize, rows_start: usize, @@ -577,6 +601,11 @@ test "Page appendGrapheme small" { try testing.expect(rac.row.grapheme); try testing.expect(rac.cell.grapheme); 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); } test "Page appendGrapheme larger than chunk" { @@ -601,3 +630,26 @@ test "Page appendGrapheme larger than chunk" { try testing.expectEqual(@as(u21, @intCast(0x0A + i)), cps[i]); } } + +test "Page clearGrapheme not all cells" { + var page = try Page.init(.{ + .cols = 10, + .rows = 10, + .styles = 8, + }); + defer page.deinit(); + + const rac = page.getRowAndCell(0, 0); + rac.cell.codepoint = 0x09; + try page.appendGrapheme(rac.row, rac.cell, 0x0A); + + const rac2 = page.getRowAndCell(1, 0); + rac2.cell.codepoint = 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); +}