@alchemy.run/sigil 0.0.0-alpha.2 → 0.0.0-alpha.4

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 (107) hide show
  1. package/README.md +2 -4
  2. package/THIRD_PARTY_NOTICES.md +31 -22
  3. package/dist/ansi.d.ts +17 -14
  4. package/dist/ansi.js +7 -2
  5. package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
  6. package/dist/index.d.ts +125 -288
  7. package/dist/index.js +2460 -2864
  8. package/dist/{truncate-CBiyyZzw.js → truncate-Cr6xVFMa.js} +447 -273
  9. package/package.json +3 -9
  10. package/src/ansi/chalk.ts +8 -51
  11. package/src/ansi/cursor.ts +2 -4
  12. package/src/ansi/east-asian-width.ts +44 -0
  13. package/src/ansi/escapes.ts +4 -26
  14. package/src/ansi/index.ts +12 -14
  15. package/src/ansi/sgr.ts +2 -5
  16. package/src/ansi/slice.ts +1 -1
  17. package/src/ansi/string-width.ts +91 -206
  18. package/src/ansi/strip.ts +13 -33
  19. package/src/ansi/supports-color.ts +8 -14
  20. package/src/ansi/tokenize.ts +94 -166
  21. package/src/ansi/truncate.ts +5 -3
  22. package/src/ansi/wrap.ts +13 -14
  23. package/src/ansi-tokenizer.ts +1 -1
  24. package/src/colorize.ts +1 -1
  25. package/src/components/App.tsx +14 -14
  26. package/src/components/BackgroundContext.ts +2 -2
  27. package/src/components/Box.tsx +51 -59
  28. package/src/components/CursorContext.ts +1 -1
  29. package/src/components/ErrorBoundary.tsx +2 -1
  30. package/src/components/ErrorOverview.tsx +9 -7
  31. package/src/components/Newline.tsx +1 -0
  32. package/src/components/Spacer.tsx +2 -1
  33. package/src/components/Static.tsx +2 -1
  34. package/src/components/StderrContext.ts +0 -2
  35. package/src/components/StdinContext.ts +0 -1
  36. package/src/components/StdoutContext.ts +1 -3
  37. package/src/components/Text.tsx +7 -6
  38. package/src/components/Transform.tsx +2 -1
  39. package/src/cursor-position.ts +1 -1
  40. package/src/devtools.ts +84 -24
  41. package/src/dom.ts +7 -7
  42. package/src/env.ts +12 -0
  43. package/src/get-max-width.ts +1 -1
  44. package/src/global.d.ts +3 -4
  45. package/src/{boxes.ts → glyphs.ts} +17 -18
  46. package/src/hooks/use-animation.ts +1 -1
  47. package/src/hooks/use-app.ts +1 -1
  48. package/src/hooks/use-box-metrics.ts +1 -1
  49. package/src/hooks/use-cursor.ts +2 -2
  50. package/src/hooks/use-focus-manager.ts +1 -1
  51. package/src/hooks/use-focus.ts +5 -6
  52. package/src/hooks/use-input.ts +4 -4
  53. package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
  54. package/src/hooks/use-paste.ts +2 -2
  55. package/src/hooks/use-stderr.ts +1 -1
  56. package/src/hooks/use-stdin.ts +1 -1
  57. package/src/hooks/use-stdout.ts +1 -1
  58. package/src/hooks/use-window-size.ts +2 -2
  59. package/src/index.ts +44 -44
  60. package/src/ink.tsx +536 -720
  61. package/src/input-parser.ts +1 -1
  62. package/src/instances.ts +1 -1
  63. package/src/kitty-keyboard.ts +128 -0
  64. package/src/log-update.ts +4 -4
  65. package/src/measure-element.ts +1 -1
  66. package/src/measure-text.ts +2 -2
  67. package/src/output.ts +4 -4
  68. package/src/parse-keypress.ts +4 -5
  69. package/src/parse-stack-line.ts +0 -1
  70. package/src/patch-console.ts +44 -0
  71. package/src/reconciler.ts +13 -60
  72. package/src/render-background.ts +3 -3
  73. package/src/render-border.ts +6 -6
  74. package/src/render-node-to-output.ts +13 -11
  75. package/src/render-to-string.ts +4 -4
  76. package/src/render.ts +9 -10
  77. package/src/renderer.ts +3 -3
  78. package/src/sanitize-ansi.ts +1 -1
  79. package/src/signal-exit.ts +4 -5
  80. package/src/squash-text-nodes.ts +2 -2
  81. package/src/stream.ts +2 -4
  82. package/src/styles.ts +6 -6
  83. package/src/terminal-size.ts +9 -8
  84. package/src/types.ts +1 -6
  85. package/src/utils.ts +2 -2
  86. package/src/wrap-text.ts +4 -4
  87. package/src/yoga/config.ts +2 -2
  88. package/src/yoga/core/absoluteLayout.ts +15 -15
  89. package/src/yoga/core/baseline.ts +3 -3
  90. package/src/yoga/core/cache.ts +4 -4
  91. package/src/yoga/core/calculateLayout.ts +33 -27
  92. package/src/yoga/core/config.ts +1 -1
  93. package/src/yoga/core/flexLine.ts +3 -3
  94. package/src/yoga/core/helpers.ts +3 -3
  95. package/src/yoga/core/layoutResults.ts +4 -4
  96. package/src/yoga/core/node.ts +16 -16
  97. package/src/yoga/core/pixelGrid.ts +4 -4
  98. package/src/yoga/core/style.ts +11 -11
  99. package/src/yoga/core/types.ts +2 -2
  100. package/src/yoga/index.ts +6 -6
  101. package/src/yoga/node.ts +8 -8
  102. package/src/ansi/widest-line.ts +0 -12
  103. package/src/auto-bind.ts +0 -41
  104. package/src/devtools-window-polyfill.ts +0 -73
  105. package/src/indent-string.ts +0 -16
  106. package/src/is-in-ci.ts +0 -7
  107. package/src/write-synchronized.ts +0 -9
