mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-11 03:39:36 +00:00
terminal: handle stored grapheme boundaries
With mode 2027 disabled, the printer attaches zero-width codepoints without applying Unicode grapheme boundaries. Enabling the mode later and printing another non-ASCII codepoint asserted that every stored pair was part of one grapheme and panicked when it was not. Feed all stored codepoints through the grapheme state machine and let it reset at existing boundaries before testing the new codepoint. The deterministic print comparison can now exercise live mode changes without clearing the screen first.
This commit is contained in:
@@ -952,7 +952,13 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
const cps = self.screens.active.cursor.page_pin.node.page().lookupGrapheme(prev.cell).?;
|
||||
for (cps) |cp2| {
|
||||
// log.debug("cp1={x} cp2={x}", .{ previous_codepoint, cp2 });
|
||||
assert(!unicode.graphemeBreak(previous_codepoint, cp2, &state));
|
||||
// With mode 2027 disabled, zero-width codepoints are
|
||||
// attached without applying grapheme boundary rules. If
|
||||
// the mode is enabled later, an existing cell can
|
||||
// therefore contain one or more breaks. Feed those breaks
|
||||
// into the state machine so it can reset its context and
|
||||
// determine the boundary for the new codepoint.
|
||||
_ = unicode.graphemeBreak(previous_codepoint, cp2, &state);
|
||||
previous_codepoint = cp2;
|
||||
}
|
||||
}
|
||||
@@ -4297,6 +4303,22 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" {
|
||||
try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } }));
|
||||
}
|
||||
|
||||
test "Terminal: enabling grapheme mode handles stored breaks" {
|
||||
var t = try init(testing.allocator, .{ .cols = 5, .rows = 1 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
t.modes.set(.grapheme_cluster, false);
|
||||
try t.print('a');
|
||||
try t.print(0x200B); // Zero width space is stored on the prior cell.
|
||||
|
||||
t.modes.set(.grapheme_cluster, true);
|
||||
try t.print(0x0301);
|
||||
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("a\xE2\x80\x8B\xCC\x81", str);
|
||||
}
|
||||
|
||||
// Terminal.print receives one codepoint at a time, so it can't use
|
||||
// unicode.graphemeWidth directly; that API requires a complete buffered
|
||||
// cluster or string end. This keeps the streaming printer's cursor advance
|
||||
@@ -12491,13 +12513,6 @@ fn testPrintSliceDifferential(
|
||||
},
|
||||
15 => {
|
||||
const v = rand.boolean();
|
||||
// Erase the display first: grapheme clusters created
|
||||
// while mode 2027 was off can trip a pre-existing
|
||||
// debug assert in print()'s cluster walk when the mode
|
||||
// is toggled on (unrelated to printSlice; it reproduces
|
||||
// with per-codepoint print alone).
|
||||
t1.eraseDisplay(.complete, false);
|
||||
t2.eraseDisplay(.complete, false);
|
||||
t1.modes.set(.grapheme_cluster, v);
|
||||
t2.modes.set(.grapheme_cluster, v);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user