From 6a4b25714a0752ab285212d25dc6ad0e807d4d34 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 15 Aug 2023 13:55:35 -0700 Subject: [PATCH] terminal: add a test to verify our grapheme state is what we expect --- src/terminal/Screen.zig | 21 ++++++++++++++- src/terminal/Terminal.zig | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index d13e51e42..412b96ff9 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -489,12 +489,19 @@ pub const Row = struct { return .{ .row = self }; } + /// Returns the number of codepoints in the cell at column x, + /// including the primary codepoint. + pub fn codepointLen(self: Row, x: usize) usize { + var it = self.codepointIterator(x); + return it.len() + 1; + } + /// Read-only iterator for the grapheme codepoints in a cell. This only /// iterates over the EXTRA GRAPHEME codepoints and not the primary /// codepoint in cell.char. pub fn codepointIterator(self: Row, x: usize) CodepointIterator { const cell = &self.storage[x + 1].cell; - assert(cell.attrs.grapheme); + if (!cell.attrs.grapheme) return .{ .data = .{ .zero = {} } }; const key = self.getId() + x + 1; const data: GraphemeData = self.screen.graphemes.get(key) orelse data: { @@ -543,6 +550,18 @@ pub const CodepointIterator = struct { data: GraphemeData, i: usize = 0, + /// Returns the number of codepoints in the iterator. + pub fn len(self: CodepointIterator) usize { + switch (self.data) { + .zero => return 0, + .one => return 1, + .two => return 2, + .three => return 3, + .four => return 4, + .many => |v| return v.len, + } + } + pub fn next(self: *CodepointIterator) ?u21 { switch (self.data) { .zero => return null, diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index ff9b0c8f3..c9a5ae24c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1630,6 +1630,61 @@ test "Terminal: print over wide char at 0,0" { try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); } +test "Terminal: print multicodepoint grapheme" { + var t = try init(testing.allocator, 80, 80); + defer t.deinit(testing.allocator); + + // https://github.com/mitchellh/ghostty/issues/289 + // This is: 👨‍👩‍👧 (which may or may not render correctly) + try t.print(0x1F468); + try t.print(0x200D); + try t.print(0x1F469); + try t.print(0x200D); + try t.print(0x1F467); + + // We should have 6 cells taken up + try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 6), t.screen.cursor.x); + + // Assert various properties about our screen to verify + // we have all expected cells. + const row = t.screen.getRow(.{ .screen = 0 }); + { + const cell = row.getCell(0); + try testing.expectEqual(@as(u32, 0x1F468), cell.char); + try testing.expect(cell.attrs.wide); + try testing.expectEqual(@as(usize, 2), row.codepointLen(0)); + } + { + const cell = row.getCell(1); + try testing.expectEqual(@as(u32, ' '), cell.char); + try testing.expect(cell.attrs.wide_spacer_tail); + try testing.expectEqual(@as(usize, 1), row.codepointLen(1)); + } + { + const cell = row.getCell(2); + try testing.expectEqual(@as(u32, 0x1F469), cell.char); + try testing.expect(cell.attrs.wide); + try testing.expectEqual(@as(usize, 2), row.codepointLen(2)); + } + { + const cell = row.getCell(3); + try testing.expectEqual(@as(u32, ' '), cell.char); + try testing.expect(cell.attrs.wide_spacer_tail); + } + { + const cell = row.getCell(4); + try testing.expectEqual(@as(u32, 0x1F467), cell.char); + try testing.expect(cell.attrs.wide); + try testing.expectEqual(@as(usize, 1), row.codepointLen(4)); + } + { + const cell = row.getCell(5); + try testing.expectEqual(@as(u32, ' '), cell.char); + try testing.expect(cell.attrs.wide_spacer_tail); + } +} + test "Terminal: soft wrap" { var t = try init(testing.allocator, 3, 80); defer t.deinit(testing.allocator);