wrap-ansi 8.0.1 → 9.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 (4) hide show
  1. package/index.d.ts +41 -0
  2. package/index.js +20 -12
  3. package/package.json +16 -11
  4. package/readme.md +6 -22
package/index.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export type Options = {
2
+ /**
3
+ By default the wrap is soft, meaning long words may extend past the column width. Setting this to `true` will make it hard wrap at the column width.
4
+
5
+ @default false
6
+ */
7
+ readonly hard?: boolean;
8
+
9
+ /**
10
+ By default, an attempt is made to split words at spaces, ensuring that they don't extend past the configured columns. If wordWrap is `false`, each column will instead be completely filled splitting words as necessary.
11
+
12
+ @default true
13
+ */
14
+ readonly wordWrap?: boolean;
15
+
16
+ /**
17
+ Whitespace on all lines is removed by default. Set this option to `false` if you don't want to trim.
18
+
19
+ @default true
20
+ */
21
+ readonly trim?: boolean;
22
+ };
23
+
24
+ /**
25
+ Wrap words to the specified column width.
26
+
27
+ @param string - A string with ANSI escape codes, like one styled by [`chalk`](https://github.com/chalk/chalk). Newline characters will be normalized to `\n`.
28
+ @param columns - The number of columns to wrap the text to.
29
+
30
+ @example
31
+ ```
32
+ import chalk from 'chalk';
33
+ import wrapAnsi from 'wrap-ansi';
34
+
35
+ const input = 'The quick brown ' + chalk.red('fox jumped over ') +
36
+ 'the lazy ' + chalk.green('dog and then ran away with the unicorn.');
37
+
38
+ console.log(wrapAnsi(input, 20));
39
+ ```
40
+ */
41
+ export default function wrapAnsi(string: string, columns: number, options?: Options): string;
package/index.js CHANGED
@@ -15,7 +15,7 @@ const ANSI_SGR_TERMINATOR = 'm';
15
15
  const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
16
16
 
17
17
  const wrapAnsiCode = code => `${ESCAPES.values().next().value}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
18
- const wrapAnsiHyperlink = uri => `${ESCAPES.values().next().value}${ANSI_ESCAPE_LINK}${uri}${ANSI_ESCAPE_BELL}`;
18
+ const wrapAnsiHyperlink = url => `${ESCAPES.values().next().value}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
19
19
 
20
20
  // Calculate the length of words split on ' ', ignoring
21
21
  // the extra characters added by ansi escape codes
