terminal: resize with less cols preserves zero width codepoints

This commit is contained in:
Mitchell Hashimoto
2023-08-28 08:18:03 -07:00
parent c243c9d72e
commit be27b825f3

View File

@ -2349,7 +2349,8 @@ pub fn resize(self: *Screen, rows: usize, cols: usize) !void {
errdefer self.storage.deinit(self.alloc); errdefer self.storage.deinit(self.alloc);
defer old.storage.deinit(self.alloc); defer old.storage.deinit(self.alloc);
// Copy grapheme map // Create empty grapheme map. Cell IDs change so we can't just copy it,
// we'll rebuild it.
self.graphemes = .{}; self.graphemes = .{};
errdefer self.deinitGraphemes(); errdefer self.deinitGraphemes();
defer old.deinitGraphemes(); defer old.deinitGraphemes();
@ -2388,9 +2389,12 @@ pub fn resize(self: *Screen, rows: usize, cols: usize) !void {
} }
// Fast path: our old row is not wrapped AND our old row fits // Fast path: our old row is not wrapped AND our old row fits
// into our new smaller size. In this case, we just do a fast // into our new smaller size AND this row has no grapheme clusters.
// copy and move on. // In this case, we just do a fast copy and move on.
if (!old_row_wrapped and trimmed_row.len <= self.cols) { if (!old_row_wrapped and
trimmed_row.len <= self.cols and
!old_row.header().flags.grapheme)
{
// If our cursor is on this line, then set the new cursor. // If our cursor is on this line, then set the new cursor.
if (cursor_pos.y == old_y) { if (cursor_pos.y == old_y) {
assert(new_cursor == null); assert(new_cursor == null);
@ -2473,6 +2477,14 @@ pub fn resize(self: *Screen, rows: usize, cols: usize) !void {
// Write the cell // Write the cell
var new_cell = row.getCellPtr(x); var new_cell = row.getCellPtr(x);
new_cell.* = cell.cell; new_cell.* = cell.cell;
// If the old cell is a multi-codepoint grapheme then we
// need to also attach the graphemes.
if (cell.cell.attrs.grapheme) {
var it = cur_old_row.codepointIterator(old_x);
while (it.next()) |cp| try row.attachGrapheme(x, cp);
}
x += 1; x += 1;
} }
@ -5625,14 +5637,16 @@ test "Screen: resize less cols with graphemes" {
try testing.expectEqual(cursor, s.cursor); try testing.expectEqual(cursor, s.cursor);
{ {
const expected = "1AB\n2EF\n3IJ";
var contents = try s.testString(alloc, .viewport); var contents = try s.testString(alloc, .viewport);
defer alloc.free(contents); defer alloc.free(contents);
try testing.expectEqualStrings(str, contents); try testing.expectEqualStrings(expected, contents);
} }
{ {
const expected = "1AB\n2EF\n3IJ";
var contents = try s.testString(alloc, .screen); var contents = try s.testString(alloc, .screen);
defer alloc.free(contents); defer alloc.free(contents);
try testing.expectEqualStrings(str, contents); try testing.expectEqualStrings(expected, contents);
} }
} }
@ -6105,6 +6119,36 @@ test "Screen: resize more cols with wide spacer head" {
} }
} }
test "Screen: resize less cols preserves grapheme cluster" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 1, 5, 0);
defer s.deinit();
const str: []const u8 = &.{ 0x43, 0xE2, 0x83, 0x90 }; // C⃐ (C with combining left arrow)
try s.testWriteString(str);
// We should have a single cell with all the codepoints
{
const row = s.getRow(.{ .screen = 0 });
try testing.expectEqual(@as(usize, 2), row.codepointLen(0));
}
{
var contents = try s.testString(alloc, .screen);
defer alloc.free(contents);
try testing.expectEqualStrings(str, contents);
}
// Resize to less columns. No wrapping, but we should still have
// the same grapheme cluster.
try s.resize(1, 4);
{
var contents = try s.testString(alloc, .screen);
defer alloc.free(contents);
try testing.expectEqualStrings(str, contents);
}
}
test "Screen: resize more cols with wide spacer head multiple lines" { test "Screen: resize more cols with wide spacer head multiple lines" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;