package/src/ansi/strip.ts CHANGED
@@ -1,33 +1,13 @@
1
- // ANSI escape sequence matching and removal.
2
- // Ported from `ansi-regex` and `strip-ansi` (MIT, Sindre Sorhus).
3
- import { BEL, C1_CSI, C1_ST, ESC } from "./escapes.ts";
4
-
5
- export function ansiRegex({ onlyFirst = false }: { onlyFirst?: boolean } = {}): RegExp {
6
- // Valid string terminator sequences are BEL, ESC\, and C1 ST
7
- const st = `(?:${BEL}|${ESC}\\\\|${C1_ST})`;
8
-
9
- // OSC sequences only: ESC ] ... ST
10
- // The payload stops at the first terminator character rather than scanning
11
- // ahead for one, so an unterminated `ESC ]` cannot rescan the rest of the
12
- // input. Terminals likewise abort a control string on an unexpected ESC.
13
- const osc = `(?:${ESC}\\][^${BEL}${ESC}${C1_ST}]*${st})`;
14
-
15
- // CSI and related: ESC/C1, optional intermediates, optional params
16
- // (supports ; and :) then final byte
17
- const csi = `[${ESC}${C1_CSI}][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]`;
18
-
19
- return new RegExp(`${osc}|${csi}`, onlyFirst ? undefined : "g");
20
- }
21
-
22
- const regex = ansiRegex();
23
-
24
- export function stripAnsi(string: string): string {
25
- // Fast path: ANSI codes require ESC (7-bit) or CSI (8-bit) introducer
26
- if (!string.includes(ESC) && !string.includes(C1_CSI)) {
27
- return string;
28
- }
29
-
30
- // Even though the regex is global, we don't need to reset `.lastIndex`
31
- // because `.replace()` does it automatically.
32
- return string.replace(regex, "");
33
- }
1
+ import { stripVTControlCharacters } from "node:util";
2
+
3
+ // Node's stripVTControlCharacters only understands semicolon-separated CSI
4
+ // parameters, so an ITU T.416 colon-parameter SGR sequence like
5
+ // `\u001B[38:2::255:100:0m` which Sigil deliberately preserves in output
6
+ // (see sanitize-ansi.ts) would leave `:2::255:100:0m` behind. Remove those
7
+ // first, then let Node handle everything else.
8
+ // https://github.com/nodejs/node/pull/65379 fixes this upstream; this wrapper
9
+ // can go once the minimum supported Node version ships it.
10
+ const colonSgrRegex = /(?:\u001B\[|\u009B)[\d;]*:[\d;:]*m/g;
11
+
12
+ export const stripAnsi = (input: string): string =>
13
+ stripVTControlCharacters(input.replaceAll(colonSgrRegex, ""));
@@ -1,8 +1,8 @@
1
1
  // Terminal color support detection.
2
2
  // Ported from `supports-color` (MIT, Sindre Sorhus & Josh Junon).
3
- import os from "node:os";
4
- import process from "node:process";
5
- import tty from "node:tty";
3
+ import { isatty } from "node:tty";
4
+
5
+ import { isWindows } from "#/env.ts";
6
6
 
7
7
  export type ColorSupportLevel = 0 | 1 | 2 | 3;
8
8
 
@@ -118,15 +118,9 @@ function supportsColorLevel(
118
118
  return min;
119
119
  }
120
120
 
121
- if (process.platform === "win32") {
122
- // Windows 10 build 10586 is the first Windows release that supports 256 colors.
123
- // Windows 10 build 14931 is the first release that supports 16m/TrueColor.
124
- const osRelease = os.release().split(".");
125
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) {
126
- return Number(osRelease[2]) >= 14_931 ? 3 : 2;
127
- }
128
-
129
- return 1;
121
+ if (isWindows) {
122
+ // Node 22 requires Windows 10 1809+ (build 17763), which supports TrueColor.
123
+ return 3;
130
124
  }
131
125
 
132
126
  if ("CI" in env) {
@@ -208,6 +202,6 @@ export function createSupportsColor(
208
202
  }
209
203
 
210
204
  export const supportsColor = {
211
- stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
212
- stderr: createSupportsColor({ isTTY: tty.isatty(2) }),
205
+ stdout: createSupportsColor({ isTTY: isatty(1) }),
206
+ stderr: createSupportsColor({ isTTY: isatty(2) }),
213
207
  };
@@ -1,34 +1,22 @@
1
- import { isFullwidthCodePoint } from "./east-asian-width.ts";
2
- // ANSI-aware tokenizer: splits a string into grapheme clusters and ANSI
3
- // codes, tracks active styles per character, and re-emits minimal escape
4
- // sequences. This is the style model Ink's renderer is built on.
5
- // Ported from `@alcalzone/ansi-tokenize` (MIT, AlCalzone).
6
- import { BEL, C1_ST, ESC } from "./escapes.ts";
7
- import { codes as sgrCodes, foreground, background, styles } from "./sgr.ts";
1
+ import { tokenizeAnsi } from "#/ansi-tokenizer.ts";
2
+ import { isFullwidthGrapheme } from "#/ansi/east-asian-width.ts";
3
+ // ANSI-aware style tokenizer: splits a string into grapheme clusters and
4
+ // ANSI codes, tracks active styles per character, and re-emits minimal
5
+ // escape sequences. This is the style model Ink's renderer is built on.
6
+ // Style semantics ported from `@alcalzone/ansi-tokenize` (MIT, AlCalzone);
7
+ // escape recognition delegates to the shared ECMA-48 grammar in
8
+ // `src/ansi-tokenizer.ts` (also behind sanitize-ansi), and grapheme widths
9
+ // come from `east-asian-width.ts` — one grammar, one width table.
10
+ import { BEL, C1_ST, ESC } from "#/ansi/escapes.ts";
11
+ import { codes as sgrCodes, foreground, background, styles } from "#/ansi/sgr.ts";
8
12
 
9
13
  // Single-character introducer suffixes (`ESC [` = CSI, `ESC ]` = OSC).
10
14
  const BACKSLASH = "\\";
11
15
  const CSI = "[";
12
16
  const OSC = "]";
13
17
 
14
- // Char codes
15
- const CC_BEL = BEL.charCodeAt(0);
16
- const CC_ESC = ESC.charCodeAt(0);
17
- const CC_BACKSLASH = BACKSLASH.charCodeAt(0);
18
- const CC_CSI = CSI.charCodeAt(0);
19
- const CC_OSC = OSC.charCodeAt(0);
20
- const CC_C1_ST = C1_ST.charCodeAt(0);
21
- const CC_0 = "0".charCodeAt(0);
22
- const CC_9 = "9".charCodeAt(0);
23
- const CC_SEMI = ";".charCodeAt(0);
24
- const CC_M = "m".charCodeAt(0);
25
-
26
- // Escape code points: \u001B and \u009B
27
- const ESCAPES = new Set([CC_ESC, 0x9b]);
28
-
29
18
  // OSC 8 hyperlink constants
30
19
  const linkCodePrefix = `${ESC}${OSC}8;`;
31
- const linkCodePrefixCharCodes = linkCodePrefix.split("").map((char) => char.charCodeAt(0));
32
20
  const linkCodeSuffix = BEL;
33
21
  const linkEndCode = `${ESC}${OSC}8;;${BEL}`;
34
22
  const linkEndCodeST = `${ESC}${OSC}8;;${ESC}${BACKSLASH}`;
@@ -103,6 +91,11 @@ export function getEndCode(code: string): string {
103
91
  return background.close;
104
92
  }
105
93
 
94
+ // Extended underline color (T.416) closes with 59m; not in the SGR map.
95
+ if (code.startsWith("58")) {
96
+ return `${ESC}[59m`;
97
+ }
98
+
106
99
  // Otherwise find the reset code in the SGR code map
107
100
  const endCode = sgrCodes.get(Number.parseInt(code, 10));
108
101
  if (endCode !== undefined) {
@@ -128,95 +121,6 @@ export function isIntensityCode(code: AnsiCode): boolean {
128
121
 
129
122
  const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
130
123
 
131
- function isFullwidthGrapheme(grapheme: string, baseCodePoint: number): boolean {
132
- if (isFullwidthCodePoint(baseCodePoint)) return true;
133
- // Variation Selector 16 forces emoji presentation (2 columns wide)
134
- if (grapheme.includes("\uFE0F")) return true;
135
- // Regional indicator pairs form flag emoji (2 columns wide)
136
- if (baseCodePoint >= 0x1f1e6 && baseCodePoint <= 0x1f1ff) return true;
137
- return false;
138
- }
139
-
140
- // HOT PATH: Use only basic string/char code operations for maximum performance
141
- function parseLinkCode(string: string, offset: number): string | undefined {
142
- string = string.slice(offset);
143
- for (let index = 1; index < linkCodePrefixCharCodes.length; index++) {
144
- if (string.charCodeAt(index) !== linkCodePrefixCharCodes[index]) {
145
- return;
146
- }
147
- }
148
-
149
- // Find the semicolon that ends params
150
- const paramsEndIndex = string.indexOf(";", linkCodePrefix.length);
151
- if (paramsEndIndex === -1) return;
152
-
153
- // This is a link code (with or without the URL part). Find the end of it.
154
- const endIndex = findOscTerminatorIndex(string, paramsEndIndex + 1);
155
- if (endIndex === -1) return;
156
-
157
- return string.slice(0, endIndex + 1);
158
- }
159
-
160
- // HOT PATH: Generic fallback for non-link OSC sequences (window title, etc.)
161
- function parseOscSequence(string: string, offset: number): string | undefined {
162
- string = string.slice(offset);
163
- // Find the OSC terminator (starting after "ESC ]")
164
- const endIndex = findOscTerminatorIndex(string, 2);
165
- if (endIndex === -1) return;
166
- return string.slice(0, endIndex + 1);
167
- }
168
-
169
- /**
170
- Finds the index of the last character of the first OSC terminator at or after
171
- `startIndex`. Recognizes BEL (\u0007), C1 ST (\u009C), and ESC+backslash.
172
- Returns -1 if no terminator is found.
173
- */
174
- function findOscTerminatorIndex(string: string, startIndex: number): number {
175
- for (let index = startIndex; index < string.length; index++) {
176
- const charCode = string.charCodeAt(index);
177
- if (charCode === CC_BEL) return index;
178
- if (charCode === CC_C1_ST) return index;
179
- if (
180
- charCode === CC_ESC &&
181
- index + 1 < string.length &&
182
- string.charCodeAt(index + 1) === CC_BACKSLASH
183
- ) {
184
- return index + 1;
185
- }
186
- }
187
-
188
- return -1;
189
- }
190
-
191
- /**
192
- Scans through the given string and finds the index of the last character of an
193
- SGR sequence like `\u001B[38;2;123;123;123m`. This assumes that the string has
194
- been checked to start with `\u001B[`. Returns -1 if no valid SGR sequence is
195
- found.
196
- */
197
- function findSgrSequenceEndIndex(string: string): number {
198
- for (let index = 2; index < string.length; index++) {
199
- const charCode = string.charCodeAt(index);
200
- // m marks the end of the SGR sequence
201
- if (charCode === CC_M) return index;
202
- // Digits and semicolons are valid
203
- if (charCode === CC_SEMI) continue;
204
- if (charCode >= CC_0 && charCode <= CC_9) continue;
205
- // Everything else is invalid
206
- break;
207
- }
208
-
209
- return -1;
210
- }
211
-
212
- // HOT PATH: Use only basic string/char code operations for maximum performance
213
- function parseSgrSequence(string: string, offset: number): string | undefined {
214
- string = string.slice(offset);
215
- const endIndex = findSgrSequenceEndIndex(string);
216
- if (endIndex === -1) return;
217
- return string.slice(0, endIndex + 1);
218
- }
219
-
220
124
  /**
221
125
  Splits compound SGR sequences like `\u001B[1;3;31m` into individual components.
222
126
  */
@@ -256,73 +160,97 @@ function splitCompoundSgrSequences(code: string): string[] {
256
160
  return result.map((part) => `${ESC}[${part}m`);
257
161
  }
258
162
 
163
+ // SGR parameters: digits separated by `;` or `:` (ITU T.416 colon form).
164
+ const SGR_PARAMETERS_RE = /^[\d;:]*$/;
165
+
166
+ const c1LinkCodePrefix = "\u009D8;";
167
+
259
168
  export function tokenize(string: string, endChar = Number.POSITIVE_INFINITY): Token[] {
260
169
  const result: Token[] = [];
261
170
  let visible = 0;
262
- let codeEndIndex = 0;
263
-
264
- for (const { segment, index } of segmenter.segment(string)) {
265
- // Skip segments consumed as part of an ANSI sequence
266
- if (index < codeEndIndex) continue;
267
-
268
- const codePoint = segment.codePointAt(0)!;
269
- if (ESCAPES.has(codePoint)) {
270
- let code: string | undefined;
271
- // Peek the next code point to determine the type of ANSI sequence
272
- const nextCodePoint = string.codePointAt(index + 1);
273
- if (nextCodePoint === CC_OSC) {
274
- // ] = operating system commands
275
- code = parseLinkCode(string, index);
276
- if (code) {
277
- // OSC 8 hyperlinks are paired codes with an endCode
171
+
172
+ outer: for (const ansiToken of tokenizeAnsi(string)) {
173
+ if (ansiToken.type === "text") {
174
+ for (const { segment } of segmenter.segment(ansiToken.value)) {
175
+ const fullWidth = isFullwidthGrapheme(segment, segment.codePointAt(0)!);
176
+ result.push({
177
+ type: "char",
178
+ value: segment,
179
+ fullWidth,
180
+ });
181
+
182
+ visible += fullWidth ? 2 : 1;
183
+ if (visible >= endChar) {
184
+ break outer;
185
+ }
186
+ }
187
+
188
+ continue;
189
+ }
190
+
191
+ if (ansiToken.type === "csi") {
192
+ // SGR sequences carry style state; C1 introducers are normalized to the
193
+ // 7-bit form so downstream comparisons see a single representation.
194
+ if (
195
+ ansiToken.finalCharacter === "m" &&
196
+ ansiToken.intermediateString === "" &&
197
+ SGR_PARAMETERS_RE.test(ansiToken.parameterString)
198
+ ) {
199
+ // Split compound codes into individual tokens
200
+ const code = `${ESC}${CSI}${ansiToken.parameterString}m`;
201
+ for (const individualCode of splitCompoundSgrSequences(code)) {
278
202
  result.push({
279
203
  type: "ansi",
280
- code,
281
- endCode: getEndCode(code),
204
+ code: individualCode,
205
+ endCode: getEndCode(individualCode),
282
206
  });
283
- } else {
284
- // Other OSC sequences (window title, etc.) are self-contained
285
- // control codes with no endCode.
286
- code = parseOscSequence(string, index);
287
- if (code) {
288
- result.push({
289
- type: "control",
290
- code,
291
- });
292
- }
293
- }
294
- } else if (nextCodePoint === CC_CSI) {
295
- // [ = control sequence introducer, like SGR sequences [...m
296
- code = parseSgrSequence(string, index);
297
- if (code) {
298
- // Split compound codes into individual tokens
299
- for (const individualCode of splitCompoundSgrSequences(code)) {
300
- result.push({
301
- type: "ansi",
302
- code: individualCode,
303
- endCode: getEndCode(individualCode),
304
- });
305
- }
306
207
  }
208
+ } else {
209
+ result.push({
210
+ type: "control",
211
+ code: ansiToken.value,
212
+ });
307
213
  }
308
214
 
309
- if (code) {
310
- codeEndIndex = index + code.length;
311
- continue;
215
+ continue;
216
+ }
217
+
218
+ if (ansiToken.type === "osc") {
219
+ // OSC 8 hyperlinks are paired codes with an endCode; C1 introducers are
220
+ // normalized to the 7-bit form. Other OSC sequences (window title,
221
+ // etc.) are self-contained control codes with no endCode.
222
+ const { value } = ansiToken;
223
+ const prefix = value.startsWith(linkCodePrefix)
224
+ ? linkCodePrefix
225
+ : value.startsWith(c1LinkCodePrefix)
226
+ ? c1LinkCodePrefix
227
+ : undefined;
228
+
229
+ if (prefix !== undefined && value.includes(";", prefix.length)) {
230
+ const code =
231
+ prefix === linkCodePrefix ? value : `${linkCodePrefix}${value.slice(prefix.length)}`;
232
+ result.push({
233
+ type: "ansi",
234
+ code,
235
+ endCode: getEndCode(code),
236
+ });
237
+ } else {
238
+ result.push({
239
+ type: "control",
240
+ code: value,
241
+ });
312
242
  }
243
+
244
+ continue;
313
245
  }
314
246
 
315
- const fullWidth = isFullwidthGrapheme(segment, codePoint);
247
+ // Everything else the grammar recognizes (ESC sequences, DCS/PM/APC/SOS
248
+ // control strings, stray ST or C1 bytes, malformed tails) is a zero-width
249
+ // control unit, preserved verbatim.
316
250
  result.push({
317
- type: "char",
318
- value: segment,
319
- fullWidth,
251
+ type: "control",
252
+ code: ansiToken.value,
320
253
  });
321
-
322
- visible += fullWidth ? 2 : 1;
323
- if (visible >= endChar) {
324
- break;
325
- }
326
254
  }
327
255
 
328
256
  return result;
@@ -377,7 +305,7 @@ Returns the combination of ANSI codes needed to undo the given ANSI codes.
377
305
  */
378
306
  export function undoAnsiCodes(codes: readonly AnsiCode[]): AnsiCode[] {
379
307
  return reduceAnsiCodes(codes)
380
- .reverse()
308
+ .toReversed()
381
309
  .map((code) => ({
382
310
  ...code,
383
311
  code: code.endCode,
@@ -1,8 +1,8 @@
1
1
  // Truncate a string to a visible width, ANSI-aware.
2
2
  // Ported from `cli-truncate` (MIT, Sindre Sorhus) on top of the shared
3
3
  // slice and width modules.
4
- import { sliceAnsi } from "./slice.ts";
5
- import { stringWidth } from "./string-width.ts";
4
+ import { sliceAnsi } from "#/ansi/slice.ts";
5
+ import { stringWidth } from "#/ansi/string-width.ts";
6
6
 
7
7
  export type TruncateOptions = {
8
8
  readonly position?: "start" | "middle" | "end";
@@ -36,7 +36,9 @@ const ANSI_ESC = 27;
36
36
  const ANSI_LEFT_BRACKET = 91;
37
37
  const ANSI_LETTER_M = 109;
38
38
 
39
- const isSgrParameter = (code: number): boolean => (code >= 48 && code <= 57) || code === 59; // 0-9 or ;
39
+ // 0-9, ; or : (ITU T.416 colon-form parameters)
40
+ const isSgrParameter = (code: number): boolean =>
41
+ (code >= 48 && code <= 57) || code === 59 || code === 58;
40
42
 
41
43
  function leadingSgrSpanEndIndex(string: string): number {
42
44
  let index = 0;
package/src/ansi/wrap.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  // Word-wrap a string to a column width, ANSI-aware.
2
2
  // Ported from `wrap-ansi` (MIT, Sindre Sorhus).
3
- import { BEL, C1_CSI, ESC } from "./escapes.ts";
3
+ import { BEL, C1_CSI, ESC } from "#/ansi/escapes.ts";
4
4
  //
5
5
  // Supported boundary: semicolon-delimited SGR styling, colon-delimited
6
6
  // RGB/indexed colors, and OSC 8 hyperlinks are tracked, while ordinary CSI
7
7
  // sequences and other complete 7-bit OSC commands are preserved as opaque
8
8
  // zero-width units. This is intentionally not a terminal emulator. Newlines
9
9
  // always delimit input lines before ANSI parsing.
10
- import { codes as sgrCodes } from "./sgr.ts";
11
- import { stringWidth } from "./string-width.ts";
10
+ import { codes as sgrCodes } from "#/ansi/sgr.ts";
11
+ import { stringWidth } from "#/ansi/string-width.ts";
12
12
 
13
13
  export type WrapOptions = {
14
14
  readonly trim?: boolean;
@@ -59,8 +59,7 @@ const segmenter = new Intl.Segmenter();
59
59
  // Complete ANSI sequences have already been removed before measuring these
60
60
  // strings. Avoid string-width's ANSI scan so malformed sequences are not
61
61
  // rescanned.
62
- const getStringWidth = (string: string): number =>
63
- stringWidth(string, { countAnsiEscapeCodes: true });
62
+ const getStringWidth = (string: string): number => stringWidth(string);
64
63
  const TAB_SIZE = 8;
65
64
 
66
65
  // Finds the next character that could introduce a sequence, so plain text is
@@ -221,18 +220,18 @@ type ActiveStyle = {
221
220
  readonly close: number;
222
221
  };
223
222
 
223
+ const EXTENDED_COLOR_CODES = new Set([
224
+ ANSI_SGR_FOREGROUND_EXTENDED,
225
+ ANSI_SGR_BACKGROUND_EXTENDED,
226
+ ANSI_SGR_UNDERLINE_COLOR_EXTENDED,
227
+ ]);
228
+
224
229
  const getColonColorToken = (parameter: string): SgrToken | undefined => {
225
230
  const parts = parameter.split(":");
226
231
  const code = Number.parseInt(parts[0]!, 10);
227
232
  const mode = Number.parseInt(parts[1]!, 10);
228
233
 
229
- if (
230
- ![
231
- ANSI_SGR_FOREGROUND_EXTENDED,
232
- ANSI_SGR_BACKGROUND_EXTENDED,
233
- ANSI_SGR_UNDERLINE_COLOR_EXTENDED,
234
- ].includes(code)
235
- ) {
234
+ if (!EXTENDED_COLOR_CODES.has(code)) {
236
235
  return;
237
236
  }
238
237
 
@@ -467,8 +466,8 @@ const applyLeadingSgrResets = (
467
466
  };
468
467
 
469
468
  const getClosingSgrSequence = (activeStyles: readonly ActiveStyle[]): string =>
470
- [...activeStyles]
471
- .reverse()
469
+ activeStyles
470
+ .toReversed()
472
471
  .map((activeStyle) => wrapAnsiCode(activeStyle.close))
473
472
  .join("");
474
473
 
@@ -1,4 +1,4 @@
1
- import { BEL, C1_CSI, C1_ST, ESC } from "./ansi/escapes.ts";
1
+ import { BEL, C1_CSI, C1_ST, ESC } from "#/ansi/escapes.ts";
2
2
 
3
3
  const bellCharacter = BEL;
4
4
  const escapeCharacter = ESC;
package/src/colorize.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { chalk, type ForegroundColorName, type BackgroundColorName } from "./ansi/chalk.ts";
1
+ import { chalk, type ForegroundColorName, type BackgroundColorName } from "#/ansi/chalk.ts";
2
2
 
3
3
  type ColorType = "foreground" | "background";
4
4
 
@@ -1,5 +1,5 @@
1
+ /** @jsxImportSource react */
1
2
  import { EventEmitter } from "node:events";
2
- import process from "node:process";
3
3
 
4
4
  import React, {
5
5
  type ReactNode,
@@ -11,19 +11,19 @@ import React, {
11
11
  useInsertionEffect,
12
12
  } from "react";
13
13
 
14
- import { cliCursor } from "../ansi/cursor.ts";
15
- import { ansiEscapes, CSI, ESC } from "../ansi/escapes.ts";
16
- import { createInputParser } from "../input-parser.ts";
17
- import { type CursorPosition } from "../log-update.ts";
18
- import { getRawModeStream, type OutputStream } from "../stream.ts";
19
- import { animationContext as AnimationContext } from "./AnimationContext.ts";
20
- import { AppContext, type SuspendTerminal } from "./AppContext.ts";
21
- import { CursorContext } from "./CursorContext.ts";
22
- import { ErrorBoundary } from "./ErrorBoundary.tsx";
23
- import { FocusContext } from "./FocusContext.ts";
24
- import { StderrContext } from "./StderrContext.ts";
25
- import { StdinContext } from "./StdinContext.ts";
26
- import { StdoutContext } from "./StdoutContext.ts";
14
+ import { cliCursor } from "#/ansi/cursor.ts";
15
+ import { ansiEscapes, CSI, ESC } from "#/ansi/escapes.ts";
16
+ import { animationContext as AnimationContext } from "#/components/AnimationContext.ts";
17
+ import { AppContext, type SuspendTerminal } from "#/components/AppContext.ts";
18
+ import { CursorContext } from "#/components/CursorContext.ts";
19
+ import { ErrorBoundary } from "#/components/ErrorBoundary.tsx";
20
+ import { FocusContext } from "#/components/FocusContext.ts";
21
+ import { StderrContext } from "#/components/StderrContext.ts";
22
+ import { StdinContext } from "#/components/StdinContext.ts";
23
+ import { StdoutContext } from "#/components/StdoutContext.ts";
24
+ import { createInputParser } from "#/input-parser.ts";
25
+ import { type CursorPosition } from "#/log-update.ts";
26
+ import { getRawModeStream, type OutputStream } from "#/stream.ts";
27
27
 
28
28
  const tab = "\t";
29
29
  const shiftTab = `${CSI}Z`;
@@ -1,7 +1,7 @@
1
1
  import { createContext } from "react";
2
2
 
3
- import { type ForegroundColorName } from "../ansi/sgr.ts";
4
- import { type LiteralUnion } from "../types.ts";
3
+ import { type ForegroundColorName } from "#/ansi/sgr.ts";
4
+ import { type LiteralUnion } from "#/types.ts";
5
5
 
6
6
  export type BackgroundColor = LiteralUnion<ForegroundColorName, string>;
7
7