'use strict'; var stringWidth = require('string-width'); var stripAnsi = require('strip-ansi'); var ESCAPES = [ '\u001b', '\u009b' ]; var END_CODE = 39; var ESCAPE_CODES = { 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 }; function wrapAnsi(code) { return ESCAPES[0] + '[' + code + 'm'; } // calculate the length of words split on ' ', ignoring // the extra characters added by ansi escape codes. function wordLengths(str) { return str.split(' ').map(function (s) { return stringWidth(s); }); } // wrap a long word across multiple rows. // ansi escape codes do not count towards length. function wrapWord(rows, word, cols) { var insideEscape = false; var visible = stripAnsi(rows[rows.length - 1]).length; for (var i = 0; i < word.length; i++) { var x = word[i]; rows[rows.length - 1] += x; if (ESCAPES.indexOf(x) !== -1) { insideEscape = true; } else if (insideEscape && x === 'm') { insideEscape = false; continue; } if (insideEscape) { continue; } visible++; if (visible >= cols && i < word.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. function exec(str, cols, opts) { var options = opts || {}; var pre = ''; var ret = ''; var escapeCode; var lengths = wordLengths(str); var words = str.split(' '); var rows = ['']; for (var i = 0, word; (word = words[i]) !== undefined; i++) { var rowLength = stringWidth(rows[rows.length - 1]); if (rowLength) { 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(''); } rows[rows.length - 1] += word; } pre = rows.map(function (r) { return r.trim(); }).join('\n'); for (var j = 0; j < pre.length; j++) { var y = pre[j]; ret += y; if (ESCAPES.indexOf(y) !== -1) { var code = parseFloat(/[0-9][^m]*/.exec(pre.slice(j, j + 4))); escapeCode = code === END_CODE ? null : code; } if (escapeCode && ESCAPE_CODES[escapeCode]) { if (pre[j + 1] === '\n') { ret += wrapAnsi(ESCAPE_CODES[escapeCode]); } else if (y === '\n') { ret += wrapAnsi(escapeCode); } } } return ret; } // for each line break, invoke the method separately. module.exports = function (str, cols, opts) { return String(str).split('\n').map(function (substr) { return exec(substr, cols, opts); }).join('\n'); };