Merge pull request #5585 from haoqixu/fix-editor-cjk-wrap

fix(tui): wrap CJK text at character boundaries in editor
This commit is contained in:
Mario Zechner
2026-06-10 11:43:42 +02:00
committed by GitHub
Unverified
2 changed files with 20 additions and 5 deletions
+19 -4
View File
@@ -4,7 +4,14 @@ import { decodePrintableKey, matchesKey } from "../keys.ts";
import { KillRing } from "../kill-ring.ts";
import { type Component, CURSOR_MARKER, type Focusable, type TUI } from "../tui.ts";
import { UndoStack } from "../undo-stack.ts";
import { getGraphemeSegmenter, getWordSegmenter, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.ts";
import {
cjkBreakRegex,
getGraphemeSegmenter,
getWordSegmenter,
isWhitespaceChar,
truncateToWidth,
visibleWidth,
} from "../utils.ts";
import { findWordBackward, findWordForward } from "../word-navigation.ts";
import { SelectList, type SelectListLayoutOptions, type SelectListTheme } from "./select-list.ts";
@@ -174,13 +181,21 @@ export function wordWrapLine(line: string, maxWidth: number, preSegmented?: Intl
// Advance.
currentWidth += gWidth;
// Record wrap opportunity: whitespace followed by non-whitespace.
// Multiple spaces join (no break between them); the break point is
// after the last space before the next word.
// Record wrap opportunity: whitespace followed by non-whitespace
// (multiple spaces join; the break point is after the last space),
// or at a boundary where either side is CJK (CJK allows breaking
// between any adjacent characters).
const next = segments[i + 1];
if (isWs && next && (isPasteMarker(next.segment) || !isWhitespaceChar(next.segment))) {
wrapOppIndex = next.index;
wrapOppWidth = currentWidth;
} else if (!isWs && next && !isWhitespaceChar(next.segment)) {
const isCjk = !isPasteMarker(grapheme) && cjkBreakRegex.test(grapheme);
const nextIsCjk = !isPasteMarker(next.segment) && cjkBreakRegex.test(next.segment);
if (isCjk || nextIsCjk) {
wrapOppIndex = next.index;
wrapOppWidth = currentWidth;
}
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ const rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
const WIDTH_CACHE_SIZE = 512;
const widthCache = new Map<string, number>();
const cjkBreakRegex =
export const cjkBreakRegex =
/[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}\p{Script_Extensions=Bopomofo}]/u;
function isPrintableAscii(str: string): boolean {