/** * Strip ANSI escape sequences, remove control characters / lone surrogates, * and normalize line endings. * * Bun-native implementation of the former native `sanitizeText` (see * `crates/pi-natives/src/text.rs::sanitize_text`). JavaScript strings are * already UTF-16 code-unit arrays. `toWellFormed()` handles the uncommon * malformed path; when it changes the input, replacement characters are * dropped and the normalized result goes through the well-formed sanitizer. * * Fast path: well-formed input with no controls or ANSI returns the original * string after the control probe. */ const ESC_CHAR = "\x1b"; // Well-formed strings only need control/ANSI detection: C0 (excl. \t \n), // CR, DEL, and C1. ESC (0x1B) is in \x0B-\x1F. const CONTROL_RE = /[\x00-\x08\x0B-\x1F\x7F-\x9F]/g; const DISPLAY_FORMAT_RE = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu; const REPLACEMENT_CHAR = "\ufffd"; export function sanitizeText(text: string): string { const wellFormed = text.toWellFormed(); if (wellFormed !== text) { return sanitizeWellFormedText(wellFormed.replaceAll(REPLACEMENT_CHAR, "")); } return sanitizeWellFormedText(text); } /** * Sanitize untrusted text that must occupy exactly one rendered row. * * {@link sanitizeText} deliberately preserves `\n`, and width-based truncation * treats it as zero-width, so a value carrying line breaks can still inject * extra rows and evade a single-line width budget. Flatten every CR/LF run to a * single space before the usual control/ANSI strip, then remove directional * format controls that can visually reorder an otherwise safe row. */ export function sanitizeDisplayLine(text: string): string { return sanitizeText(text.replace(/[\r\n]+/gu, " ")).replace(DISPLAY_FORMAT_RE, ""); } function sanitizeWellFormedText(text: string): string { CONTROL_RE.lastIndex = 0; if (CONTROL_RE.exec(text) === null) return text; const stripped = text.indexOf(ESC_CHAR) === -1 ? text : Bun.stripANSI(text); CONTROL_RE.lastIndex = 0; return stripped.replace(CONTROL_RE, ""); }