diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index a2e11db15..14ba4b0a8 100644 --- a/packages/tui/src/utils.ts +++ b/packages/tui/src/utils.ts @@ -112,12 +112,26 @@ export function visibleWidth(str: string): number { return visibleWidthRaw(str); } -const makeBoolArray = (chars: string): ReadonlyArray => { - const table = Array.from({ length: 128 }, () => false); +const THAI_LAO_AM_REGEX = /[\u0e33\u0eb3]/; +const THAI_LAO_AM_GLOBAL_REGEX = /[\u0e33\u0eb3]/g; + +/** + * Normalize text for terminal output without changing logical editor content. + * Some terminals render precomposed Thai/Lao AM vowels inconsistently during + * differential repaint. Their compatibility decompositions have the same cell + * width but avoid stale-cell artifacts in terminal renderers. + */ +export function normalizeTerminalOutput(str: string): string { + if (!THAI_LAO_AM_REGEX.test(str)) return str; + return str.replace(THAI_LAO_AM_GLOBAL_REGEX, (char) => (char === "\u0e33" ? "\u0e4d\u0e32" : "\u0ecd\u0eb2")); +} + +const makeBoolArray = (chars: string): Uint8Array => { + const table = new Uint8Array(128); for (let i = 0; i < chars.length; i++) { const code = chars.charCodeAt(i); if (code < table.length) { - table[code] = true; + table[code] = 1; } } return table; @@ -129,8 +143,8 @@ const ASCII_WHITESPACE = makeBoolArray("\x09\x0a\x0b\x0c\x0d\x20"); * Check if a character is whitespace. */ export function isWhitespaceChar(char: string): boolean { - const code = char.codePointAt(0) || 0; - return ASCII_WHITESPACE[code] ?? false; + const code = char.codePointAt(0) ?? 0; + return code < 128 && ASCII_WHITESPACE[code] === 1; } const ASCII_PUNCTUATION = makeBoolArray("(){}[]<>.,;:'\"!?+-=*/\\|&%^$#@~`"); @@ -139,8 +153,8 @@ const ASCII_PUNCTUATION = makeBoolArray("(){}[]<>.,;:'\"!?+-=*/\\|&%^$#@~`"); * Check if a character is punctuation. */ export function isPunctuationChar(char: string): boolean { - const code = char.codePointAt(0) || 0; - return ASCII_PUNCTUATION[code] ?? false; + const code = char.codePointAt(0) ?? 0; + return code < 128 && ASCII_PUNCTUATION[code] === 1; } export type WordNavKind = "whitespace" | "delimiter" | "cjk" | "word" | "other";