@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.
- package/README.md +2 -4
- package/THIRD_PARTY_NOTICES.md +31 -22
- package/dist/ansi.d.ts +17 -14
- package/dist/ansi.js +7 -2
- package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
- package/dist/index.d.ts +125 -288
- package/dist/index.js +2460 -2864
- package/dist/{truncate-CBiyyZzw.js → truncate-Cr6xVFMa.js} +447 -273
- package/package.json +3 -9
- package/src/ansi/chalk.ts +8 -51
- package/src/ansi/cursor.ts +2 -4
- package/src/ansi/east-asian-width.ts +44 -0
- package/src/ansi/escapes.ts +4 -26
- package/src/ansi/index.ts +12 -14
- package/src/ansi/sgr.ts +2 -5
- package/src/ansi/slice.ts +1 -1
- package/src/ansi/string-width.ts +91 -206
- package/src/ansi/strip.ts +13 -33
- package/src/ansi/supports-color.ts +8 -14
- package/src/ansi/tokenize.ts +94 -166
- package/src/ansi/truncate.ts +5 -3
- package/src/ansi/wrap.ts +13 -14
- package/src/ansi-tokenizer.ts +1 -1
- package/src/colorize.ts +1 -1
- package/src/components/App.tsx +14 -14
- package/src/components/BackgroundContext.ts +2 -2
- package/src/components/Box.tsx +51 -59
- package/src/components/CursorContext.ts +1 -1
- package/src/components/ErrorBoundary.tsx +2 -1
- package/src/components/ErrorOverview.tsx +9 -7
- package/src/components/Newline.tsx +1 -0
- package/src/components/Spacer.tsx +2 -1
- package/src/components/Static.tsx +2 -1
- package/src/components/StderrContext.ts +0 -2
- package/src/components/StdinContext.ts +0 -1
- package/src/components/StdoutContext.ts +1 -3
- package/src/components/Text.tsx +7 -6
- package/src/components/Transform.tsx +2 -1
- package/src/cursor-position.ts +1 -1
- package/src/devtools.ts +84 -24
- package/src/dom.ts +7 -7
- package/src/env.ts +12 -0
- package/src/get-max-width.ts +1 -1
- package/src/global.d.ts +3 -4
- package/src/{boxes.ts → glyphs.ts} +17 -18
- package/src/hooks/use-animation.ts +1 -1
- package/src/hooks/use-app.ts +1 -1
- package/src/hooks/use-box-metrics.ts +1 -1
- package/src/hooks/use-cursor.ts +2 -2
- package/src/hooks/use-focus-manager.ts +1 -1
- package/src/hooks/use-focus.ts +5 -6
- package/src/hooks/use-input.ts +4 -4
- package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
- package/src/hooks/use-paste.ts +2 -2
- package/src/hooks/use-stderr.ts +1 -1
- package/src/hooks/use-stdin.ts +1 -1
- package/src/hooks/use-stdout.ts +1 -1
- package/src/hooks/use-window-size.ts +2 -2
- package/src/index.ts +44 -44
- package/src/ink.tsx +536 -720
- package/src/input-parser.ts +1 -1
- package/src/instances.ts +1 -1
- package/src/kitty-keyboard.ts +128 -0
- package/src/log-update.ts +4 -4
- package/src/measure-element.ts +1 -1
- package/src/measure-text.ts +2 -2
- package/src/output.ts +4 -4
- package/src/parse-keypress.ts +4 -5
- package/src/parse-stack-line.ts +0 -1
- package/src/patch-console.ts +44 -0
- package/src/reconciler.ts +13 -60
- package/src/render-background.ts +3 -3
- package/src/render-border.ts +6 -6
- package/src/render-node-to-output.ts +13 -11
- package/src/render-to-string.ts +4 -4
- package/src/render.ts +9 -10
- package/src/renderer.ts +3 -3
- package/src/sanitize-ansi.ts +1 -1
- package/src/signal-exit.ts +4 -5
- package/src/squash-text-nodes.ts +2 -2
- package/src/stream.ts +2 -4
- package/src/styles.ts +6 -6
- package/src/terminal-size.ts +9 -8
- package/src/types.ts +1 -6
- package/src/utils.ts +2 -2
- package/src/wrap-text.ts +4 -4
- package/src/yoga/config.ts +2 -2
- package/src/yoga/core/absoluteLayout.ts +15 -15
- package/src/yoga/core/baseline.ts +3 -3
- package/src/yoga/core/cache.ts +4 -4
- package/src/yoga/core/calculateLayout.ts +33 -27
- package/src/yoga/core/config.ts +1 -1
- package/src/yoga/core/flexLine.ts +3 -3
- package/src/yoga/core/helpers.ts +3 -3
- package/src/yoga/core/layoutResults.ts +4 -4
- package/src/yoga/core/node.ts +16 -16
- package/src/yoga/core/pixelGrid.ts +4 -4
- package/src/yoga/core/style.ts +11 -11
- package/src/yoga/core/types.ts +2 -2
- package/src/yoga/index.ts +6 -6
- package/src/yoga/node.ts +8 -8
- package/src/ansi/widest-line.ts +0 -12
- package/src/auto-bind.ts +0 -41
- package/src/devtools-window-polyfill.ts +0 -73
- package/src/indent-string.ts +0 -16
- package/src/is-in-ci.ts +0 -7
- package/src/write-synchronized.ts +0 -9
package/src/ansi/strip.ts
CHANGED
|
@@ -1,33 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
import
|
|
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 (
|
|
122
|
-
//
|
|
123
|
-
|
|
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:
|
|
212
|
-
stderr: createSupportsColor({ isTTY:
|
|
205
|
+
stdout: createSupportsColor({ isTTY: isatty(1) }),
|
|
206
|
+
stderr: createSupportsColor({ isTTY: isatty(2) }),
|
|
213
207
|
};
|
package/src/ansi/tokenize.ts
CHANGED
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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(
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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: "
|
|
318
|
-
|
|
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
|
-
.
|
|
308
|
+
.toReversed()
|
|
381
309
|
.map((code) => ({
|
|
382
310
|
...code,
|
|
383
311
|
code: code.endCode,
|
package/src/ansi/truncate.ts
CHANGED
|
@@ -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 "
|
|
5
|
-
import { stringWidth } from "
|
|
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
|
-
|
|
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 "
|
|
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 "
|
|
11
|
-
import { stringWidth } from "
|
|
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
|
-
|
|
471
|
-
.
|
|
469
|
+
activeStyles
|
|
470
|
+
.toReversed()
|
|
472
471
|
.map((activeStyle) => wrapAnsiCode(activeStyle.close))
|
|
473
472
|
.join("");
|
|
474
473
|
|
package/src/ansi-tokenizer.ts
CHANGED
package/src/colorize.ts
CHANGED
package/src/components/App.tsx
CHANGED
|
@@ -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 "
|
|
15
|
-
import { ansiEscapes, CSI, ESC } from "
|
|
16
|
-
import {
|
|
17
|
-
import { type
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
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 "
|
|
4
|
-
import { type LiteralUnion } from "
|
|
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
|
|