diff options
Diffstat (limited to 'node_modules/wrap-ansi/index.js')
| -rwxr-xr-x | node_modules/wrap-ansi/index.js | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/node_modules/wrap-ansi/index.js b/node_modules/wrap-ansi/index.js new file mode 100755 index 00000000..d2ae454f --- /dev/null +++ b/node_modules/wrap-ansi/index.js @@ -0,0 +1,193 @@ +'use strict'; +const stringWidth = require('string-width'); +const stripAnsi = require('strip-ansi'); + +const ESCAPES = new Set([ + '\u001B', + '\u009B' +]); + +const END_CODE = 39; + +const ESCAPE_CODES = new Map([ + [0, 0], + [1, 22], + [2, 22], + [3, 23], + [4, 24], + [7, 27], + [8, 28], + [9, 29], + [30, 39], + [31, 39], + [32, 39], + [33, 39], + [34, 39], + [35, 39], + [36, 39], + [37, 39], + [90, 39], + [40, 49], + [41, 49], + [42, 49], + [43, 49], + [44, 49], + [45, 49], + [46, 49], + [47, 49] +]); + +const wrapAnsi = code => `${ESCAPES.values().next().value}[${code}m`; + +// Calculate the length of words split on ' ', ignoring +// the extra characters added by ansi escape codes +const wordLengths = str => str.split(' ').map(s => stringWidth(s)); + +// Wrap a long word across multiple rows +// Ansi escape codes do not count towards length +const wrapWord = (rows, word, cols) => { + const arr = Array.from(word); + + let insideEscape = false; + let visible = stringWidth(stripAnsi(rows[rows.length - 1])); + + for (const item of arr.entries()) { + const i = item[0]; + const char = item[1]; + const charLength = stringWidth(char); + + if (visible + charLength <= cols) { + rows[rows.length - 1] += char; + } else { + rows.push(char); + visible = 0; + } + + if (ESCAPES.has(char)) { + insideEscape = true; + } else if (insideEscape && char === 'm') { + insideEscape = false; + continue; + } + + if (insideEscape) { + continue; + } + + visible += charLength; + + if (visible === cols && i < arr.length - 1) { + rows.push(''); + visible = 0; + } + } + + // It's possible that the last row we copy over is only + // ansi escape characters, handle this edge-case + if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) { + rows[rows.length - 2] += rows.pop(); + } +}; + +// The wrap-ansi module can be invoked +// in either 'hard' or 'soft' wrap mode +// +// 'hard' will never allow a string to take up more +// than cols characters +// +// 'soft' allows long words to expand past the column length +const exec = (str, cols, opts) => { + const options = opts || {}; + + if (str.trim() === '') { + return options.trim === false ? str : str.trim(); + } + + let pre = ''; + let ret = ''; + let escapeCode; + + const lengths = wordLengths(str); + const words = str.split(' '); + const rows = ['']; + + for (const item of Array.from(words).entries()) { + const i = item[0]; + const word = item[1]; + + rows[rows.length - 1] = options.trim === false ? rows[rows.length - 1] : rows[rows.length - 1].trim(); + let rowLength = stringWidth(rows[rows.length - 1]); + + if (rowLength || word === '') { + if (rowLength === cols && options.wordWrap === false) { + // If we start with a new word but the current row length equals the length of the columns, add a new row + rows.push(''); + rowLength = 0; + } + + rows[rows.length - 1] += ' '; + rowLength++; + } + + // In 'hard' wrap mode, the length of a line is + // never allowed to extend past 'cols' + if (lengths[i] > cols && options.hard) { + if (rowLength) { + rows.push(''); + } + wrapWord(rows, word, cols); + continue; + } + + if (rowLength + lengths[i] > cols && rowLength > 0) { + if (options.wordWrap === false && rowLength < cols) { + wrapWord(rows, word, cols); + continue; + } + + rows.push(''); + } + + if (rowLength + lengths[i] > cols && options.wordWrap === false) { + wrapWord(rows, word, cols); + continue; + } + + rows[rows.length - 1] += word; + } + + pre = rows.map(r => options.trim === false ? r : r.trim()).join('\n'); + + for (const item of Array.from(pre).entries()) { + const i = item[0]; + const char = item[1]; + + ret += char; + + if (ESCAPES.has(char)) { + const code = parseFloat(/\d[^m]*/.exec(pre.slice(i, i + 4))); + escapeCode = code === END_CODE ? null : code; + } + + const code = ESCAPE_CODES.get(Number(escapeCode)); + + if (escapeCode && code) { + if (pre[i + 1] === '\n') { + ret += wrapAnsi(code); + } else if (char === '\n') { + ret += wrapAnsi(escapeCode); + } + } + } + + return ret; +}; + +// For each newline, invoke the method separately +module.exports = (str, cols, opts) => { + return String(str) + .normalize() + .split('\n') + .map(line => exec(line, cols, opts)) + .join('\n'); +}; |
