Merge pull request #608 from mitchellh/mode-2027

terminal: mode 2027
This commit is contained in:
Mitchell Hashimoto
2023-10-02 09:35:02 -07:00
committed by GitHub
2 changed files with 54 additions and 15 deletions

View File

@ -592,25 +592,27 @@ pub fn print(self: *Terminal, c: u21) !void {
// If we're not on the main display, do nothing for now
if (self.status_display != .main) return;
// Get the previous cell so we can detect grapheme clusters. We only
// do this if c is outside of Latin-1 because characters in the Latin-1
// range cannot possibly be grapheme joiners. This helps keep non-graphemes
// extremely fast and we take this much slower path for graphemes. No hate
// on graphemes, I'd love to make them much faster, but I wanted to focus
// on correctness first.
//
// NOTE: This is disabled because no shells handle this correctly. We'll
// need to work with shells and other emulators to probably figure out
// a way to support this. In the mean time, I'm going to keep all the
// grapheme detection and keep it up to date so we're ready to go.
if (false and c > 255 and self.screen.cursor.x > 0) {
// TODO: test this!
// Perform grapheme clustering if grapheme support is enabled (mode 2027).
// This is MUCH slower than the normal path so the conditional below is
// purposely ordered in least-likely to most-likely so we can drop out
// as quickly as possible.
if (c > 255 and
self.modes.get(.grapheme_cluster) and
self.screen.cursor.x > 0)
{
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
// We need the previous cell to determine if we're at a grapheme
// break or not. If we are NOT, then we are still combining the
// same grapheme. Otherwise, we can stay in this cell.
const Prev = struct { cell: *Screen.Cell, x: usize };
const prev: Prev = prev: {
const x = self.screen.cursor.x - 1;
const immediate = row.getCellPtr(x);
// If the previous cell is a wide spacer tail, then we actually
// want to use the cell before that because that has the actual
// content.
if (!immediate.attrs.wide_spacer_tail) break :prev .{
.cell = immediate,
.x = x,
@ -1871,7 +1873,7 @@ test "Terminal: print over wide char at 0,0" {
try testing.expectEqual(@as(usize, 1), t.screen.cursor.x);
}
test "Terminal: print multicodepoint grapheme" {
test "Terminal: print multicodepoint grapheme, disabled mode 2027" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
@ -1926,6 +1928,42 @@ test "Terminal: print multicodepoint grapheme" {
}
}
test "Terminal: print multicodepoint grapheme, mode 2027" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
// Enable grapheme clustering
t.modes.set(.grapheme_cluster, true);
// 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 2 cells taken up. It is one character but "wide".
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 2), 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, 5), 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));
}
}
test "Terminal: soft wrap" {
var t = try init(testing.allocator, 3, 80);
defer t.deinit(testing.allocator);

View File

@ -172,6 +172,7 @@ const entries: []const ModeEntry = &.{
.{ .name = "alt_screen_save_cursor_clear_enter", .value = 1049 },
.{ .name = "bracketed_paste", .value = 2004 },
.{ .name = "synchronized_output", .value = 2026 },
.{ .name = "grapheme_cluster", .value = 2027 },
};
test {