@@ -28,7 +28,7 @@ const wrapWord = (rows, word, columns) => {
28
28
 
29
29
  let isInsideEscape = false;
30
30
  let isInsideLinkEscape = false;
31
- let visible = stringWidth(stripAnsi(rows[rows.length - 1]));
31
+ let visible = stringWidth(stripAnsi(rows.at(-1)));
32
32
 
33
33
  for (const [index, character] of characters.entries()) {
34
34
  const characterLength = stringWidth(character);
@@ -42,7 +42,9 @@ const wrapWord = (rows, word, columns) => {
42
42
 
43
43
  if (ESCAPES.has(character)) {
44
44
  isInsideEscape = true;
45
- isInsideLinkEscape = characters.slice(index + 1).join('').startsWith(ANSI_ESCAPE_LINK);
45
+
46
+ const ansiEscapeLinkCandidate = characters.slice(index + 1, index + 1 + ANSI_ESCAPE_LINK.length).join('');
47
+ isInsideLinkEscape = ansiEscapeLinkCandidate === ANSI_ESCAPE_LINK;
46
48
  }
47
49
 
48
50
  if (isInsideEscape) {
@@ -68,7 +70,7 @@ const wrapWord = (rows, word, columns) => {
68
70
 
69
71
  // It's possible that the last row we copy over is only
70
72
  // ansi escape characters, handle this edge-case
71
- if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) {
73
+ if (!visible && rows.at(-1).length > 0 && rows.length > 1) {
72
74
  rows[rows.length - 2] += rows.pop();
73
75
  }
74
76
  };
@@ -93,11 +95,11 @@ const stringVisibleTrimSpacesRight = string => {
93
95
  return words.slice(0, last).join(' ') + words.slice(last).join('');
94
96
  };
95
97
 
96
- // The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode
98
+ // The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode.
97
99
  //
98
- // 'hard' will never allow a string to take up more than columns characters
100
+ // 'hard' will never allow a string to take up more than columns characters.
99
101
  //
100
- // 'soft' allows long words to expand past the column length
102
+ // 'soft' allows long words to expand past the column length.
101
103
  const exec = (string, columns, options = {}) => {
102
104
  if (options.trim !== false && string.trim() === '') {
103
105
  return '';
@@ -112,10 +114,10 @@ const exec = (string, columns, options = {}) => {
112
114
 
113
115
  for (const [index, word] of string.split(' ').entries()) {
114
116
  if (options.trim !== false) {
115
- rows[rows.length - 1] = rows[rows.length - 1].trimStart();
117
+ rows[rows.length - 1] = rows.at(-1).trimStart();
116
118
  }
117
119
 
118
- let rowLength = stringWidth(rows[rows.length - 1]);
120
+ let rowLength = stringWidth(rows.at(-1));
119
121
 
120
122
  if (index !== 0) {
121
123
  if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
@@ -164,13 +166,17 @@ const exec = (string, columns, options = {}) => {
164
166
  rows = rows.map(row => stringVisibleTrimSpacesRight(row));
165
167
  }
166
168
 
167
- const pre = [...rows.join('\n')];
169
+ const preString = rows.join('\n');
170
+ const pre = [...preString];
171
+
172
+ // We need to keep a separate index as `String#slice()` works on Unicode code units, while `pre` is an array of codepoints.
173
+ let preStringIndex = 0;
168
174
 
169
175
  for (const [index, character] of pre.entries()) {
170
176
  returnValue += character;
171
177
 
172
178
  if (ESCAPES.has(character)) {
173
- const {groups} = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`).exec(pre.slice(index).join('')) || {groups: {}};
179
+ const {groups} = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`).exec(preString.slice(preStringIndex)) || {groups: {}};
174
180
  if (groups.code !== undefined) {
175
181
  const code = Number.parseFloat(groups.code);
176
182
  escapeCode = code === END_CODE ? undefined : code;
@@ -198,6 +204,8 @@ const exec = (string, columns, options = {}) => {
198
204
  returnValue += wrapAnsiHyperlink(escapeUrl);
199
205
  }
200
206
  }
207
+
208
+ preStringIndex += character.length;
201
209
  }
202
210
 
203
211
  return returnValue;
@@ -207,7 +215,7 @@ const exec = (string, columns, options = {}) => {
207
215
  export default function wrapAnsi(string, columns, options) {
208
216
  return String(string)
209
217
  .normalize()
210
- .replace(/\r\n/g, '\n')
218
+ .replaceAll('\r\n', '\n')
211
219
  .split('\n')
212
220
  .map(line => exec(line, columns, options))
213
221
  .join('\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrap-ansi",
3
- "version": "8.0.1",
3
+ "version": "9.0.0",
4
4
  "description": "Wordwrap a string with ANSI escape codes",
5
5
  "license": "MIT",
6
6
  "repository": "chalk/wrap-ansi",
@@ -11,15 +11,19 @@
11
11
  "url": "https://sindresorhus.com"
12
12
  },
13
13
  "type": "module",
14
- "exports": "./index.js",
14
+ "exports": {
15
+ "types": "./index.d.ts",
16
+ "default": "./index.js"
17
+ },
15
18
  "engines": {
16
- "node": ">=12"
19
+ "node": ">=18"
17
20
  },
18
21
  "scripts": {
19
- "test": "xo && nyc ava"
22
+ "test": "xo && nyc ava && tsd"
20
23
  },
21
24
  "files": [
22
- "index.js"
25
+ "index.js",
26
+ "index.d.ts"
23
27
  ],
24
28
  "keywords": [
25
29
  "wrap",
@@ -49,16 +53,17 @@
49
53
  "text"
50
54
  ],
51
55
  "dependencies": {
52
- "ansi-styles": "^6.1.0",
53
- "string-width": "^5.0.1",
54
- "strip-ansi": "^7.0.1"
56
+ "ansi-styles": "^6.2.1",
57
+ "string-width": "^7.0.0",
58
+ "strip-ansi": "^7.1.0"
55
59
  },
56
60
  "devDependencies": {
57
- "ava": "^3.15.0",
58
- "chalk": "^4.1.2",
61
+ "ava": "^5.3.1",
62
+ "chalk": "^5.3.0",
59
63
  "coveralls": "^3.1.1",
60
64
  "has-ansi": "^5.0.1",
61
65
  "nyc": "^15.1.0",
62
- "xo": "^0.44.0"
66
+ "tsd": "^0.29.0",
67
+ "xo": "^0.56.0"
63
68
  }
64
69
  }
package/readme.md CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  ## Install
6
6
 
7
- ```
8
- $ npm install wrap-ansi
7
+ ```sh
8
+ npm install wrap-ansi
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -32,13 +32,15 @@ Wrap words to the specified column width.
32
32
 
33
33
  Type: `string`
34
34
 
35
- String with ANSI escape codes. Like one styled by [`chalk`](https://github.com/chalk/chalk). Newline characters will be normalized to `\n`.
35
+ A string with ANSI escape codes, like one styled by [`chalk`](https://github.com/chalk/chalk).
36
+
37
+ Newline characters will be normalized to `\n`.
36
38
 
37
39
  #### columns
38
40
 
39
41
  Type: `number`
40
42
 
41
- Number of columns to wrap the text to.
43
+ The number of columns to wrap the text to.
42
44
 
43
45
  #### options
44
46
 
@@ -71,21 +73,3 @@ Whitespace on all lines is removed by default. Set this option to `false` if you
71
73
  - [cli-truncate](https://github.com/sindresorhus/cli-truncate) - Truncate a string to a specific width in the terminal
72
74
  - [chalk](https://github.com/chalk/chalk) - Terminal string styling done right
73
75
  - [jsesc](https://github.com/mathiasbynens/jsesc) - Generate ASCII-only output from Unicode strings. Useful for creating test fixtures.
74
-
75
- ## Maintainers
76
-
77
- - [Sindre Sorhus](https://github.com/sindresorhus)
78
- - [Josh Junon](https://github.com/qix-)
79
- - [Benjamin Coe](https://github.com/bcoe)
80
-
81
- ---
82
-
83
- <div align="center">
84
- <b>
85
- <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>
86
- </b>
87
- <br>
88
- <sub>
89
- Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
90
- </sub>
91
- </div>