@node-core/utils 7.1.0 → 7.2.1

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 (232) hide show
  1. package/components/metadata.js +2 -1
  2. package/lib/ci/build-types/test_build.js +3 -3
  3. package/lib/ci/failure_aggregator.js +2 -2
  4. package/lib/ci/run_ci.js +3 -2
  5. package/lib/cli.js +6 -6
  6. package/lib/figures.js +6 -6
  7. package/lib/prepare_security.js +20 -21
  8. package/lib/request.js +1 -1
  9. package/lib/update-v8/backport.js +3 -4
  10. package/lib/utils.js +4 -2
  11. package/lib/verbosity.js +2 -4
  12. package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/README.md +4 -4
  13. package/node_modules/@listr2/prompt-adapter-inquirer/dist/index.d.mts +21 -0
  14. package/node_modules/@listr2/prompt-adapter-inquirer/dist/index.mjs +46 -0
  15. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/LICENSE +22 -0
  16. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/index.d.ts +2 -0
  17. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/index.js +2 -0
  18. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/inquirer.d.ts +35 -0
  19. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/inquirer.js +1 -0
  20. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/utils.d.ts +34 -0
  21. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/utils.js +2 -0
  22. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/package.json +88 -0
  23. package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/package.json +25 -20
  24. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/base.d.ts +48 -3
  25. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/base.js +58 -19
  26. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/package.json +1 -1
  27. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/readme.md +36 -1
  28. package/node_modules/ansi-regex/index.js +9 -4
  29. package/node_modules/ansi-regex/package.json +1 -1
  30. package/node_modules/ansi-regex/readme.md +6 -0
  31. package/node_modules/ansi-styles/index.d.ts +12 -12
  32. package/node_modules/ansi-styles/index.js +3 -3
  33. package/node_modules/ansi-styles/package.json +4 -4
  34. package/node_modules/cli-truncate/index.d.ts +3 -3
  35. package/node_modules/cli-truncate/index.js +40 -14
  36. package/node_modules/cli-truncate/node_modules/string-width/index.js +133 -15
  37. package/node_modules/cli-truncate/node_modules/string-width/package.json +4 -4
  38. package/node_modules/cli-truncate/node_modules/string-width/readme.md +1 -1
  39. package/node_modules/cli-truncate/package.json +7 -7
  40. package/node_modules/cli-truncate/readme.md +2 -3
  41. package/node_modules/ghauth/.github/dependabot.yml +11 -3
  42. package/node_modules/ghauth/.github/workflows/depsound.yml +33 -0
  43. package/node_modules/ghauth/.github/workflows/test-and-release.yml +4 -4
  44. package/node_modules/ghauth/CHANGELOG.md +60 -0
  45. package/node_modules/ghauth/package.json +30 -6
  46. package/node_modules/js-yaml/README.md +20 -187
  47. package/node_modules/js-yaml/dist/browser/js-yaml.esm.min.mjs +1008 -946
  48. package/node_modules/js-yaml/dist/browser/js-yaml.esm.min.mjs.map +1 -1
  49. package/node_modules/js-yaml/dist/browser/js-yaml.umd.min.js +29 -23
  50. package/node_modules/js-yaml/dist/browser/js-yaml.umd.min.js.map +1 -1
  51. package/node_modules/js-yaml/dist/js-yaml.cjs.js +1043 -583
  52. package/node_modules/js-yaml/dist/js-yaml.cjs.js.map +1 -1
  53. package/node_modules/js-yaml/dist/js-yaml.d.ts +877 -133
  54. package/node_modules/js-yaml/dist/js-yaml.mjs +995 -556
  55. package/node_modules/js-yaml/dist/js-yaml.mjs.map +1 -1
  56. package/node_modules/js-yaml/package.json +8 -7
  57. package/node_modules/listr2/dist/{index.d.cts → index.d.mts} +90 -60
  58. package/node_modules/listr2/dist/{index.js → index.mjs} +496 -373
  59. package/node_modules/listr2/node_modules/string-width/index.d.ts +39 -0
  60. package/node_modules/listr2/node_modules/string-width/index.js +207 -0
  61. package/node_modules/listr2/node_modules/string-width/package.json +65 -0
  62. package/node_modules/listr2/node_modules/string-width/readme.md +66 -0
  63. package/node_modules/listr2/node_modules/wrap-ansi/index.d.ts +1 -1
  64. package/node_modules/listr2/node_modules/wrap-ansi/index.js +544 -116
  65. package/node_modules/listr2/node_modules/wrap-ansi/package.json +10 -13
  66. package/node_modules/listr2/node_modules/wrap-ansi/readme.md +2 -0
  67. package/node_modules/listr2/package.json +16 -19
  68. package/node_modules/log-update/index.d.ts +64 -1
  69. package/node_modules/log-update/index.js +268 -18
  70. package/node_modules/log-update/node_modules/string-width/index.d.ts +39 -0
  71. package/node_modules/log-update/node_modules/string-width/index.js +207 -0
  72. package/node_modules/log-update/node_modules/string-width/package.json +65 -0
  73. package/node_modules/log-update/node_modules/string-width/readme.md +66 -0
  74. package/node_modules/log-update/node_modules/wrap-ansi/index.d.ts +1 -1
  75. package/node_modules/log-update/node_modules/wrap-ansi/index.js +544 -116
  76. package/node_modules/log-update/node_modules/wrap-ansi/package.json +10 -13
  77. package/node_modules/log-update/node_modules/wrap-ansi/readme.md +2 -0
  78. package/node_modules/log-update/package.json +14 -12
  79. package/node_modules/log-update/readme.md +61 -0
  80. package/node_modules/slice-ansi/index.d.ts +2 -2
  81. package/node_modules/slice-ansi/index.js +260 -112
  82. package/node_modules/slice-ansi/package.json +10 -9
  83. package/node_modules/slice-ansi/readme.md +3 -2
  84. package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
  85. package/node_modules/smol-toml/README.md +205 -191
  86. package/node_modules/smol-toml/dist/date.d.ts +0 -1
  87. package/node_modules/smol-toml/dist/date.js +0 -1
  88. package/node_modules/smol-toml/dist/error.d.ts +0 -1
  89. package/node_modules/smol-toml/dist/error.js +0 -1
  90. package/node_modules/smol-toml/dist/extract.js +32 -65
  91. package/node_modules/smol-toml/dist/index.cjs +302 -288
  92. package/node_modules/smol-toml/dist/index.d.ts +0 -1
  93. package/node_modules/smol-toml/dist/index.js +0 -1
  94. package/node_modules/smol-toml/dist/parse.d.ts +1 -3
  95. package/node_modules/smol-toml/dist/parse.js +22 -21
  96. package/node_modules/smol-toml/dist/primitive.js +75 -76
  97. package/node_modules/smol-toml/dist/stringify.d.ts +0 -1
  98. package/node_modules/smol-toml/dist/stringify.js +43 -30
  99. package/node_modules/smol-toml/dist/struct.js +90 -93
  100. package/node_modules/smol-toml/dist/util.d.ts +1 -1
  101. package/node_modules/smol-toml/dist/util.js +36 -39
  102. package/node_modules/smol-toml/package.json +6 -6
  103. package/node_modules/strip-ansi/index.js +5 -0
  104. package/node_modules/strip-ansi/package.json +5 -5
  105. package/package.json +3 -9
  106. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.cjs +0 -127
  107. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.d.cts +0 -152
  108. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.d.ts +0 -152
  109. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.js +0 -92
  110. package/node_modules/chalk/package.json +0 -92
  111. package/node_modules/chalk/readme.md +0 -331
  112. package/node_modules/chalk/source/index.d.ts +0 -437
  113. package/node_modules/chalk/source/index.js +0 -261
  114. package/node_modules/chalk/source/utilities.js +0 -33
  115. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +0 -299
  116. package/node_modules/chalk/source/vendor/ansi-styles/index.js +0 -261
  117. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +0 -1
  118. package/node_modules/chalk/source/vendor/supports-color/browser.js +0 -32
  119. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +0 -55
  120. package/node_modules/chalk/source/vendor/supports-color/index.js +0 -206
  121. package/node_modules/colorette/LICENSE.md +0 -7
  122. package/node_modules/colorette/README.md +0 -134
  123. package/node_modules/colorette/index.cjs +0 -218
  124. package/node_modules/colorette/index.d.ts +0 -93
  125. package/node_modules/colorette/index.js +0 -150
  126. package/node_modules/colorette/package.json +0 -40
  127. package/node_modules/eventemitter3/LICENSE +0 -21
  128. package/node_modules/eventemitter3/README.md +0 -94
  129. package/node_modules/eventemitter3/dist/eventemitter3.esm.js +0 -347
  130. package/node_modules/eventemitter3/dist/eventemitter3.esm.min.js +0 -1
  131. package/node_modules/eventemitter3/dist/eventemitter3.esm.min.js.map +0 -1
  132. package/node_modules/eventemitter3/dist/eventemitter3.umd.js +0 -355
  133. package/node_modules/eventemitter3/dist/eventemitter3.umd.min.js +0 -1
  134. package/node_modules/eventemitter3/dist/eventemitter3.umd.min.js.map +0 -1
  135. package/node_modules/eventemitter3/index.d.ts +0 -135
  136. package/node_modules/eventemitter3/index.js +0 -336
  137. package/node_modules/eventemitter3/index.mjs +0 -4
  138. package/node_modules/eventemitter3/package.json +0 -67
  139. package/node_modules/figures/index.d.ts +0 -279
  140. package/node_modules/figures/index.js +0 -292
  141. package/node_modules/figures/package.json +0 -49
  142. package/node_modules/figures/readme.md +0 -337
  143. package/node_modules/isexe/LICENSE.md +0 -55
  144. package/node_modules/isexe/README.md +0 -80
  145. package/node_modules/isexe/dist/commonjs/index.d.ts +0 -14
  146. package/node_modules/isexe/dist/commonjs/index.d.ts.map +0 -1
  147. package/node_modules/isexe/dist/commonjs/index.js +0 -56
  148. package/node_modules/isexe/dist/commonjs/index.js.map +0 -1
  149. package/node_modules/isexe/dist/commonjs/index.min.js +0 -2
  150. package/node_modules/isexe/dist/commonjs/index.min.js.map +0 -7
  151. package/node_modules/isexe/dist/commonjs/options.d.ts +0 -32
  152. package/node_modules/isexe/dist/commonjs/options.d.ts.map +0 -1
  153. package/node_modules/isexe/dist/commonjs/options.js +0 -3
  154. package/node_modules/isexe/dist/commonjs/options.js.map +0 -1
  155. package/node_modules/isexe/dist/commonjs/package.json +0 -3
  156. package/node_modules/isexe/dist/commonjs/posix.d.ts +0 -18
  157. package/node_modules/isexe/dist/commonjs/posix.d.ts.map +0 -1
  158. package/node_modules/isexe/dist/commonjs/posix.js +0 -67
  159. package/node_modules/isexe/dist/commonjs/posix.js.map +0 -1
  160. package/node_modules/isexe/dist/commonjs/win32.d.ts +0 -18
  161. package/node_modules/isexe/dist/commonjs/win32.d.ts.map +0 -1
  162. package/node_modules/isexe/dist/commonjs/win32.js +0 -63
  163. package/node_modules/isexe/dist/commonjs/win32.js.map +0 -1
  164. package/node_modules/isexe/dist/esm/index.d.ts +0 -14
  165. package/node_modules/isexe/dist/esm/index.d.ts.map +0 -1
  166. package/node_modules/isexe/dist/esm/index.js +0 -16
  167. package/node_modules/isexe/dist/esm/index.js.map +0 -1
  168. package/node_modules/isexe/dist/esm/index.min.js +0 -2
  169. package/node_modules/isexe/dist/esm/index.min.js.map +0 -7
  170. package/node_modules/isexe/dist/esm/options.d.ts +0 -32
  171. package/node_modules/isexe/dist/esm/options.d.ts.map +0 -1
  172. package/node_modules/isexe/dist/esm/options.js +0 -2
  173. package/node_modules/isexe/dist/esm/options.js.map +0 -1
  174. package/node_modules/isexe/dist/esm/package.json +0 -3
  175. package/node_modules/isexe/dist/esm/posix.d.ts +0 -18
  176. package/node_modules/isexe/dist/esm/posix.d.ts.map +0 -1
  177. package/node_modules/isexe/dist/esm/posix.js +0 -62
  178. package/node_modules/isexe/dist/esm/posix.js.map +0 -1
  179. package/node_modules/isexe/dist/esm/win32.d.ts +0 -18
  180. package/node_modules/isexe/dist/esm/win32.d.ts.map +0 -1
  181. package/node_modules/isexe/dist/esm/win32.js +0 -58
  182. package/node_modules/isexe/dist/esm/win32.js.map +0 -1
  183. package/node_modules/isexe/package.json +0 -78
  184. package/node_modules/listr2/dist/index.cjs +0 -2189
  185. package/node_modules/listr2/dist/index.d.ts +0 -1528
  186. package/node_modules/rfdc/.github/workflows/ci.yml +0 -21
  187. package/node_modules/rfdc/LICENSE +0 -15
  188. package/node_modules/rfdc/default.js +0 -3
  189. package/node_modules/rfdc/index.d.ts +0 -13
  190. package/node_modules/rfdc/index.js +0 -198
  191. package/node_modules/rfdc/index.test-d.ts +0 -13
  192. package/node_modules/rfdc/package.json +0 -73
  193. package/node_modules/rfdc/readme.md +0 -181
  194. package/node_modules/rfdc/test/index.js +0 -306
  195. package/node_modules/smol-toml/dist/date.d.ts.map +0 -1
  196. package/node_modules/smol-toml/dist/date.js.map +0 -1
  197. package/node_modules/smol-toml/dist/error.d.ts.map +0 -1
  198. package/node_modules/smol-toml/dist/error.js.map +0 -1
  199. package/node_modules/smol-toml/dist/extract.js.map +0 -1
  200. package/node_modules/smol-toml/dist/index.cjs.map +0 -1
  201. package/node_modules/smol-toml/dist/index.d.ts.map +0 -1
  202. package/node_modules/smol-toml/dist/index.js.map +0 -1
  203. package/node_modules/smol-toml/dist/parse.d.ts.map +0 -1
  204. package/node_modules/smol-toml/dist/parse.js.map +0 -1
  205. package/node_modules/smol-toml/dist/primitive.d.ts +0 -29
  206. package/node_modules/smol-toml/dist/primitive.d.ts.map +0 -1
  207. package/node_modules/smol-toml/dist/primitive.js.map +0 -1
  208. package/node_modules/smol-toml/dist/stringify.d.ts.map +0 -1
  209. package/node_modules/smol-toml/dist/stringify.js.map +0 -1
  210. package/node_modules/smol-toml/dist/struct.js.map +0 -1
  211. package/node_modules/smol-toml/dist/util.d.ts.map +0 -1
  212. package/node_modules/smol-toml/dist/util.js.map +0 -1
  213. package/node_modules/smol-toml/src/date.ts +0 -152
  214. package/node_modules/smol-toml/src/error.ts +0 -77
  215. package/node_modules/smol-toml/src/extract.ts +0 -113
  216. package/node_modules/smol-toml/src/index.ts +0 -42
  217. package/node_modules/smol-toml/src/parse.ts +0 -184
  218. package/node_modules/smol-toml/src/primitive.ts +0 -249
  219. package/node_modules/smol-toml/src/stringify.ts +0 -191
  220. package/node_modules/smol-toml/src/struct.ts +0 -202
  221. package/node_modules/smol-toml/src/util.ts +0 -106
  222. package/node_modules/which/LICENSE +0 -15
  223. package/node_modules/which/README.md +0 -51
  224. package/node_modules/which/bin/which.js +0 -52
  225. package/node_modules/which/lib/index.js +0 -111
  226. package/node_modules/which/package.json +0 -52
  227. /package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/LICENSE +0 -0
  228. /package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/index.d.ts +0 -0
  229. /package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/index.js +0 -0
  230. /package/node_modules/{chalk → ansi-escapes}/license +0 -0
  231. /package/node_modules/{figures → listr2/node_modules/string-width}/license +0 -0
  232. /package/node_modules/log-update/node_modules/{ansi-escapes → string-width}/license +0 -0
