diff --git a/packages/tui/src/components/editor.ts b/packages/tui/src/components/editor.ts index 128254b21..724dd4607 100644 --- a/packages/tui/src/components/editor.ts +++ b/packages/tui/src/components/editor.ts @@ -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; + } } } diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index 3f27639ec..bf228ce0e 100644 --- a/packages/tui/src/utils.ts +++ b/packages/tui/src/utils.ts @@ -45,7 +45,7 @@ const rgiEmojiRegex = /^\p{RGI_Emoji}$/v; const WIDTH_CACHE_SIZE = 512; const widthCache = new Map(); -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 {