




























@@ -22,6 +22,7 @@ type OpenFence = {
2222const DEFAULT_MAX_CHARS = 2000;
2323const DEFAULT_MAX_LINES = 17;
2424const FENCE_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/;
25+const CJK_PUNCTUATION_BREAK_AFTER_RE = /[、。,.!?;:)]}〉》」』】〕〗〙]/u;
25262627function countLines(text: string) {
2728if (!text) {
@@ -63,6 +64,51 @@ function closeFenceIfNeeded(text: string, openFence: OpenFence | null) {
6364return `${text}${closeLine}`;
6465}
656667+function isHighSurrogate(code: number) {
68+return code >= 0xd800 && code <= 0xdbff;
69+}
70+71+function isLowSurrogate(code: number) {
72+return code >= 0xdc00 && code <= 0xdfff;
73+}
74+75+function clampToCodePointBoundary(text: string, index: number) {
76+const boundary = Math.min(Math.max(0, index), text.length);
77+if (boundary <= 0 || boundary >= text.length) {
78+return boundary;
79+}
80+const previous = text.charCodeAt(boundary - 1);
81+const next = text.charCodeAt(boundary);
82+if (isHighSurrogate(previous) && isLowSurrogate(next)) {
83+return boundary > 1 ? boundary - 1 : boundary + 1;
84+}
85+return boundary;
86+}
87+88+function findWhitespaceBreak(window: string) {
89+for (let i = window.length - 1; i >= 0; i--) {
90+if (/\s/.test(window[i])) {
91+// Return the separator index so whitespace stays with the next segment.
92+return i;
93+}
94+}
95+return -1;
96+}
97+98+function findCjkPunctuationBreak(window: string) {
99+for (let end = window.length; end > 0; ) {
100+const code = window.charCodeAt(end - 1);
101+const start = isLowSurrogate(code) && end > 1 ? end - 2 : end - 1;
102+const char = window.slice(start, end);
103+if (start > 0 && CJK_PUNCTUATION_BREAK_AFTER_RE.test(char)) {
104+// Return the exclusive end so CJK punctuation stays with the current segment.
105+return end;
106+}
107+end = start;
108+}
109+return -1;
110+}
111+66112function splitLongLine(
67113line: string,
68114maxChars: number,
@@ -76,20 +122,18 @@ function splitLongLine(
76122let remaining = line;
77123while (remaining.length > limit) {
78124if (opts.preserveWhitespace) {
79-out.push(remaining.slice(0, limit));
80-remaining = remaining.slice(limit);
125+const breakIdx = clampToCodePointBoundary(remaining, limit);
126+out.push(remaining.slice(0, breakIdx));
127+remaining = remaining.slice(breakIdx);
81128continue;
82129}
83130const window = remaining.slice(0, limit);
84-let breakIdx = -1;
85-for (let i = window.length - 1; i >= 0; i--) {
86-if (/\s/.test(window[i])) {
87-breakIdx = i;
88-break;
89-}
131+let breakIdx = findWhitespaceBreak(window);
132+if (breakIdx <= 0) {
133+breakIdx = findCjkPunctuationBreak(window);
90134}
91135if (breakIdx <= 0) {
92-breakIdx = limit;
136+breakIdx = clampToCodePointBoundary(remaining, limit);
93137}
94138out.push(remaining.slice(0, breakIdx));
95139// Keep the separator for the next segment so words don't get glued together.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。