@@ -1,98 +1,551 @@
1
1
  import stringWidth from 'string-width';
2
- import stripAnsi from 'strip-ansi';
3
2
  import ansiStyles from 'ansi-styles';
4
3
 
5
- const ESCAPES = new Set([
6
- '\u001B',
7
- '\u009B',
8
- ]);
9
-
10
- const END_CODE = 39;
4
+ const ANSI_ESCAPE = '\u001B';
11
5
  const ANSI_ESCAPE_BELL = '\u0007';
6
+ const C1_CSI = '\u009B';
12
7
  const ANSI_CSI = '[';
13
8
  const ANSI_OSC = ']';
14
9
  const ANSI_SGR_TERMINATOR = 'm';
15
- const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
10
+ const ANSI_SGR_RESET = 0;
11
+ const ANSI_SGR_RESET_FOREGROUND = 39;
12
+ const ANSI_SGR_RESET_BACKGROUND = 49;
13
+ const ANSI_SGR_RESET_UNDERLINE_COLOR = 59;
14
+ const ANSI_SGR_FOREGROUND_EXTENDED = 38;
15
+ const ANSI_SGR_BACKGROUND_EXTENDED = 48;
16
+ const ANSI_SGR_UNDERLINE_COLOR_EXTENDED = 58;
17
+ const ANSI_SGR_COLOR_MODE_RGB = 2;
18
+ const ANSI_SGR_COLOR_MODE_256 = 5;
19
+ const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;`;
20
+ // The first character of every sequence we recognize.
21
+ const ESCAPES = new Set([
22
+ ANSI_ESCAPE,
23
+ C1_CSI,
24
+ ]);
25
+ const ESCAPE_CHARACTERS = [...ESCAPES].join('');
26
+
27
+ const CSI_INTRODUCER = `(?:${ANSI_ESCAPE}\\${ANSI_CSI}|${C1_CSI})`;
28
+ const CSI_PARAMETERS = '[0-?]*[ -/]*[@-~]';
29
+ const SGR_PARAMETERS = `(?<sgr>[0-9;:]*)${ANSI_SGR_TERMINATOR}`;
30
+ const OSC_STRING_TERMINATOR = `(?:${ANSI_ESCAPE_BELL}|${ANSI_ESCAPE}\\\\)`;
31
+ const OSC_STRING_PAYLOAD = String.raw`[^\u0000-\u001F\u007F-\u009F]*`;
32
+ // A hyperlink is `OSC 8 ; parameters ; URI ST`, where `parameters` is a possibly empty list of `key=value` pairs joined by `:`.
33
+ const LINK_PARAMETERS = String.raw`8;(?<parameters>[^;\u0000-\u001F\u007F-\u009F]*);(?<uri>${OSC_STRING_PAYLOAD})${OSC_STRING_TERMINATOR}`;
34
+ const OSC_STRING = `${OSC_STRING_PAYLOAD}${OSC_STRING_TERMINATOR}`;
35
+
36
+ // Supported boundary: semicolon-delimited SGR styling, colon-delimited RGB/indexed colors, and OSC 8 hyperlinks are tracked, while ordinary CSI sequences and other complete 7-bit OSC commands are preserved as opaque zero-width units. This is intentionally not a terminal emulator, so other colon-delimited SGR semantics, C0 bytes inside sequences, DCS/SOS/PM/APC control strings, cancellations, generic ESC sequences, and 8-bit control-string forms are unsupported. Newlines always delimit input lines before ANSI parsing.
37
+ const ANSI_ESCAPE_REGEX = new RegExp(
38
+ `${CSI_INTRODUCER}(?:${SGR_PARAMETERS}|${CSI_PARAMETERS})`
39
+ + `|${ANSI_ESCAPE}\\${ANSI_OSC}(?:${LINK_PARAMETERS}|${OSC_STRING})`,
40
+ 'y',
41
+ );
42
+
43
+ const ANSI_SGR_MODIFIER_CLOSE_CODES = new Set(ansiStyles.codes.values());
44
+ ANSI_SGR_MODIFIER_CLOSE_CODES.delete(ANSI_SGR_RESET);
45
+
46
+ const segmenter = new Intl.Segmenter();
47
+ // Complete ANSI sequences have already been removed before measuring these strings. Avoid string-width's ANSI scan so malformed sequences are not rescanned.
48
+ const getStringWidth = string => stringWidth(string, {countAnsiEscapeCodes: true});
49
+ const TAB_SIZE = 8;
50
+
51
+ // Finds the next character that could introduce a sequence, so plain text is skipped in one native step.
52
+ const ESCAPE_INTRODUCER_REGEX = new RegExp(`[${ESCAPE_CHARACTERS}]`, 'g');
53
+ // The final pass only cares about sequences and row boundaries, so it skips everything else in one native step.
54
+ const ROW_BOUNDARY_REGEX = new RegExp(`[\\n${ESCAPE_CHARACTERS}]`, 'g');
55
+ // Every printable ASCII character is its own grapheme cluster of width one, which lets the segmenter be skipped.
56
+ const ASCII_PRINTABLE_REGEX = /^[ -~]*$/;
57
+
58
+ const wrapAnsiCode = code => `${ANSI_ESCAPE}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
59
+ const wrapAnsiHyperlink = (url, parameters = '') => `${ANSI_ESCAPE}${ANSI_ESCAPE_LINK}${parameters};${url}${ANSI_ESCAPE_BELL}`;
60
+
61
+ // Match a complete escape sequence starting at `index`, or return `undefined` when none starts there.
62
+ const matchAnsiEscape = (string, index) => {
63
+ if (!ESCAPES.has(string[index])) {
64
+ return;
65
+ }
16
66
 
17
- const wrapAnsiCode = code => `${ESCAPES.values().next().value}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
18
- const wrapAnsiHyperlink = url => `${ESCAPES.values().next().value}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
67
+ ANSI_ESCAPE_REGEX.lastIndex = index;
68
+ return ANSI_ESCAPE_REGEX.exec(string) ?? undefined;
69
+ };
19
70
 
