123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. 'use strict';
  2. var stringWidth = require('string-width');
  3. var stripAnsi = require('strip-ansi');
  4. var ESCAPES = [
  5. '\u001b',
  6. '\u009b'
  7. ];
  8. var END_CODE = 39;
  9. var ESCAPE_CODES = {
  10. 0: 0,
  11. 1: 22,
  12. 2: 22,
  13. 3: 23,
  14. 4: 24,
  15. 7: 27,
  16. 8: 28,
  17. 9: 29,
  18. 30: 39,
  19. 31: 39,
  20. 32: 39,
  21. 33: 39,
  22. 34: 39,
  23. 35: 39,
  24. 36: 39,
  25. 37: 39,
  26. 90: 39,
  27. 40: 49,
  28. 41: 49,
  29. 42: 49,
  30. 43: 49,
  31. 44: 49,
  32. 45: 49,
  33. 46: 49,
  34. 47: 49
  35. };
  36. function wrapAnsi(code) {
  37. return ESCAPES[0] + '[' + code + 'm';
  38. }
  39. // calculate the length of words split on ' ', ignoring
  40. // the extra characters added by ansi escape codes.
  41. function wordLengths(str) {
  42. return str.split(' ').map(function (s) {
  43. return stringWidth(s);
  44. });
  45. }
  46. // wrap a long word across multiple rows.
  47. // ansi escape codes do not count towards length.
  48. function wrapWord(rows, word, cols) {
  49. var insideEscape = false;
  50. var visible = stripAnsi(rows[rows.length - 1]).length;
  51. for (var i = 0; i < word.length; i++) {
  52. var x = word[i];
  53. rows[rows.length - 1] += x;
  54. if (ESCAPES.indexOf(x) !== -1) {
  55. insideEscape = true;
  56. } else if (insideEscape && x === 'm') {
  57. insideEscape = false;
  58. continue;
  59. }
  60. if (insideEscape) {
  61. continue;
  62. }
  63. visible++;
  64. if (visible >= cols && i < word.length - 1) {
  65. rows.push('');
  66. visible = 0;
  67. }
  68. }
  69. // it's possible that the last row we copy over is only
  70. // ansi escape characters, handle this edge-case.
  71. if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) {
  72. rows[rows.length - 2] += rows.pop();
  73. }
  74. }
  75. // the wrap-ansi module can be invoked
  76. // in either 'hard' or 'soft' wrap mode.
  77. //
  78. // 'hard' will never allow a string to take up more
  79. // than cols characters.
  80. //
  81. // 'soft' allows long words to expand past the column length.
  82. function exec(str, cols, opts) {
  83. var options = opts || {};
  84. var pre = '';
  85. var ret = '';
  86. var escapeCode;
  87. var lengths = wordLengths(str);
  88. var words = str.split(' ');
  89. var rows = [''];
  90. for (var i = 0, word; (word = words[i]) !== undefined; i++) {
  91. var rowLength = stringWidth(rows[rows.length - 1]);
  92. if (rowLength) {
  93. rows[rows.length - 1] += ' ';
  94. rowLength++;
  95. }
  96. // in 'hard' wrap mode, the length of a line is
  97. // never allowed to extend past 'cols'.
  98. if (lengths[i] > cols && options.hard) {
  99. if (rowLength) {
  100. rows.push('');
  101. }
  102. wrapWord(rows, word, cols);
  103. continue;
  104. }
  105. if (rowLength + lengths[i] > cols && rowLength > 0) {
  106. if (options.wordWrap === false && rowLength < cols) {
  107. wrapWord(rows, word, cols);
  108. continue;
  109. }
  110. rows.push('');
  111. }
  112. rows[rows.length - 1] += word;
  113. }
  114. pre = rows.map(function (r) {
  115. return r.trim();
  116. }).join('\n');
  117. for (var j = 0; j < pre.length; j++) {
  118. var y = pre[j];
  119. ret += y;
  120. if (ESCAPES.indexOf(y) !== -1) {
  121. var code = parseFloat(/[0-9][^m]*/.exec(pre.slice(j, j + 4)));
  122. escapeCode = code === END_CODE ? null : code;
  123. }
  124. if (escapeCode && ESCAPE_CODES[escapeCode]) {
  125. if (pre[j + 1] === '\n') {
  126. ret += wrapAnsi(ESCAPE_CODES[escapeCode]);
  127. } else if (y === '\n') {
  128. ret += wrapAnsi(escapeCode);
  129. }
  130. }
  131. }
  132. return ret;
  133. }
  134. // for each line break, invoke the method separately.
  135. module.exports = function (str, cols, opts) {
  136. return String(str).split('\n').map(function (substr) {
  137. return exec(substr, cols, opts);
  138. }).join('\n');
  139. };