/** * Split after sentence punctuation and keep the punctuation on the left piece. * * Replaces lookbehind splits. Those are syntax errors on Safari before 16.4, * so this file must not grow one. A newline inside `punctuation` * is a kept boundary. `splitOnNewlines` instead discards `\n+` runs. Do not * combine that option with a newline in `punctuation`. */ export type SentenceSplitOptions = { /** Drop whitespace after the kept punctuation. Same as a trailing `\s*`. */ consumeFollowingWhitespace?: boolean; /** Discard `\n+` even when the newline is not after punctuation. */ splitOnNewlines?: boolean; }; function isJsWhitespace(char: string): boolean { return char.length === 1 && /\s/u.test(char); } export function splitAfterSentencePunctuation( text: string, punctuation: string, options?: SentenceSplitOptions, ): string[] { const consumeFollowingWhitespace = options?.consumeFollowingWhitespace === true; const splitOnNewlines = options?.splitOnNewlines === true; const marks = new Set(punctuation); const parts: string[] = []; let start = 0; let index = 0; while (index < text.length) { const char = text.charAt(index); if (marks.has(char)) { const boundaryEnd = index + 1; let delimiterEnd = boundaryEnd; if (consumeFollowingWhitespace) { while (delimiterEnd < text.length && isJsWhitespace(text.charAt(delimiterEnd))) { delimiterEnd += 1; } } // A zero-width match at the end of the string does not add a trailing empty piece. if (delimiterEnd === text.length && delimiterEnd === boundaryEnd) break; parts.push(text.slice(start, boundaryEnd)); start = delimiterEnd; index = delimiterEnd; continue; } if (splitOnNewlines && char === "\n") { let newlineEnd = index + 1; while (newlineEnd < text.length && text.charAt(newlineEnd) === "\n") newlineEnd += 1; parts.push(text.slice(start, index)); start = newlineEnd; index = newlineEnd; continue; } index += 1; } parts.push(text.slice(start)); return parts; }