20
- // Calculate the length of words split on ' ', ignoring
21
- // the extra characters added by ansi escape codes
22
- const wordLengths = string => string.split(' ').map(character => stringWidth(character));
71
+ // Walk a string as alternating plain text runs and complete escape sequences.
72
+ // A character that looks like an introducer but does not start a valid sequence stays plain text.
73
+ const forEachSegment = (string, onPlainText, onEscape = () => {}) => {
74
+ let plainStart = 0;
75
+ let index = 0;
23
76
 
24
- // Wrap a long word across multiple rows
25
- // Ansi escape codes do not count towards length
26
- const wrapWord = (rows, word, columns) => {
27
- const characters = [...word];
77
+ while (index < string.length) {
78
+ ESCAPE_INTRODUCER_REGEX.lastIndex = index;
79
+ const introducer = ESCAPE_INTRODUCER_REGEX.exec(string);
28
80
 
29
- let isInsideEscape = false;
30
- let isInsideLinkEscape = false;
31
- let visible = stringWidth(stripAnsi(rows.at(-1)));
81
+ if (!introducer) {
82
+ break;
83
+ }
32
84
 
33
- for (const [index, character] of characters.entries()) {
34
- const characterLength = stringWidth(character);
85
+ const escape = matchAnsiEscape(string, introducer.index);
35
86
 
36
- if (visible + characterLength <= columns) {
37
- rows[rows.length - 1] += character;
38
- } else {
39
- rows.push(character);
40
- visible = 0;
87
+ if (!escape) {
88
+ index = introducer.index + 1;
89
+ continue;
41
90
  }
42
91
 
43
- if (ESCAPES.has(character)) {
44
- isInsideEscape = true;
92
+ if (introducer.index > plainStart) {
93
+ onPlainText(string.slice(plainStart, introducer.index));
94
+ }
45
95
 
46
- const ansiEscapeLinkCandidate = characters.slice(index + 1, index + 1 + ANSI_ESCAPE_LINK.length).join('');
47
- isInsideLinkEscape = ansiEscapeLinkCandidate === ANSI_ESCAPE_LINK;
96
+ onEscape(escape[0]);
97
+ index = introducer.index + escape[0].length;
98
+ plainStart = index;
99
+ }
100
+
101
+ if (plainStart < string.length) {
102
+ onPlainText(string.slice(plainStart));
103
+ }
104
+ };
105
+
106
+ // The visible width of a string, ignoring escape sequences.
107
+ const getWidth = string => {
108
+ let plainText = '';
109
+
110
+ forEachSegment(string, part => {
111
+ plainText += part;
112
+ });
113
+
114
+ return getStringWidth(plainText);
115
+ };
116
+
117
+ // Split a string into escape sequences, which are zero width and must never be split, and grapheme clusters.
118
+ // The supported boundary is between grapheme clusters and ANSI sequences; ANSI inserted inside a cluster is treated as separate segments.
119
+ const getTokens = string => {
120
+ const tokens = [];
121
+
122
+ forEachSegment(string, plainText => {
123
+ if (ASCII_PRINTABLE_REGEX.test(plainText)) {
124
+ for (const character of plainText) {
125
+ tokens.push({value: character, width: 1});
126
+ }
127
+
128
+ return;
48
129
  }
49
130
 
50
- if (isInsideEscape) {
51
- if (isInsideLinkEscape) {
52
- if (character === ANSI_ESCAPE_BELL) {
53
- isInsideEscape = false;
54
- isInsideLinkEscape = false;
55
- }
56
- } else if (character === ANSI_SGR_TERMINATOR) {
57
- isInsideEscape = false;
131
+ for (const {segment} of segmenter.segment(plainText)) {
132
+ tokens.push({value: segment, width: getStringWidth(segment)});
133
+ }
134
+ }, escape => {
135
+ tokens.push({value: escape, width: 0});
136
+ });
137
+
138
+ return tokens;
139
+ };
140
+
141
+ // Split on spaces, ignoring spaces that appear inside a recognized sequence.
142
+ const splitWords = string => {
143
+ let currentWord = {value: '', plainText: ''};
144
+ const words = [currentWord];
145
+
146
+ forEachSegment(string, plainText => {
147
+ const parts = plainText.split(' ');
148
+ currentWord.value += parts[0];
149
+ currentWord.plainText += parts[0];
150
+
151
+ for (let index = 1; index < parts.length; index++) {
152
+ currentWord = {value: parts[index], plainText: parts[index]};
153
+ words.push(currentWord);
154
+ }
155
+ }, escape => {
156
+ currentWord.value += escape;
157
+ });
158
+
159
+ // Measured once per word rather than per run, so a grapheme cluster split by an escape still counts once.
160
+ for (const word of words) {
161
+ word.width = getStringWidth(word.plainText);
162
+ }
163
+
164
+ return words;
165
+ };
166
+
167
+ const getColonColorToken = parameter => {
168
+ const parts = parameter.split(':');
169
+ const code = Number.parseInt(parts[0], 10);
170
+ const mode = Number.parseInt(parts[1], 10);
171
+
172
+ if (![ANSI_SGR_FOREGROUND_EXTENDED, ANSI_SGR_BACKGROUND_EXTENDED, ANSI_SGR_UNDERLINE_COLOR_EXTENDED].includes(code)) {
173
+ return;
174
+ }
175
+
176
+ if (mode === ANSI_SGR_COLOR_MODE_256 && parts.length === 3 && /^\d+$/.test(parts[2])) {
177
+ return {code, open: parameter, hasArguments: true};
178
+ }
179
+
180
+ if (mode !== ANSI_SGR_COLOR_MODE_RGB) {
181
+ return;
182
+ }
183
+
184
+ const components = parts.length === 6 ? parts.slice(3) : parts.slice(2);
185
+ const colorSpace = parts.length === 6 ? parts[2] : undefined;
186
+ if (components.length === 3 && components.every(component => /^\d+$/.test(component)) && (colorSpace === undefined || /^\d*$/.test(colorSpace))) {
187
+ return {code, open: parameter, hasArguments: true};
188
+ }
189
+ };
190
+
191
+ const getSgrTokens = sgrParameters => {
192
+ const parameters = sgrParameters.split(';');
193
+ const sgrTokens = [];
194
+
195
+ for (let index = 0; index < parameters.length; index++) {
196
+ const parameter = parameters[index];
197
+ if (parameter.includes(':')) {
198
+ const colonColorToken = getColonColorToken(parameter);
199
+ if (colonColorToken) {
200
+ sgrTokens.push(colonColorToken);
58
201
  }
59
202
 
60
203
  continue;
61
204
  }
62
205
 
63
- visible += characterLength;
206
+ const code = parameter === '' ? ANSI_SGR_RESET : Number.parseInt(parameter, 10);
207
+
208
+ if (!Number.isFinite(code)) {
209
+ continue;
210
+ }
211
+
212
+ if (code === ANSI_SGR_FOREGROUND_EXTENDED || code === ANSI_SGR_BACKGROUND_EXTENDED || code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED) {
213
+ if (index + 1 >= parameters.length) {
214
+ break;
215
+ }
216
+
217
+ const mode = Number.parseInt(parameters[index + 1], 10);
218
+ const colorIndex = Number.parseInt(parameters[index + 2], 10);
219
+ if (mode === ANSI_SGR_COLOR_MODE_256 && Number.isFinite(colorIndex)) {
220
+ sgrTokens.push({code, open: [code, mode, colorIndex].join(';'), hasArguments: true});
221
+ index += 2;
222
+ continue;
223
+ }
224
+
225
+ const red = Number.parseInt(parameters[index + 2], 10);
226
+ const green = Number.parseInt(parameters[index + 3], 10);
227
+ const blue = Number.parseInt(parameters[index + 4], 10);
228
+ if (mode === ANSI_SGR_COLOR_MODE_RGB && Number.isFinite(red) && Number.isFinite(green) && Number.isFinite(blue)) {
229
+ sgrTokens.push({code, open: [code, mode, red, green, blue].join(';'), hasArguments: true});
230
+ index += 4;
231
+ continue;
232
+ }
233
+
234
+ break;
235
+ }
236
+
237
+ sgrTokens.push({code, open: String(code), hasArguments: false});
238
+ }
239
+
240
+ return sgrTokens;
241
+ };
242
+
243
+ const removeActiveStyle = (activeStyles, family) => {
244
+ const activeStyleIndex = activeStyles.findIndex(activeStyle => activeStyle.family === family);
245
+
246
+ if (activeStyleIndex !== -1) {
247
+ activeStyles.splice(activeStyleIndex, 1);
248
+ }
249
+ };
250
+
251
+ const upsertActiveStyle = (activeStyles, nextActiveStyle) => {
252
+ removeActiveStyle(activeStyles, nextActiveStyle.family);
253
+ activeStyles.push(nextActiveStyle);
254
+ };
255
+
256
+ const removeModifierStylesByClose = (activeStyles, closeCode) => {
257
+ for (let index = activeStyles.length - 1; index >= 0; index--) {
258
+ const activeStyle = activeStyles[index];
259
+ if (activeStyle.family.startsWith('modifier-') && activeStyle.close === closeCode) {
260
+ activeStyles.splice(index, 1);
261
+ }
262
+ }
263
+ };
264
+
265
+ const getColorStyle = sgrToken => {
266
+ const {code, open, hasArguments} = sgrToken;
267
+ if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97) || (code === ANSI_SGR_FOREGROUND_EXTENDED && hasArguments)) {
268
+ return {
269
+ family: 'foreground',
270
+ open,
271
+ close: ANSI_SGR_RESET_FOREGROUND,
272
+ };
273
+ }
274
+
275
+ if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107) || (code === ANSI_SGR_BACKGROUND_EXTENDED && hasArguments)) {
276
+ return {
277
+ family: 'background',
278
+ open,
279
+ close: ANSI_SGR_RESET_BACKGROUND,
280
+ };
281
+ }
282
+
283
+ if (code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED && hasArguments) {
284
+ return {
285
+ family: 'underlineColor',
286
+ open,
287
+ close: ANSI_SGR_RESET_UNDERLINE_COLOR,
288
+ };
289
+ }
290
+ };
291
+
292
+ const applySgrResetCode = (code, activeStyles) => {
293
+ if (code === ANSI_SGR_RESET) {
294
+ activeStyles.length = 0;
295
+ return true;
296
+ }
297
+
298
+ if (code === ANSI_SGR_RESET_FOREGROUND) {
299
+ removeActiveStyle(activeStyles, 'foreground');
300
+ return true;
301
+ }
302
+
303
+ if (code === ANSI_SGR_RESET_BACKGROUND) {
304
+ removeActiveStyle(activeStyles, 'background');
305
+ return true;
306
+ }
307
+
308
+ if (code === ANSI_SGR_RESET_UNDERLINE_COLOR) {
309
+ removeActiveStyle(activeStyles, 'underlineColor');
310
+ return true;
311
+ }
312
+
313
+ if (ANSI_SGR_MODIFIER_CLOSE_CODES.has(code)) {
314
+ removeModifierStylesByClose(activeStyles, code);
315
+ return true;
316
+ }
317
+
318
+ return false;
319
+ };
320
+
321
+ const applySgrToken = (sgrToken, activeStyles) => {
322
+ const {code} = sgrToken;
323
+
324
+ if (applySgrResetCode(code, activeStyles)) {
325
+ return;
326
+ }
327
+
328
+ const colorStyle = getColorStyle(sgrToken);
329
+ if (colorStyle) {
330
+ upsertActiveStyle(activeStyles, colorStyle);
331
+ return;
332
+ }
333
+
334
+ const close = ansiStyles.codes.get(code);
335
+ if (close !== undefined && close !== ANSI_SGR_RESET) {
336
+ upsertActiveStyle(activeStyles, {
337
+ family: `modifier-${code}`,
338
+ open: sgrToken.open,
339
+ close,
340
+ });
341
+ }
342
+ };
343
+
344
+ const applySgrParameters = (sgrParameters, activeStyles) => {
345
+ for (const sgrToken of getSgrTokens(sgrParameters)) {
346
+ applySgrToken(sgrToken, activeStyles);
347
+ }
348
+ };
349
+
350
+ const applySgrResets = (sgrParameters, activeStyles) => {
351
+ for (const {code} of getSgrTokens(sgrParameters)) {
352
+ applySgrResetCode(code, activeStyles);
353
+ }
354
+ };
355
+
356
+ const applyLeadingSgrResets = (string, startIndex, activeStyles) => {
357
+ let index = startIndex;
358
+
359
+ while (index < string.length) {
360
+ const match = matchAnsiEscape(string, index);
361
+ if (!match) {
362
+ break;
363
+ }
364
+
365
+ if (match.groups.sgr !== undefined) {
366
+ applySgrResets(match.groups.sgr, activeStyles);
367
+ }
368
+
369
+ index += match[0].length;
370
+ }
371
+ };
64
372
 
65
- if (visible === columns && index < characters.length - 1) {
373
+ const getClosingSgrSequence = activeStyles => [...activeStyles].reverse().map(activeStyle => wrapAnsiCode(activeStyle.close)).join('');
374
+ const getOpeningSgrSequence = activeStyles => activeStyles.map(activeStyle => wrapAnsiCode(activeStyle.open)).join('');
375
+
376
+ // Wrap a long word across multiple rows
377
+ // ANSI escape codes do not count towards length
378
+ // Takes the visible width of the last row and returns the width of the row the word ends on, so callers never have to measure the rows themselves.
379
+ const wrapWord = (rows, word, columns, rowWidth) => {
380
+ const tokens = getTokens(word);
381
+
382
+ let visible = rowWidth;
383
+
384
+ for (let index = 0; index < tokens.length; index++) {
385
+ const token = tokens[index];
386
+
387
+ // Escape sequences and combining marks are zero width, so they always stay on the current row.
388
+ if (token.width > 0 && visible > 0 && visible + token.width > columns) {
389
+ rows.push('');
390
+ visible = 0;
391
+ }
392
+
393
+ rows[rows.length - 1] += token.value;
394
+ visible += token.width;
395
+
396
+ if (visible === columns && index < tokens.length - 1) {
66
397
  rows.push('');
67
398
  visible = 0;
68
399
  }
69
400
  }
70
401
 
71
402
  // It's possible that the last row we copy over is only
72
- // ansi escape characters, handle this edge-case
403
+ // ANSI escape characters, handle this edge-case
73
404
  if (!visible && rows.at(-1).length > 0 && rows.length > 1) {
74
405
  rows[rows.length - 2] += rows.pop();
75
406
  }
407
+
408
+ // The tokens are measured one by one, so a grapheme cluster that an escape sequence splits is counted once per part rather than once as a whole. Only the finished row tells the true width, and it is at most one row long to measure.
409
+ return getWidth(rows.at(-1));
76
410
  };
77
411
 
78
412
  // Trims spaces from a string ignoring invisible sequences
79
413
  const stringVisibleTrimSpacesRight = string => {
80
- const words = string.split(' ');
81
- let last = words.length;
414
+ if (!string.includes(' ')) {
415
+ return string;
416
+ }
82
417
 
83
- while (last > 0) {
84
- if (stringWidth(words[last - 1]) > 0) {
418
+ const segments = [];
419
+ forEachSegment(string, plainText => {
420
+ segments.push({value: plainText, isEscape: false});
421
+ }, escape => {
422
+ segments.push({value: escape, isEscape: true});
423
+ });
424
+
425
+ // Drop the spaces that trail the last visible character, but keep the invisible sequences among them.
426
+ for (let index = segments.length - 1; index >= 0; index--) {
427
+ const segment = segments[index];
428
+
429
+ if (segment.isEscape) {
430
+ continue;
431
+ }
432
+
433
+ // Scanned rather than matched with a regex, as a trailing-space pattern backtracks quadratically.
434
+ let end = segment.value.length;
435
+ while (end > 0 && segment.value[end - 1] === ' ') {
436
+ end--;
437
+ }
438
+
439
+ segment.value = segment.value.slice(0, end);
440
+
441
+ if (getStringWidth(segment.value) > 0) {
85
442
  break;
86
443
  }
444
+ }
445
+
446
+ return segments.map(segment => segment.value).join('');
447
+ };
87
448
 
88
- last--;
449
+ const expandTabs = line => {
450
+ if (!line.includes('\t')) {
451
+ return line;
89
452
  }
90
453
 
91
- if (last === words.length) {
92
- return string;
454
+ let visible = 0;
455
+ let expandedLine = '';
456
+ let plainTextSinceTab = '';
457
+
458
+ const expandPlainText = plainText => {
459
+ const segments = plainText.split('\t');
460
+
461
+ for (const [index, segment] of segments.entries()) {
462
+ expandedLine += segment;
463
+ plainTextSinceTab += segment;
464
+
465
+ if (index < segments.length - 1) {
466
+ visible += getStringWidth(plainTextSinceTab);
467
+ plainTextSinceTab = '';
468
+ const spaces = TAB_SIZE - (visible % TAB_SIZE);
469
+ expandedLine += ' '.repeat(spaces);
470
+ visible += spaces;
471
+ }
472
+ }
473
+ };
474
+
475
+ forEachSegment(line, expandPlainText, escape => {
476
+ expandedLine += escape;
477
+ });
478
+
479
+ return expandedLine;
480
+ };
481
+
482
+ // Close the active styles and hyperlink before every row break and reopen them after, so each row stands on its own.
483
+ // Only sequences and newlines matter here, so the string is scanned directly rather than split into grapheme clusters.
484
+ const restoreStylesAcrossRows = preString => {
485
+ let returnValue = '';
486
+ let activeHyperlink;
487
+ const activeStyles = [];
488
+ let index = 0;
489
+ let copiedIndex = 0;
490
+
491
+ while (index < preString.length) {
492
+ ROW_BOUNDARY_REGEX.lastIndex = index;
493
+ const boundary = ROW_BOUNDARY_REGEX.exec(preString);
494
+
495
+ if (!boundary) {
496
+ break;
497
+ }
498
+
499
+ index = boundary.index;
500
+
501
+ if (boundary[0] !== '\n') {
502
+ const escape = matchAnsiEscape(preString, index);
503
+
504
+ if (!escape) {
505
+ index++;
506
+ continue;
507
+ }
508
+
509
+ const {groups} = escape;
510
+ if (groups.sgr !== undefined) {
511
+ applySgrParameters(groups.sgr, activeStyles);
512
+ } else if (groups.uri !== undefined) {
513
+ activeHyperlink = groups.uri.length === 0 ? undefined : {parameters: groups.parameters, uri: groups.uri};
514
+ }
515
+
516
+ index += escape[0].length;
517
+ continue;
518
+ }
519
+
520
+ // Everything up to the row break is copied verbatim, sequences included.
521
+ returnValue += preString.slice(copiedIndex, index);
522
+
523
+ // An empty row never reopened anything, so there is nothing to close.
524
+ if (index > copiedIndex) {
525
+ if (activeHyperlink) {
526
+ returnValue += wrapAnsiHyperlink('');
527
+ }
528
+
529
+ returnValue += getClosingSgrSequence(activeStyles);
530
+ }
531
+
532
+ returnValue += '\n';
533
+ index++;
534
+ copiedIndex = index;
535
+
536
+ // An empty row has nothing to style, so the styles stay closed until the next row with content. A trailing row break leaves no row at all.
537
+ if (index < preString.length && preString[index] !== '\n') {
538
+ const openingStyles = [...activeStyles];
539
+ applyLeadingSgrResets(preString, index, openingStyles);
540
+ returnValue += getOpeningSgrSequence(openingStyles);
541
+
542
+ if (activeHyperlink) {
543
+ returnValue += wrapAnsiHyperlink(activeHyperlink.uri, activeHyperlink.parameters);
544
+ }
545
+ }
93
546
  }
94
547
 
95
- return words.slice(0, last).join(' ') + words.slice(last).join('');
548
+ return returnValue + preString.slice(copiedIndex);
96
549
  };
97
550
 
98
551
  // The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode.
@@ -105,21 +558,35 @@ const exec = (string, columns, options = {}) => {
105
558
  return '';
106
559
  }
107
560
 
108
- let returnValue = '';
109
- let escapeCode;
110
- let escapeUrl;
111
-
112
- const lengths = wordLengths(string);
561
+ const words = splitWords(string);
113
562
  let rows = [''];
563
+ // Tracked as rows are built. Remeasuring the row for every word makes wrapping quadratic in the line length.
564
+ let rowLength = 0;
565
+ // Words are only ever appended, so a row that already starts with content can never become trimmable again. Retrimming it for every word makes wrapping quadratic in the line length.
566
+ let trimmedRowIndex = -1;
114
567
 
115
- for (const [index, word] of string.split(' ').entries()) {
116
- if (options.trim !== false) {
117
- rows[rows.length - 1] = rows.at(-1).trimStart();
118
- }
568
+ let isFirstWord = true;
569
+
570
+ for (const word of words) {
571
+ const rowIndex = rows.length - 1;
119
572
 
120
- let rowLength = stringWidth(rows.at(-1));
573
+ if (options.trim !== false && trimmedRowIndex !== rowIndex) {
574
+ const row = rows[rowIndex];
575
+ const trimmedRow = row.trimStart();
121
576
 
122
- if (index !== 0) {
577
+ if (trimmedRow.length !== row.length) {
578
+ rows[rowIndex] = trimmedRow;
579
+ rowLength = getWidth(trimmedRow);
580
+ }
581
+
582
+ if (trimmedRow.length > 0) {
583
+ trimmedRowIndex = rowIndex;
584
+ }
585
+ }
586
+
587
+ if (isFirstWord) {
588
+ isFirstWord = false;
589
+ } else {
123
590
  if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
124
591
  // If we start with a new word but the current row length equals the length of the columns, add a new row
125
592
  rows.push('');
@@ -133,90 +600,51 @@ const exec = (string, columns, options = {}) => {
133
600
  }
134
601
 
135
602
  // In 'hard' wrap mode, the length of a line is never allowed to extend past 'columns'
136
- if (options.hard && lengths[index] > columns) {
137
- const remainingColumns = (columns - rowLength);
138
- const breaksStartingThisLine = 1 + Math.floor((lengths[index] - remainingColumns - 1) / columns);
139
- const breaksStartingNextLine = Math.floor((lengths[index] - 1) / columns);
603
+ if (options.hard && options.wordWrap !== false && word.width > columns) {
604
+ const remainingColumns = columns - rowLength;
605
+ const breaksStartingThisLine = 1 + Math.floor((word.width - remainingColumns - 1) / columns);
606
+ const breaksStartingNextLine = Math.floor((word.width - 1) / columns);
140
607
  if (breaksStartingNextLine < breaksStartingThisLine) {
141
608
  rows.push('');
609
+ rowLength = 0;
142
610
  }
143
611
 
144
- wrapWord(rows, word, columns);
612
+ rowLength = wrapWord(rows, word.value, columns, rowLength);
145
613
  continue;
146
614
  }
147
615
 
148
- if (rowLength + lengths[index] > columns && rowLength > 0 && lengths[index] > 0) {
616
+ if (rowLength + word.width > columns && rowLength > 0 && word.width > 0) {
149
617
  if (options.wordWrap === false && rowLength < columns) {
150
- wrapWord(rows, word, columns);
618
+ rowLength = wrapWord(rows, word.value, columns, rowLength);
151
619
  continue;
152
620
  }
153
621
 
154
622
  rows.push('');
623
+ rowLength = 0;
155
624
  }
156
625
 
157
- if (rowLength + lengths[index] > columns && options.wordWrap === false) {
158
- wrapWord(rows, word, columns);
626
+ if (rowLength + word.width > columns && options.wordWrap === false) {
627
+ rowLength = wrapWord(rows, word.value, columns, rowLength);
159
628
  continue;
160
629
  }
161
630
 
162
- rows[rows.length - 1] += word;
631
+ rows[rows.length - 1] += word.value;
632
+ rowLength += word.width;
163
633
  }
164
634
 
165
635
  if (options.trim !== false) {
166
636
  rows = rows.map(row => stringVisibleTrimSpacesRight(row));
167
637
  }
168
638
 
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;
174
-
175
- for (const [index, character] of pre.entries()) {
176
- returnValue += character;
177
-
178
- if (ESCAPES.has(character)) {
179
- const {groups} = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`).exec(preString.slice(preStringIndex)) || {groups: {}};
180
- if (groups.code !== undefined) {
181
- const code = Number.parseFloat(groups.code);
182
- escapeCode = code === END_CODE ? undefined : code;
183
- } else if (groups.uri !== undefined) {
184
- escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;
185
- }
186
- }
187
-
188
- const code = ansiStyles.codes.get(Number(escapeCode));
189
-
190
- if (pre[index + 1] === '\n') {
191
- if (escapeUrl) {
192
- returnValue += wrapAnsiHyperlink('');
193
- }
194
-
195
- if (escapeCode && code) {
196
- returnValue += wrapAnsiCode(code);
197
- }
198
- } else if (character === '\n') {
199
- if (escapeCode && code) {
200
- returnValue += wrapAnsiCode(escapeCode);
201
- }
202
-
203
- if (escapeUrl) {
204
- returnValue += wrapAnsiHyperlink(escapeUrl);
205
- }
206
- }
207
-
208
- preStringIndex += character.length;
209
- }
210
-
211
- return returnValue;
639
+ return restoreStylesAcrossRows(rows.join('\n'));
212
640
  };
213
641
 
214
- // For each newline, invoke the method separately
642
+ // For each newline, invoke the method separately.
215
643
  export default function wrapAnsi(string, columns, options) {
216
644
  return String(string)
217
645
  .normalize()
218
646
  .replaceAll('\r\n', '\n')
219
647
  .split('\n')
220
- .map(line => exec(line, columns, options))
648
+ .map(line => exec(expandTabs(line), columns, options))
221
649
  .join('\n');
222
650
  }