@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed editor rendering artifacts for Thai Sara Am and Lao AM vowel characters ([#3904](https://github.com/badlogic/pi-mono/issues/3904))
|
||||||
|
|
||||||
## [0.70.6] - 2026-04-28
|
## [0.70.6] - 2026-04-28
|
||||||
|
|
||||||
## [0.70.5] - 2026-04-27
|
## [0.70.5] - 2026-04-27
|
||||||
|
|||||||
@@ -178,14 +178,26 @@ function graphemeWidth(segment: string): number {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Thai SARA AM (U+0E33) and Lao VOWEL SIGN AM (U+0EB3) are encoded as
|
||||||
|
// spacing letters, but terminals commonly render isolated occurrences as
|
||||||
|
// mark-like clusters (compat decomposition: nonspacing niggahita + vowel).
|
||||||
|
// Count isolated clusters conservatively to avoid cursor drift and stale
|
||||||
|
// cells during differential rendering. When attached to a base consonant,
|
||||||
|
// the base code point is first and normal per-codepoint width below applies.
|
||||||
|
if (cp === 0x0e33 || cp === 0x0eb3) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
let width = eastAsianWidth(cp);
|
let width = eastAsianWidth(cp);
|
||||||
|
|
||||||
// Trailing halfwidth/fullwidth forms
|
// Trailing halfwidth/fullwidth forms and AM vowels that segment with a base.
|
||||||
if (segment.length > 1) {
|
if (segment.length > 1) {
|
||||||
for (const char of segment.slice(1)) {
|
for (const char of segment.slice(1)) {
|
||||||
const c = char.codePointAt(0)!;
|
const c = char.codePointAt(0)!;
|
||||||
if (c >= 0xff00 && c <= 0xffef) {
|
if (c >= 0xff00 && c <= 0xffef) {
|
||||||
width += eastAsianWidth(c);
|
width += eastAsianWidth(c);
|
||||||
|
} else if (c === 0x0e33 || c === 0x0eb3) {
|
||||||
|
width += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -626,6 +626,18 @@ describe("Editor component", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders isolated Thai and Lao AM clusters without width drift", () => {
|
||||||
|
for (const text of ["ำabc", "ຳabc"]) {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
const width = 8;
|
||||||
|
editor.setText(text);
|
||||||
|
|
||||||
|
for (const line of editor.render(width)) {
|
||||||
|
assert.strictEqual(visibleWidth(line), width, `line width drift for ${JSON.stringify(text)}: ${line}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("wraps CJK characters correctly (each is 2 columns wide)", () => {
|
it("wraps CJK characters correctly (each is 2 columns wide)", () => {
|
||||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
const width = 10 + 1; // +1 col reserved for cursor
|
const width = 10 + 1; // +1 col reserved for cursor
|
||||||
|
|||||||
@@ -59,4 +59,11 @@ describe("visibleWidth", () => {
|
|||||||
it("counts tabs inline and skips ANSI inline", () => {
|
it("counts tabs inline and skips ANSI inline", () => {
|
||||||
assert.strictEqual(visibleWidth("\t\x1b[31m界\x1b[0m"), 5);
|
assert.strictEqual(visibleWidth("\t\x1b[31m界\x1b[0m"), 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("counts isolated Thai and Lao AM clusters conservatively", () => {
|
||||||
|
assert.strictEqual(visibleWidth("ำ"), 2);
|
||||||
|
assert.strictEqual(visibleWidth("ຳ"), 2);
|
||||||
|
assert.strictEqual(visibleWidth("กำ"), 2);
|
||||||
|
assert.strictEqual(visibleWidth("ກຳ"), 2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user