UNPKG

ink

Version:
77 lines • 3.36 kB
import { hasAnsiControlCharacters, tokenizeAnsi } from './ansi-tokenizer.js'; const sgrParametersRegex = /^[\d:;]*$/; // Terminals print nothing for C0 controls and DEL, so strip them; tabs and newlines carry layout. ESC and C1 never reach text tokens, the tokenizer owns them. const controlCharactersRegex = /(?![\t\n])\p{Control}/gu; const leadingMarksRegex = /^\p{Mark}+/u; // @alcalzone/ansi-tokenize only accepts `[0-9;]` SGR parameters and renders anything else as visible cells, so colon sub-parameters are rewritten before layout. // 256-color and truecolor forms map losslessly to the semicolon form, underline styles (`4:n`) degrade to plain underline, everything else (underline color `58:...`, malformed color forms) is dropped. const normalizeParameter = (parameter) => { const parts = parameter.split(':'); if (parts.length === 1) { return parameter; } if (parts[0] === '4') { return parts[1] === '0' || parts[1] === '' ? '24' : '4'; } if (parts[0] !== '38' && parts[0] !== '48') { return undefined; } if (parts[1] === '5' && parts.length === 3) { return parts.join(';'); } if (parts[1] === '2') { // Drop an empty or default color space id. if (parts.length === 6 && (parts[2] === '' || parts[2] === '0')) { parts.splice(2, 1); } if (parts.length === 5) { return parts.join(';'); } } return undefined; }; // Strip ANSI escape sequences that would conflict with Ink's layout. // Preserved: SGR sequences (colors, bold, etc. - end with 'm') and // OSC sequences (hyperlinks, etc. - ESC ] or C1 OSC). // Stripped: cursor movement, screen clearing, and other control sequences. // Combining marks share their base character's cell, so SGR sequences are deferred until after the marks. Otherwise ANSI tokenization consumes the marks as part of the escape sequence. const sanitizeAnsi = (text) => { if (!hasAnsiControlCharacters(text)) { return text.replaceAll(controlCharactersRegex, ''); } let output = ''; let pendingStyles = ''; for (const token of tokenizeAnsi(text)) { if (token.type === 'text') { const value = token.value.replaceAll(controlCharactersRegex, ''); const marks = leadingMarksRegex.exec(value)?.[0] ?? ''; output += marks; if (value.length > marks.length) { output += pendingStyles + value.slice(marks.length); pendingStyles = ''; } continue; } if (token.type === 'osc') { output += pendingStyles + token.value; pendingStyles = ''; continue; } if (token.type !== 'csi' || token.finalCharacter !== 'm' || token.intermediateString !== '' || !sgrParametersRegex.test(token.parameterString)) { continue; } const parameters = token.parameterString .split(';') .map(parameter => normalizeParameter(parameter)) .filter(parameter => parameter !== undefined); if (parameters.length > 0) { pendingStyles += `\u{1B}[${parameters.join(';')}m`; } } return output + pendingStyles; }; export default sanitizeAnsi; //# sourceMappingURL=sanitize-ansi.js.map