|
|
|
|
@@ -329,12 +329,16 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|
|
|
|
@branchHint(.unlikely);
|
|
|
|
|
// 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.
|
|
|
|
|
// same grapheme, and will be appending to prev.cell. Otherwise, we are
|
|
|
|
|
// in a new cell.
|
|
|
|
|
const Prev = struct { cell: *Cell, left: size.CellCountInt };
|
|
|
|
|
const prev: Prev = prev: {
|
|
|
|
|
var prev: Prev = prev: {
|
|
|
|
|
const left: size.CellCountInt = left: {
|
|
|
|
|
// If we have wraparound, then we always use the prev col
|
|
|
|
|
if (self.modes.get(.wraparound)) break :left 1;
|
|
|
|
|
// If we have wraparound, then we use the prev col unless
|
|
|
|
|
// there's a pending wrap, in which case we use the current.
|
|
|
|
|
if (self.modes.get(.wraparound)) {
|
|
|
|
|
break :left @intFromBool(!self.screens.active.cursor.pending_wrap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we do not have wraparound, the logic is trickier. If
|
|
|
|
|
// we're not on the last column, then we just use the previous
|
|
|
|
|
@@ -380,6 +384,8 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|
|
|
|
// If we can NOT break, this means that "c" is part of a grapheme
|
|
|
|
|
// with the previous char.
|
|
|
|
|
if (!grapheme_break) {
|
|
|
|
|
var desired_wide: enum { no_change, wide, narrow } = .no_change;
|
|
|
|
|
|
|
|
|
|
// If this is an emoji variation selector then we need to modify
|
|
|
|
|
// the cell width accordingly. VS16 makes the character wide and
|
|
|
|
|
// VS15 makes it narrow.
|
|
|
|
|
@@ -390,71 +396,132 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|
|
|
|
if (!prev_props.emoji_vs_base) return;
|
|
|
|
|
|
|
|
|
|
switch (c) {
|
|
|
|
|
0xFE0F => wide: {
|
|
|
|
|
if (prev.cell.wide == .wide) break :wide;
|
|
|
|
|
0xFE0F => desired_wide = .wide,
|
|
|
|
|
0xFE0E => desired_wide = .narrow,
|
|
|
|
|
else => unreachable,
|
|
|
|
|
}
|
|
|
|
|
} else if (!unicode.table.get(c).width_zero_in_grapheme) {
|
|
|
|
|
// If we have a code point that contributes to the width of a
|
|
|
|
|
// grapheme, it necessarily means that we're at least at width
|
|
|
|
|
// 2, since the first code point must be at least width 1 to
|
|
|
|
|
// start. (Note that Prepend code points could effectively mean
|
|
|
|
|
// the first code point should be width 0, but we don't handle
|
|
|
|
|
// that yet.)
|
|
|
|
|
desired_wide = .wide;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Move our cursor back to the previous. We'll move
|
|
|
|
|
// the cursor within this block to the proper location.
|
|
|
|
|
self.screens.active.cursorLeft(prev.left);
|
|
|
|
|
switch (desired_wide) {
|
|
|
|
|
.wide => wide: {
|
|
|
|
|
if (prev.cell.wide == .wide) break :wide;
|
|
|
|
|
|
|
|
|
|
// If we don't have space for the wide char, we need
|
|
|
|
|
// to insert spacers and wrap. Then we just print the wide
|
|
|
|
|
// char as normal.
|
|
|
|
|
if (self.screens.active.cursor.x == right_limit - 1) {
|
|
|
|
|
if (!self.modes.get(.wraparound)) return;
|
|
|
|
|
// Move our cursor back to the previous. We'll move
|
|
|
|
|
// the cursor within this block to the proper location.
|
|
|
|
|
self.screens.active.cursorLeft(prev.left);
|
|
|
|
|
|
|
|
|
|
// If we don't have space for the wide char, we need to
|
|
|
|
|
// insert spacers and wrap. We need special handling if the
|
|
|
|
|
// previous cell has grapheme data.
|
|
|
|
|
if (self.screens.active.cursor.x == right_limit - 1) {
|
|
|
|
|
if (!self.modes.get(.wraparound)) return;
|
|
|
|
|
|
|
|
|
|
const prev_cp = prev.cell.content.codepoint;
|
|
|
|
|
|
|
|
|
|
if (prev.cell.hasGrapheme()) {
|
|
|
|
|
// This is like printCell but without clearing the
|
|
|
|
|
// grapheme data from the cell, so we can move it
|
|
|
|
|
// later.
|
|
|
|
|
prev.cell.wide = if (right_limit == self.cols) .spacer_head else .narrow;
|
|
|
|
|
prev.cell.content.codepoint = 0;
|
|
|
|
|
|
|
|
|
|
try self.printWrap();
|
|
|
|
|
self.printCell(prev_cp, .wide);
|
|
|
|
|
|
|
|
|
|
const new_pin = self.screens.active.cursor.page_pin.*;
|
|
|
|
|
const new_rac = new_pin.rowAndCell();
|
|
|
|
|
|
|
|
|
|
transfer_graphemes: {
|
|
|
|
|
var old_pin = self.screens.active.cursor.page_pin.up(1) orelse break :transfer_graphemes;
|
|
|
|
|
old_pin.x = right_limit - 1;
|
|
|
|
|
const old_rac = old_pin.rowAndCell();
|
|
|
|
|
|
|
|
|
|
if (new_pin.node == old_pin.node) {
|
|
|
|
|
new_pin.node.data.moveGrapheme(prev.cell, new_rac.cell);
|
|
|
|
|
prev.cell.content_tag = .codepoint;
|
|
|
|
|
new_rac.cell.content_tag = .codepoint_grapheme;
|
|
|
|
|
new_rac.row.grapheme = true;
|
|
|
|
|
} else {
|
|
|
|
|
const cps = old_pin.node.data.lookupGrapheme(old_rac.cell).?;
|
|
|
|
|
for (cps) |cp| {
|
|
|
|
|
try self.screens.active.appendGrapheme(new_rac.cell, cp);
|
|
|
|
|
}
|
|
|
|
|
old_pin.node.data.clearGrapheme(old_rac.cell);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
old_pin.node.data.updateRowGraphemeFlag(old_rac.row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Point prev.cell to our new previous cell that
|
|
|
|
|
// we'll be appending graphemes to
|
|
|
|
|
prev.cell = new_rac.cell;
|
|
|
|
|
} else {
|
|
|
|
|
self.printCell(
|
|
|
|
|
0,
|
|
|
|
|
if (right_limit == self.cols) .spacer_head else .narrow,
|
|
|
|
|
);
|
|
|
|
|
try self.printWrap();
|
|
|
|
|
self.printCell(prev_cp, .wide);
|
|
|
|
|
|
|
|
|
|
// Point prev.cell to our new previous cell that
|
|
|
|
|
// we'll be appending graphemes to
|
|
|
|
|
prev.cell = self.screens.active.cursor.page_cell;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
prev.cell.wide = .wide;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.printCell(prev.cell.content.codepoint, .wide);
|
|
|
|
|
// Write our spacer, since prev.cell is now wide
|
|
|
|
|
self.screens.active.cursorRight(1);
|
|
|
|
|
self.printCell(0, .spacer_tail);
|
|
|
|
|
|
|
|
|
|
// Write our spacer
|
|
|
|
|
// Move the cursor again so we're beyond our spacer
|
|
|
|
|
if (self.screens.active.cursor.x == right_limit - 1) {
|
|
|
|
|
self.screens.active.cursor.pending_wrap = true;
|
|
|
|
|
} else {
|
|
|
|
|
self.screens.active.cursorRight(1);
|
|
|
|
|
self.printCell(0, .spacer_tail);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Move the cursor again so we're beyond our spacer
|
|
|
|
|
if (self.screens.active.cursor.x == right_limit - 1) {
|
|
|
|
|
self.screens.active.cursor.pending_wrap = true;
|
|
|
|
|
} else {
|
|
|
|
|
self.screens.active.cursorRight(1);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
.narrow => narrow: {
|
|
|
|
|
// Prev cell is no longer wide
|
|
|
|
|
if (prev.cell.wide != .wide) break :narrow;
|
|
|
|
|
prev.cell.wide = .narrow;
|
|
|
|
|
|
|
|
|
|
0xFE0E => narrow: {
|
|
|
|
|
// Prev cell is no longer wide
|
|
|
|
|
if (prev.cell.wide != .wide) break :narrow;
|
|
|
|
|
prev.cell.wide = .narrow;
|
|
|
|
|
// Remove the wide spacer tail
|
|
|
|
|
const cell = self.screens.active.cursorCellLeft(prev.left - 1);
|
|
|
|
|
cell.wide = .narrow;
|
|
|
|
|
|
|
|
|
|
// Remove the wide spacer tail
|
|
|
|
|
const cell = self.screens.active.cursorCellLeft(prev.left - 1);
|
|
|
|
|
cell.wide = .narrow;
|
|
|
|
|
// Back track the cursor so that we don't end up with
|
|
|
|
|
// an extra space after the character. Since xterm is
|
|
|
|
|
// not VS aware, it cannot be used as a reference for
|
|
|
|
|
// this behavior; but it does follow the principle of
|
|
|
|
|
// least surprise, and also matches the behavior that
|
|
|
|
|
// can be observed in Kitty, which is one of the only
|
|
|
|
|
// other VS aware terminals.
|
|
|
|
|
if (self.screens.active.cursor.x == right_limit - 1) {
|
|
|
|
|
// If we're already at the right edge, we stay
|
|
|
|
|
// here and set the pending wrap to false since
|
|
|
|
|
// when we pend a wrap, we only move our cursor once
|
|
|
|
|
// even for wide chars (tests verify).
|
|
|
|
|
self.screens.active.cursor.pending_wrap = false;
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, move back.
|
|
|
|
|
self.screens.active.cursorLeft(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Back track the cursor so that we don't end up with
|
|
|
|
|
// an extra space after the character. Since xterm is
|
|
|
|
|
// not VS aware, it cannot be used as a reference for
|
|
|
|
|
// this behavior; but it does follow the principle of
|
|
|
|
|
// least surprise, and also matches the behavior that
|
|
|
|
|
// can be observed in Kitty, which is one of the only
|
|
|
|
|
// other VS aware terminals.
|
|
|
|
|
if (self.screens.active.cursor.x == right_limit - 1) {
|
|
|
|
|
// If we're already at the right edge, we stay
|
|
|
|
|
// here and set the pending wrap to false since
|
|
|
|
|
// when we pend a wrap, we only move our cursor once
|
|
|
|
|
// even for wide chars (tests verify).
|
|
|
|
|
self.screens.active.cursor.pending_wrap = false;
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, move back.
|
|
|
|
|
self.screens.active.cursorLeft(1);
|
|
|
|
|
}
|
|
|
|
|
break :narrow;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
break :narrow;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => unreachable,
|
|
|
|
|
}
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.debug("c={X} grapheme attach to left={} primary_cp={X}", .{
|
|
|
|
|
@@ -3834,19 +3901,23 @@ test "Terminal: print invalid VS15 in emoji ZWJ sequence" {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: VS15 to make narrow character with pending wrap" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 2 });
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 4 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
try testing.expect(t.modes.get(.wraparound));
|
|
|
|
|
|
|
|
|
|
try t.print(0x1F34B); // Lemon, width=2
|
|
|
|
|
try t.print(0x2614); // Umbrella with rain drops, width=2
|
|
|
|
|
try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } }));
|
|
|
|
|
t.clearDirty();
|
|
|
|
|
|
|
|
|
|
// We only move one because we're in a pending wrap state.
|
|
|
|
|
// We only move to the end of the line because we're in a pending wrap
|
|
|
|
|
// state.
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expectEqual(@as(usize, 3), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
try t.print(0xFE0E); // VS15 to make narrow
|
|
|
|
|
@@ -3855,17 +3926,17 @@ test "Terminal: VS15 to make narrow character with pending wrap" {
|
|
|
|
|
|
|
|
|
|
// VS15 should clear the pending wrap state
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expectEqual(@as(usize, 3), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(!t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const str = try t.plainString(testing.allocator);
|
|
|
|
|
defer testing.allocator.free(str);
|
|
|
|
|
try testing.expectEqualStrings("☔︎", str);
|
|
|
|
|
try testing.expectEqualStrings("🍋☔︎", str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?;
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0x2614), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
@@ -3873,6 +3944,102 @@ test "Terminal: VS15 to make narrow character with pending wrap" {
|
|
|
|
|
const cps = list_cell.node.data.lookupGrapheme(cell).?;
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), cps.len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VS15 should not affect the previous grapheme
|
|
|
|
|
{
|
|
|
|
|
const lemon_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0x1F34B), lemon_cell.content.codepoint);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.wide, lemon_cell.wide);
|
|
|
|
|
const spacer_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0), spacer_cell.content.codepoint);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_tail, spacer_cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: VS16 to make wide character on next line" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 3 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
t.cursorRight(2);
|
|
|
|
|
try t.print('#');
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(t.screens.active.cursor.pending_wrap);
|
|
|
|
|
try testing.expect(t.isDirty(.{ .screen = .{ .x = 2, .y = 0 } }));
|
|
|
|
|
t.clearDirty();
|
|
|
|
|
|
|
|
|
|
try t.print(0xFE0F); // VS16 to make wide
|
|
|
|
|
|
|
|
|
|
try testing.expect(t.isDirty(.{ .screen = .{ .x = 2, .y = 0 } }));
|
|
|
|
|
t.clearDirty();
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(!t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// Previous cell turns into spacer_head
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0), cell.content.codepoint);
|
|
|
|
|
try testing.expect(!cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_head, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// '#' cell is wide
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, '#'), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqualSlices(u21, &.{0xFE0F}, list_cell.node.data.lookupGrapheme(cell).?);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.wide, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// spacer_tail
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 1 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0), cell.content.codepoint);
|
|
|
|
|
try testing.expect(!cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: VS16 to make wide character with pending wrap" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 3 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
t.cursorRight(1);
|
|
|
|
|
try t.print('#');
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(!t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
try t.print(0xFE0F); // VS16 to make wide
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expect(t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// '#' cell is wide
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, '#'), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqualSlices(u21, &.{0xFE0F}, list_cell.node.data.lookupGrapheme(cell).?);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.wide, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// spacer_tail
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0), cell.content.codepoint);
|
|
|
|
|
try testing.expect(!cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: VS16 to make wide character with mode 2027" {
|
|
|
|
|
@@ -4013,6 +4180,173 @@ test "Terminal: print invalid VS16 with second char" {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: print grapheme ò (o with nonspacing mark) should be narrow" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 5 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
try t.print('o');
|
|
|
|
|
try t.print(0x0300); // combining grave accent
|
|
|
|
|
|
|
|
|
|
// We should have 1 cell taken up.
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x);
|
|
|
|
|
|
|
|
|
|
// Assert various properties about our screen to verify
|
|
|
|
|
// we have all expected cells.
|
|
|
|
|
{
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 'o'), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqualSlices(u21, &.{0x0300}, list_cell.node.data.lookupGrapheme(cell).?);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.narrow, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: print Devanagari grapheme should be wide" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 5 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
// क्ष
|
|
|
|
|
try t.print(0x0915);
|
|
|
|
|
try t.print(0x094D);
|
|
|
|
|
try t.print(0x200D);
|
|
|
|
|
try t.print(0x0937);
|
|
|
|
|
|
|
|
|
|
// We should have 2 cells taken up. It is one character but "wide".
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
|
|
|
|
|
// Assert various properties about our screen to verify
|
|
|
|
|
// we have all expected cells.
|
|
|
|
|
{
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0x0915), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqualSlices(u21, &.{ 0x094D, 0x200D, 0x0937 }, list_cell.node.data.lookupGrapheme(cell).?);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.wide, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: print Devanagari grapheme should be wide on next line" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = 5, .cols = 3 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
t.cursorRight(2);
|
|
|
|
|
|
|
|
|
|
// क्ष
|
|
|
|
|
try t.print(0x0915);
|
|
|
|
|
try t.print(0x094D);
|
|
|
|
|
try t.print(0x200D);
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
// This one increases the width to wide
|
|
|
|
|
try t.print(0x0937);
|
|
|
|
|
|
|
|
|
|
// We should have 2 cells taken up. It is one character but "wide".
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(!t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// Previous cell turns into spacer_head
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0), cell.content.codepoint);
|
|
|
|
|
try testing.expect(!cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_head, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// Devanagari grapheme is wide
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0x0915), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqualSlices(u21, &.{ 0x094D, 0x200D, 0x0937 }, list_cell.node.data.lookupGrapheme(cell).?);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.wide, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 1 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: print Devanagari grapheme should be wide on next page" {
|
|
|
|
|
const rows = pagepkg.std_capacity.rows;
|
|
|
|
|
const cols = pagepkg.std_capacity.cols;
|
|
|
|
|
var t = try init(testing.allocator, .{ .rows = rows, .cols = cols });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|
|
|
|
|
// Enable grapheme clustering
|
|
|
|
|
t.modes.set(.grapheme_cluster, true);
|
|
|
|
|
|
|
|
|
|
t.cursorDown(rows - 1);
|
|
|
|
|
|
|
|
|
|
for (rows..t.screens.active.pages.pages.first.?.data.capacity.rows) |_| {
|
|
|
|
|
try t.index();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.cursorRight(cols - 1);
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(cols - 1, t.screens.active.cursor.x);
|
|
|
|
|
try testing.expectEqual(rows - 1, t.screens.active.cursor.y);
|
|
|
|
|
|
|
|
|
|
// क्ष
|
|
|
|
|
try t.print(0x0915);
|
|
|
|
|
try t.print(0x094D);
|
|
|
|
|
try t.print(0x200D);
|
|
|
|
|
try testing.expectEqual(cols - 1, t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
// This one increases the width to wide
|
|
|
|
|
try t.print(0x0937);
|
|
|
|
|
|
|
|
|
|
// We should have 2 cells taken up. It is one character but "wide".
|
|
|
|
|
try testing.expectEqual(rows - 1, t.screens.active.cursor.y);
|
|
|
|
|
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
|
|
|
|
try testing.expect(!t.screens.active.cursor.pending_wrap);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// Previous cell turns into spacer_head
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = cols - 1, .y = rows - 2 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0), cell.content.codepoint);
|
|
|
|
|
try testing.expect(!cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_head, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// Devanagari grapheme is wide
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = rows - 1 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(@as(u21, 0x0915), cell.content.codepoint);
|
|
|
|
|
try testing.expect(cell.hasGrapheme());
|
|
|
|
|
try testing.expectEqualSlices(u21, &.{ 0x094D, 0x200D, 0x0937 }, list_cell.node.data.lookupGrapheme(cell).?);
|
|
|
|
|
try testing.expectEqual(Cell.Wide.wide, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 1, .y = rows - 1 } }).?;
|
|
|
|
|
const cell = list_cell.cell;
|
|
|
|
|
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "Terminal: print invalid VS16 with second char (combining)" {
|
|
|
|
|
var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 });
|
|
|
|
|
defer t.deinit(testing.allocator);
|
|
|
|
|
|