wrap-ansi 3.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +81 -89
  2. package/package.json +59 -60
  3. package/readme.md +13 -5
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
  const stringWidth = require('string-width');
3
3
  const stripAnsi = require('strip-ansi');
4
+ const ansiStyles = require('ansi-styles');
4
5
 
5
6
  const ESCAPES = new Set([
6
7
  '\u001B',
@@ -9,74 +10,44 @@ const ESCAPES = new Set([
9
10
 
10
11
  const END_CODE = 39;
11
12
 
12
- const ESCAPE_CODES = new Map([
13
- [0, 0],
14
- [1, 22],
15
- [2, 22],
16
- [3, 23],
17
- [4, 24],
18
- [7, 27],
19
- [8, 28],
20
- [9, 29],
21
- [30, 39],
22
- [31, 39],
23
- [32, 39],
24
- [33, 39],
25
- [34, 39],
26
- [35, 39],
27
- [36, 39],
28
- [37, 39],
29
- [90, 39],
30
- [40, 49],
31
- [41, 49],
32
- [42, 49],
33
- [43, 49],
34
- [44, 49],
35
- [45, 49],
36
- [46, 49],
37
- [47, 49]
38
- ]);
39
-
40
13
  const wrapAnsi = code => `${ESCAPES.values().next().value}[${code}m`;
41
14
 
42
15
  // Calculate the length of words split on ' ', ignoring
43
16
  // the extra characters added by ansi escape codes
44
- const wordLengths = str => str.split(' ').map(s => stringWidth(s));
17
+ const wordLengths = string => string.split(' ').map(character => stringWidth(character));
45
18
 
46
19
  // Wrap a long word across multiple rows
47
20
  // Ansi escape codes do not count towards length
48
- const wrapWord = (rows, word, cols) => {
49
- const arr = Array.from(word);
21
+ const wrapWord = (rows, word, columns) => {
22
+ const characters = [...word];
50
23
 
51
- let insideEscape = false;
24
+ let isInsideEscape = false;
52
25
  let visible = stringWidth(stripAnsi(rows[rows.length - 1]));
53
26
 
54
- for (const item of arr.entries()) {
55
- const i = item[0];
56
- const char = item[1];
57
- const charLength = stringWidth(char);
27
+ for (const [index, character] of characters.entries()) {
28
+ const characterLength = stringWidth(character);
58
29
 
59
- if (visible + charLength <= cols) {
60
- rows[rows.length - 1] += char;
30
+ if (visible + characterLength <= columns) {
31
+ rows[rows.length - 1] += character;
61
32
  } else {
62
- rows.push(char);
33
+ rows.push(character);
63
34
  visible = 0;
64
35
  }
65
36
 
66
- if (ESCAPES.has(char)) {
67
- insideEscape = true;
68
- } else if (insideEscape && char === 'm') {
69
- insideEscape = false;
37
+ if (ESCAPES.has(character)) {
38
+ isInsideEscape = true;
39
+ } else if (isInsideEscape && character === 'm') {
40
+ isInsideEscape = false;
70
41
  continue;
71
42
  }
72
43
 
73
- if (insideEscape) {
44
+ if (isInsideEscape) {
74
45
  continue;
75
46
  }
76
47
 
77
- visible += charLength;
48
+ visible += characterLength;
78
49
 
79
- if (visible === cols && i < arr.length - 1) {
50
+ if (visible === columns && index < characters.length - 1) {
80
51
  rows.push('');
81
52
  visible = 0;
82
53
  }
@@ -89,92 +60,113 @@ const wrapWord = (rows, word, cols) => {
89
60
  }
90
61
  };
91
62
 
92
- // The wrap-ansi module can be invoked
93
- // in either 'hard' or 'soft' wrap mode
63
+ // Trims spaces from a string ignoring invisible sequences
64
+ const stringVisibleTrimSpacesRight = str => {
65
+ const words = str.split(' ');
66
+ let last = words.length;
67
+
68
+ while (last > 0) {
69
+ if (stringWidth(words[last - 1]) > 0) {
70
+ break;
71
+ }
72
+
73
+ last--;
74
+ }
75
+
76
+ if (last === words.length) {
77
+ return str;
78
+ }
79
+
80
+ return words.slice(0, last).join(' ') + words.slice(last).join('');
81
+ };
82
+
83
+ // The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode
94
84
  //
95
- // 'hard' will never allow a string to take up more
96
- // than cols characters
85
+ // 'hard' will never allow a string to take up more than columns characters
97
86
  //
98
87
  // 'soft' allows long words to expand past the column length
99
- const exec = (str, cols, opts) => {
100
- const options = opts || {};
101
-
102
- if (str.trim() === '') {
103
- return options.trim === false ? str : str.trim();
88
+ const exec = (string, columns, options = {}) => {
89
+ if (options.trim !== false && string.trim() === '') {
90
+ return '';
104
91
  }
105
92
 
106
93
  let pre = '';
107
94
  let ret = '';
108
95
  let escapeCode;
109
96
 
110
- const lengths = wordLengths(str);
111
- const words = str.split(' ');
112
- const rows = [''];
97
+ const lengths = wordLengths(string);
98
+ let rows = [''];
113
99
 
114
- for (const item of Array.from(words).entries()) {
115
- const i = item[0];
116
- const word = item[1];
100
+ for (const [index, word] of string.split(' ').entries()) {
101
+ if (options.trim !== false) {
102
+ rows[rows.length - 1] = rows[rows.length - 1].trimLeft();
103
+ }
117
104
 
118
- rows[rows.length - 1] = options.trim === false ? rows[rows.length - 1] : rows[rows.length - 1].trim();
119
105
  let rowLength = stringWidth(rows[rows.length - 1]);
120
106
 
121
- if (rowLength || word === '') {
122
- if (rowLength === cols && options.wordWrap === false) {
107
+ if (index !== 0) {
108
+ if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
123
109
  // If we start with a new word but the current row length equals the length of the columns, add a new row
124
110
  rows.push('');
125
111
  rowLength = 0;
126
112
  }
127
113
 
128
- rows[rows.length - 1] += ' ';
129
- rowLength++;
114
+ if (rowLength > 0 || options.trim === false) {
115
+ rows[rows.length - 1] += ' ';
116
+ rowLength++;
117
+ }
130
118
  }
131
119
 
132
- // In 'hard' wrap mode, the length of a line is
133
- // never allowed to extend past 'cols'
134
- if (lengths[i] > cols && options.hard) {
135
- if (rowLength) {
120
+ // In 'hard' wrap mode, the length of a line is never allowed to extend past 'columns'
121
+ if (options.hard && lengths[index] > columns) {
122
+ const remainingColumns = (columns - rowLength);
123
+ const breaksStartingThisLine = 1 + Math.floor((lengths[index] - remainingColumns - 1) / columns);
124
+ const breaksStartingNextLine = Math.floor((lengths[index] - 1) / columns);
125
+ if (breaksStartingNextLine < breaksStartingThisLine) {
136
126
  rows.push('');
137
127
  }
138
- wrapWord(rows, word, cols);
128
+
129
+ wrapWord(rows, word, columns);
139
130
  continue;
140
131
  }
141
132
 
142
- if (rowLength + lengths[i] > cols && rowLength > 0) {
143
- if (options.wordWrap === false && rowLength < cols) {
144
- wrapWord(rows, word, cols);
133
+ if (rowLength + lengths[index] > columns && rowLength > 0 && lengths[index] > 0) {
134
+ if (options.wordWrap === false && rowLength < columns) {
135
+ wrapWord(rows, word, columns);
145
136
  continue;
146
137
  }
147
138
 
148
139
  rows.push('');
149
140
  }
150
141
 
151
- if (rowLength + lengths[i] > cols && options.wordWrap === false) {
152
- wrapWord(rows, word, cols);
142
+ if (rowLength + lengths[index] > columns && options.wordWrap === false) {
143
+ wrapWord(rows, word, columns);
153
144
  continue;
154
145
  }
155
146
 
156
147
  rows[rows.length - 1] += word;
157
148
  }
158
149
 
159
- pre = rows.map(r => options.trim === false ? r : r.trim()).join('\n');
150
+ if (options.trim !== false) {
151
+ rows = rows.map(stringVisibleTrimSpacesRight);
152
+ }
160
153
 
161
- for (const item of Array.from(pre).entries()) {
162
- const i = item[0];
163
- const char = item[1];
154
+ pre = rows.join('\n');
164
155
 
165
- ret += char;
156
+ for (const [index, character] of [...pre].entries()) {
157
+ ret += character;
166
158
 
167
- if (ESCAPES.has(char)) {
168
- const code = parseFloat(/\d[^m]*/.exec(pre.slice(i, i + 4)));
159
+ if (ESCAPES.has(character)) {
160
+ const code = parseFloat(/\d[^m]*/.exec(pre.slice(index, index + 4)));
169
161
  escapeCode = code === END_CODE ? null : code;
170
162
  }
171
163
 
172
- const code = ESCAPE_CODES.get(Number(escapeCode));
164
+ const code = ansiStyles.codes.get(Number(escapeCode));
173
165
 
174
166
  if (escapeCode && code) {
175
- if (pre[i + 1] === '\n') {
167
+ if (pre[index + 1] === '\n') {
176
168
  ret += wrapAnsi(code);
177
- } else if (char === '\n') {
169
+ } else if (character === '\n') {
178
170
  ret += wrapAnsi(escapeCode);
179
171
  }
180
172
  }
@@ -184,10 +176,10 @@ const exec = (str, cols, opts) => {
184
176
  };
185
177
 
186
178
  // For each newline, invoke the method separately
187
- module.exports = (str, cols, opts) => {
188
- return String(str)
179
+ module.exports = (string, columns, options) => {
180
+ return String(string)
189
181
  .normalize()
190
182
  .split('\n')
191
- .map(line => exec(line, cols, opts))
183
+ .map(line => exec(line, columns, options))
192
184
  .join('\n');
193
185
  };
package/package.json CHANGED
@@ -1,62 +1,61 @@
1
1
  {
2
- "name": "wrap-ansi",
3
- "version": "3.0.1",
4
- "description": "Wordwrap a string with ANSI escape codes",
5
- "license": "MIT",
6
- "repository": "chalk/wrap-ansi",
7
- "author": {
8
- "name": "Sindre Sorhus",
9
- "email": "sindresorhus@gmail.com",
10
- "url": "sindresorhus.com"
11
- },
12
- "engines": {
13
- "node": ">=4"
14
- },
15
- "scripts": {
16
- "test": "xo && nyc ava",
17
- "coveralls": "nyc report --reporter=text-lcov | coveralls"
18
- },
19
- "files": [
20
- "index.js"
21
- ],
22
- "keywords": [
23
- "wrap",
24
- "break",
25
- "wordwrap",
26
- "wordbreak",
27
- "linewrap",
28
- "ansi",
29
- "styles",
30
- "color",
31
- "colour",
32
- "colors",
33
- "terminal",
34
- "console",
35
- "cli",
36
- "string",
37
- "tty",
38
- "escape",
39
- "formatting",
40
- "rgb",
41
- "256",
42
- "shell",
43
- "xterm",
44
- "log",
45
- "logging",
46
- "command-line",
47
- "text"
48
- ],
49
- "dependencies": {
50
- "string-width": "^2.1.1",
51
- "strip-ansi": "^4.0.0"
52
- },
53
- "devDependencies": {
54
- "ava": "^0.21.0",
55
- "chalk": "^2.0.1",
56
- "coveralls": "^2.11.4",
57
- "has-ansi": "^3.0.0",
58
- "nyc": "^11.0.3",
59
- "strip-ansi": "^4.0.0",
60
- "xo": "^0.18.2"
61
- }
2
+ "name": "wrap-ansi",
3
+ "version": "6.0.0",
4
+ "description": "Wordwrap a string with ANSI escape codes",
5
+ "license": "MIT",
6
+ "repository": "chalk/wrap-ansi",
7
+ "author": {
8
+ "name": "Sindre Sorhus",
9
+ "email": "sindresorhus@gmail.com",
10
+ "url": "sindresorhus.com"
11
+ },
12
+ "engines": {
13
+ "node": ">=8"
14
+ },
15
+ "scripts": {
16
+ "test": "xo && nyc ava"
17
+ },
18
+ "files": [
19
+ "index.js"
20
+ ],
21
+ "keywords": [
22
+ "wrap",
23
+ "break",
24
+ "wordwrap",
25
+ "wordbreak",
26
+ "linewrap",
27
+ "ansi",
28
+ "styles",
29
+ "color",
30
+ "colour",
31
+ "colors",
32
+ "terminal",
33
+ "console",
34
+ "cli",
35
+ "string",
36
+ "tty",
37
+ "escape",
38
+ "formatting",
39
+ "rgb",
40
+ "256",
41
+ "shell",
42
+ "xterm",
43
+ "log",
44
+ "logging",
45
+ "command-line",
46
+ "text"
47
+ ],
48
+ "dependencies": {
49
+ "ansi-styles": "^4.0.0",
50
+ "string-width": "^4.1.0",
51
+ "strip-ansi": "^5.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "ava": "^2.1.0",
55
+ "chalk": "^2.4.2",
56
+ "coveralls": "^3.0.3",
57
+ "has-ansi": "^3.0.0",
58
+ "nyc": "^14.1.1",
59
+ "xo": "^0.24.0"
60
+ }
62
61
  }
package/readme.md CHANGED
@@ -27,11 +27,11 @@ console.log(wrapAnsi(input, 20));
27
27
 
28
28
  ## API
29
29
 
30
- ### wrapAnsi(input, columns, [options])
30
+ ### wrapAnsi(string, columns, options?)
31
31
 
32
32
  Wrap words to the specified column width.
33
33
 
34
- #### input
34
+ #### string
35
35
 
36
36
  Type: `string`
37
37
 
@@ -45,7 +45,7 @@ Number of columns to wrap the text to.
45
45
 
46
46
  #### options
47
47
 
48
- Type: `Object`
48
+ Type: `object`
49
49
 
50
50
  ##### hard
51
51
 
@@ -84,6 +84,14 @@ Whitespace on all lines is removed by default. Set this option to `false` if you
84
84
  - [Benjamin Coe](https://github.com/bcoe)
85
85
 
86
86
 
87
- ## License
87
+ ---
88
88
 
89
- MIT
89
+ <div align="center">
90
+ <b>
91
+ <a href="https://tidelift.com/subscription/pkg/npm-wrap_ansi?utm_source=npm-wrap-ansi&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
92
+ </b>
93
+ <br>
94
+ <sub>
95
+ Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
96
+ </sub>
97
+ </div>