11cb0ef41Sopenharmony_ciimport stringWidth from 'string-width'; 21cb0ef41Sopenharmony_ciimport stripAnsi from 'strip-ansi'; 31cb0ef41Sopenharmony_ciimport ansiStyles from 'ansi-styles'; 41cb0ef41Sopenharmony_ci 51cb0ef41Sopenharmony_ciconst ESCAPES = new Set([ 61cb0ef41Sopenharmony_ci '\u001B', 71cb0ef41Sopenharmony_ci '\u009B', 81cb0ef41Sopenharmony_ci]); 91cb0ef41Sopenharmony_ci 101cb0ef41Sopenharmony_ciconst END_CODE = 39; 111cb0ef41Sopenharmony_ciconst ANSI_ESCAPE_BELL = '\u0007'; 121cb0ef41Sopenharmony_ciconst ANSI_CSI = '['; 131cb0ef41Sopenharmony_ciconst ANSI_OSC = ']'; 141cb0ef41Sopenharmony_ciconst ANSI_SGR_TERMINATOR = 'm'; 151cb0ef41Sopenharmony_ciconst ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`; 161cb0ef41Sopenharmony_ci 171cb0ef41Sopenharmony_ciconst wrapAnsiCode = code => `${ESCAPES.values().next().value}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`; 181cb0ef41Sopenharmony_ciconst wrapAnsiHyperlink = uri => `${ESCAPES.values().next().value}${ANSI_ESCAPE_LINK}${uri}${ANSI_ESCAPE_BELL}`; 191cb0ef41Sopenharmony_ci 201cb0ef41Sopenharmony_ci// Calculate the length of words split on ' ', ignoring 211cb0ef41Sopenharmony_ci// the extra characters added by ansi escape codes 221cb0ef41Sopenharmony_ciconst wordLengths = string => string.split(' ').map(character => stringWidth(character)); 231cb0ef41Sopenharmony_ci 241cb0ef41Sopenharmony_ci// Wrap a long word across multiple rows 251cb0ef41Sopenharmony_ci// Ansi escape codes do not count towards length 261cb0ef41Sopenharmony_ciconst wrapWord = (rows, word, columns) => { 271cb0ef41Sopenharmony_ci const characters = [...word]; 281cb0ef41Sopenharmony_ci 291cb0ef41Sopenharmony_ci let isInsideEscape = false; 301cb0ef41Sopenharmony_ci let isInsideLinkEscape = false; 311cb0ef41Sopenharmony_ci let visible = stringWidth(stripAnsi(rows[rows.length - 1])); 321cb0ef41Sopenharmony_ci 331cb0ef41Sopenharmony_ci for (const [index, character] of characters.entries()) { 341cb0ef41Sopenharmony_ci const characterLength = stringWidth(character); 351cb0ef41Sopenharmony_ci 361cb0ef41Sopenharmony_ci if (visible + characterLength <= columns) { 371cb0ef41Sopenharmony_ci rows[rows.length - 1] += character; 381cb0ef41Sopenharmony_ci } else { 391cb0ef41Sopenharmony_ci rows.push(character); 401cb0ef41Sopenharmony_ci visible = 0; 411cb0ef41Sopenharmony_ci } 421cb0ef41Sopenharmony_ci 431cb0ef41Sopenharmony_ci if (ESCAPES.has(character)) { 441cb0ef41Sopenharmony_ci isInsideEscape = true; 451cb0ef41Sopenharmony_ci isInsideLinkEscape = characters.slice(index + 1).join('').startsWith(ANSI_ESCAPE_LINK); 461cb0ef41Sopenharmony_ci } 471cb0ef41Sopenharmony_ci 481cb0ef41Sopenharmony_ci if (isInsideEscape) { 491cb0ef41Sopenharmony_ci if (isInsideLinkEscape) { 501cb0ef41Sopenharmony_ci if (character === ANSI_ESCAPE_BELL) { 511cb0ef41Sopenharmony_ci isInsideEscape = false; 521cb0ef41Sopenharmony_ci isInsideLinkEscape = false; 531cb0ef41Sopenharmony_ci } 541cb0ef41Sopenharmony_ci } else if (character === ANSI_SGR_TERMINATOR) { 551cb0ef41Sopenharmony_ci isInsideEscape = false; 561cb0ef41Sopenharmony_ci } 571cb0ef41Sopenharmony_ci 581cb0ef41Sopenharmony_ci continue; 591cb0ef41Sopenharmony_ci } 601cb0ef41Sopenharmony_ci 611cb0ef41Sopenharmony_ci visible += characterLength; 621cb0ef41Sopenharmony_ci 631cb0ef41Sopenharmony_ci if (visible === columns && index < characters.length - 1) { 641cb0ef41Sopenharmony_ci rows.push(''); 651cb0ef41Sopenharmony_ci visible = 0; 661cb0ef41Sopenharmony_ci } 671cb0ef41Sopenharmony_ci } 681cb0ef41Sopenharmony_ci 691cb0ef41Sopenharmony_ci // It's possible that the last row we copy over is only 701cb0ef41Sopenharmony_ci // ansi escape characters, handle this edge-case 711cb0ef41Sopenharmony_ci if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) { 721cb0ef41Sopenharmony_ci rows[rows.length - 2] += rows.pop(); 731cb0ef41Sopenharmony_ci } 741cb0ef41Sopenharmony_ci}; 751cb0ef41Sopenharmony_ci 761cb0ef41Sopenharmony_ci// Trims spaces from a string ignoring invisible sequences 771cb0ef41Sopenharmony_ciconst stringVisibleTrimSpacesRight = string => { 781cb0ef41Sopenharmony_ci const words = string.split(' '); 791cb0ef41Sopenharmony_ci let last = words.length; 801cb0ef41Sopenharmony_ci 811cb0ef41Sopenharmony_ci while (last > 0) { 821cb0ef41Sopenharmony_ci if (stringWidth(words[last - 1]) > 0) { 831cb0ef41Sopenharmony_ci break; 841cb0ef41Sopenharmony_ci } 851cb0ef41Sopenharmony_ci 861cb0ef41Sopenharmony_ci last--; 871cb0ef41Sopenharmony_ci } 881cb0ef41Sopenharmony_ci 891cb0ef41Sopenharmony_ci if (last === words.length) { 901cb0ef41Sopenharmony_ci return string; 911cb0ef41Sopenharmony_ci } 921cb0ef41Sopenharmony_ci 931cb0ef41Sopenharmony_ci return words.slice(0, last).join(' ') + words.slice(last).join(''); 941cb0ef41Sopenharmony_ci}; 951cb0ef41Sopenharmony_ci 961cb0ef41Sopenharmony_ci// The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode 971cb0ef41Sopenharmony_ci// 981cb0ef41Sopenharmony_ci// 'hard' will never allow a string to take up more than columns characters 991cb0ef41Sopenharmony_ci// 1001cb0ef41Sopenharmony_ci// 'soft' allows long words to expand past the column length 1011cb0ef41Sopenharmony_ciconst exec = (string, columns, options = {}) => { 1021cb0ef41Sopenharmony_ci if (options.trim !== false && string.trim() === '') { 1031cb0ef41Sopenharmony_ci return ''; 1041cb0ef41Sopenharmony_ci } 1051cb0ef41Sopenharmony_ci 1061cb0ef41Sopenharmony_ci let returnValue = ''; 1071cb0ef41Sopenharmony_ci let escapeCode; 1081cb0ef41Sopenharmony_ci let escapeUrl; 1091cb0ef41Sopenharmony_ci 1101cb0ef41Sopenharmony_ci const lengths = wordLengths(string); 1111cb0ef41Sopenharmony_ci let rows = ['']; 1121cb0ef41Sopenharmony_ci 1131cb0ef41Sopenharmony_ci for (const [index, word] of string.split(' ').entries()) { 1141cb0ef41Sopenharmony_ci if (options.trim !== false) { 1151cb0ef41Sopenharmony_ci rows[rows.length - 1] = rows[rows.length - 1].trimStart(); 1161cb0ef41Sopenharmony_ci } 1171cb0ef41Sopenharmony_ci 1181cb0ef41Sopenharmony_ci let rowLength = stringWidth(rows[rows.length - 1]); 1191cb0ef41Sopenharmony_ci 1201cb0ef41Sopenharmony_ci if (index !== 0) { 1211cb0ef41Sopenharmony_ci if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) { 1221cb0ef41Sopenharmony_ci // If we start with a new word but the current row length equals the length of the columns, add a new row 1231cb0ef41Sopenharmony_ci rows.push(''); 1241cb0ef41Sopenharmony_ci rowLength = 0; 1251cb0ef41Sopenharmony_ci } 1261cb0ef41Sopenharmony_ci 1271cb0ef41Sopenharmony_ci if (rowLength > 0 || options.trim === false) { 1281cb0ef41Sopenharmony_ci rows[rows.length - 1] += ' '; 1291cb0ef41Sopenharmony_ci rowLength++; 1301cb0ef41Sopenharmony_ci } 1311cb0ef41Sopenharmony_ci } 1321cb0ef41Sopenharmony_ci 1331cb0ef41Sopenharmony_ci // In 'hard' wrap mode, the length of a line is never allowed to extend past 'columns' 1341cb0ef41Sopenharmony_ci if (options.hard && lengths[index] > columns) { 1351cb0ef41Sopenharmony_ci const remainingColumns = (columns - rowLength); 1361cb0ef41Sopenharmony_ci const breaksStartingThisLine = 1 + Math.floor((lengths[index] - remainingColumns - 1) / columns); 1371cb0ef41Sopenharmony_ci const breaksStartingNextLine = Math.floor((lengths[index] - 1) / columns); 1381cb0ef41Sopenharmony_ci if (breaksStartingNextLine < breaksStartingThisLine) { 1391cb0ef41Sopenharmony_ci rows.push(''); 1401cb0ef41Sopenharmony_ci } 1411cb0ef41Sopenharmony_ci 1421cb0ef41Sopenharmony_ci wrapWord(rows, word, columns); 1431cb0ef41Sopenharmony_ci continue; 1441cb0ef41Sopenharmony_ci } 1451cb0ef41Sopenharmony_ci 1461cb0ef41Sopenharmony_ci if (rowLength + lengths[index] > columns && rowLength > 0 && lengths[index] > 0) { 1471cb0ef41Sopenharmony_ci if (options.wordWrap === false && rowLength < columns) { 1481cb0ef41Sopenharmony_ci wrapWord(rows, word, columns); 1491cb0ef41Sopenharmony_ci continue; 1501cb0ef41Sopenharmony_ci } 1511cb0ef41Sopenharmony_ci 1521cb0ef41Sopenharmony_ci rows.push(''); 1531cb0ef41Sopenharmony_ci } 1541cb0ef41Sopenharmony_ci 1551cb0ef41Sopenharmony_ci if (rowLength + lengths[index] > columns && options.wordWrap === false) { 1561cb0ef41Sopenharmony_ci wrapWord(rows, word, columns); 1571cb0ef41Sopenharmony_ci continue; 1581cb0ef41Sopenharmony_ci } 1591cb0ef41Sopenharmony_ci 1601cb0ef41Sopenharmony_ci rows[rows.length - 1] += word; 1611cb0ef41Sopenharmony_ci } 1621cb0ef41Sopenharmony_ci 1631cb0ef41Sopenharmony_ci if (options.trim !== false) { 1641cb0ef41Sopenharmony_ci rows = rows.map(row => stringVisibleTrimSpacesRight(row)); 1651cb0ef41Sopenharmony_ci } 1661cb0ef41Sopenharmony_ci 1671cb0ef41Sopenharmony_ci const pre = [...rows.join('\n')]; 1681cb0ef41Sopenharmony_ci 1691cb0ef41Sopenharmony_ci for (const [index, character] of pre.entries()) { 1701cb0ef41Sopenharmony_ci returnValue += character; 1711cb0ef41Sopenharmony_ci 1721cb0ef41Sopenharmony_ci if (ESCAPES.has(character)) { 1731cb0ef41Sopenharmony_ci const {groups} = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`).exec(pre.slice(index).join('')) || {groups: {}}; 1741cb0ef41Sopenharmony_ci if (groups.code !== undefined) { 1751cb0ef41Sopenharmony_ci const code = Number.parseFloat(groups.code); 1761cb0ef41Sopenharmony_ci escapeCode = code === END_CODE ? undefined : code; 1771cb0ef41Sopenharmony_ci } else if (groups.uri !== undefined) { 1781cb0ef41Sopenharmony_ci escapeUrl = groups.uri.length === 0 ? undefined : groups.uri; 1791cb0ef41Sopenharmony_ci } 1801cb0ef41Sopenharmony_ci } 1811cb0ef41Sopenharmony_ci 1821cb0ef41Sopenharmony_ci const code = ansiStyles.codes.get(Number(escapeCode)); 1831cb0ef41Sopenharmony_ci 1841cb0ef41Sopenharmony_ci if (pre[index + 1] === '\n') { 1851cb0ef41Sopenharmony_ci if (escapeUrl) { 1861cb0ef41Sopenharmony_ci returnValue += wrapAnsiHyperlink(''); 1871cb0ef41Sopenharmony_ci } 1881cb0ef41Sopenharmony_ci 1891cb0ef41Sopenharmony_ci if (escapeCode && code) { 1901cb0ef41Sopenharmony_ci returnValue += wrapAnsiCode(code); 1911cb0ef41Sopenharmony_ci } 1921cb0ef41Sopenharmony_ci } else if (character === '\n') { 1931cb0ef41Sopenharmony_ci if (escapeCode && code) { 1941cb0ef41Sopenharmony_ci returnValue += wrapAnsiCode(escapeCode); 1951cb0ef41Sopenharmony_ci } 1961cb0ef41Sopenharmony_ci 1971cb0ef41Sopenharmony_ci if (escapeUrl) { 1981cb0ef41Sopenharmony_ci returnValue += wrapAnsiHyperlink(escapeUrl); 1991cb0ef41Sopenharmony_ci } 2001cb0ef41Sopenharmony_ci } 2011cb0ef41Sopenharmony_ci } 2021cb0ef41Sopenharmony_ci 2031cb0ef41Sopenharmony_ci return returnValue; 2041cb0ef41Sopenharmony_ci}; 2051cb0ef41Sopenharmony_ci 2061cb0ef41Sopenharmony_ci// For each newline, invoke the method separately 2071cb0ef41Sopenharmony_ciexport default function wrapAnsi(string, columns, options) { 2081cb0ef41Sopenharmony_ci return String(string) 2091cb0ef41Sopenharmony_ci .normalize() 2101cb0ef41Sopenharmony_ci .replace(/\r\n/g, '\n') 2111cb0ef41Sopenharmony_ci .split('\n') 2121cb0ef41Sopenharmony_ci .map(line => exec(line, columns, options)) 2131cb0ef41Sopenharmony_ci .join('\n'); 2141cb0ef41Sopenharmony_ci} 215