mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-24 13:50:11 +00:00
Fix zero-width grapheme attachment during pending wrap (#12581)
This PR fixes an issue where a zero-width combining mark could attach to the wrong cell when the preceding character was written in the final column and the cursor had a pending wrap. The test I added used to fail before the fix, but it passes now.
This commit is contained in:
@@ -573,20 +573,25 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
// it.
|
||||
if (self.modes.get(.grapheme_cluster)) return;
|
||||
|
||||
// If we're at cell zero, then this is malformed data and we don't
|
||||
// print anything or even store this. Zero-width characters are ALWAYS
|
||||
// attached to some other non-zero-width character at the time of
|
||||
// writing.
|
||||
if (self.screens.active.cursor.x == 0) {
|
||||
// If we have wraparound enabled and a pending wrap, the character
|
||||
// we're attaching to is still under the cursor. Otherwise, it's the
|
||||
// cell to the left.
|
||||
const left: size.CellCountInt = if (self.modes.get(.wraparound) and self.screens.active.cursor.pending_wrap) 0 else 1;
|
||||
|
||||
// If we're at cell zero and not pending a wrap, then this is malformed
|
||||
// data and we don't print anything or even store this. Zero-width
|
||||
// characters are ALWAYS attached to some other non-zero-width
|
||||
// character at the time of writing.
|
||||
if (self.screens.active.cursor.x == 0 and left == 1) {
|
||||
log.warn("zero-width character with no prior character, ignoring", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
// Find our previous cell
|
||||
const prev = prev: {
|
||||
const immediate = self.screens.active.cursorCellLeft(1);
|
||||
const immediate = self.screens.active.cursorCellLeft(left);
|
||||
if (immediate.wide != .spacer_tail) break :prev immediate;
|
||||
break :prev self.screens.active.cursorCellLeft(2);
|
||||
break :prev self.screens.active.cursorCellLeft(left + 1);
|
||||
};
|
||||
|
||||
// If our previous cell has no text, just ignore the zero-width character
|
||||
@@ -3313,6 +3318,23 @@ test "Terminal: zero-width character at start" {
|
||||
try testing.expect(!t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } }));
|
||||
}
|
||||
|
||||
// https://github.com/ghostty-org/ghostty/issues/12581
|
||||
test "Terminal: zero-width character attaches to pending wrap cell" {
|
||||
var t = try init(testing.allocator, .{ .cols = 2, .rows = 2 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// Disable grapheme clustering to exercise the fallback path.
|
||||
t.modes.set(.grapheme_cluster, false);
|
||||
|
||||
try t.print('x');
|
||||
try t.print('å');
|
||||
try t.print(0x0332); // Combining low line.
|
||||
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("xå̲", str);
|
||||
}
|
||||
|
||||
// https://github.com/mitchellh/ghostty/issues/1400
|
||||
test "Terminal: print single very long line" {
|
||||
var t = try init(testing.allocator, .{ .rows = 5, .cols = 5 });
|
||||
|
||||
@@ -1052,6 +1052,29 @@ test "vt_write split escape sequence" {
|
||||
try testing.expectEqualStrings("Hello Bold", str);
|
||||
}
|
||||
|
||||
test "vt_write split combining mark after base at right edge" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 2,
|
||||
.rows = 2,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
// Put "å" in the final column, then send its combining low line in a
|
||||
// separate write so the mark arrives while the cursor has a pending wrap.
|
||||
vt_write(t, "xå", 3);
|
||||
vt_write(t, "\xcc\xb2", 2);
|
||||
|
||||
const str = try t.?.terminal.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("xå̲", str);
|
||||
}
|
||||
|
||||
test "get cols and rows" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user