@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.10
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 +499 -313
- package/THIRD_PARTY_NOTICES.md +70 -23
- package/dist/Text-DV9CuzAT.d.ts +452 -0
- package/dist/ansi.d.ts +217 -0
- package/dist/ansi.js +87 -0
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.js +3 -0
- package/dist/cell-_ZVhbfl0.js +44 -0
- package/dist/color-CkbalRqK.js +2 -0
- package/dist/color-policy-BAC9-TZX.js +592 -0
- package/dist/color-policy-CCxuHIdD.d.ts +22 -0
- package/dist/color-profile-CyeHnG1T.d.ts +97 -0
- package/dist/color-profile-DHhQHY55.js +36 -0
- package/dist/color.d.ts +21 -0
- package/dist/color.js +3 -0
- package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
- package/dist/detect-B3dL4Q11.js +374 -0
- package/dist/detect-Db6GbKOm.d.ts +195 -0
- package/dist/{devtools-QpCMm9JH.mjs → devtools-DbthxoD1.js} +23 -24
- package/dist/env-YVw64yZS.js +9 -0
- package/dist/escapes-CB_6CWOE.d.ts +72 -0
- package/dist/geometry-BxXOzJgo.d.ts +11 -0
- package/dist/index-D48vQhhe.d.ts +21 -0
- package/dist/index-DDVME65c.d.ts +919 -0
- package/dist/index.d.ts +1310 -0
- package/dist/index.js +3209 -0
- package/dist/osc-BFKKSqpg.js +71 -0
- package/dist/osc-Cn0fw77g.d.ts +23 -0
- package/dist/paint-Cx-zC_sX.d.ts +81 -0
- package/dist/query-BNc2B8GD.d.ts +152 -0
- package/dist/router.d.ts +392 -0
- package/dist/router.js +709 -0
- package/dist/sample-Cqw1bjUL.js +445 -0
- package/dist/screen-CgC2WlVM.d.ts +49 -0
- package/dist/screen-CiPytswf.js +342 -0
- package/dist/screen.d.ts +5 -0
- package/dist/screen.js +5 -0
- package/dist/semantic-text-style-DIMzC7xt.js +91 -0
- package/dist/serialize-BTkAZgw1.js +79 -0
- package/dist/session-DDQ5V300.js +723 -0
- package/dist/sgr-BhwaWAJB.js +246 -0
- package/dist/store-C1P5fOUi.d.ts +72 -0
- package/dist/string-width-CijQwpIk.js +69 -0
- package/dist/strip-BvU4toXG.js +6 -0
- package/dist/terminal.d.ts +121 -0
- package/dist/terminal.js +2 -0
- package/dist/tokenize-AjqbvtiT.js +1242 -0
- package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
- package/dist/truncate-D31fhU6i.js +562 -0
- package/dist/use-focus-CcCIdCWA.js +1337 -0
- package/dist/yoga-5jKhYCJC.js +3465 -0
- package/dist/yoga.d.ts +2 -0
- package/dist/yoga.js +2 -0
- package/package.json +70 -24
- package/src/ansi/chalk.ts +138 -0
- package/src/ansi/cursor.ts +46 -0
- package/src/ansi/east-asian-width.ts +259 -0
- package/src/ansi/escapes.ts +120 -0
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +27 -0
- package/src/ansi/osc.ts +77 -0
- package/src/ansi/sgr.ts +234 -0
- package/src/ansi/slice.ts +43 -0
- package/src/ansi/string-width.ts +121 -0
- package/src/ansi/strip.ts +13 -0
- package/src/ansi/tokenize.ts +380 -0
- package/src/ansi/truncate.ts +196 -0
- package/src/ansi/wrap.ts +765 -0
- package/src/ansi-tokenizer.ts +510 -0
- package/src/capabilities/color-policy.ts +34 -0
- package/src/capabilities/detect.ts +608 -0
- package/src/capabilities/index.ts +37 -0
- package/src/capabilities/query.ts +679 -0
- package/src/capabilities/store.ts +394 -0
- package/src/code-excerpt.ts +39 -0
- package/src/color/index.ts +3 -0
- package/src/color/paint.ts +169 -0
- package/src/color/palette.ts +48 -0
- package/src/color/sample.ts +323 -0
- package/src/color.ts +1 -0
- package/src/components/AccessibilityContext.ts +5 -0
- package/src/components/AnimationContext.ts +24 -0
- package/src/components/AnsiText.tsx +42 -0
- package/src/components/App.tsx +878 -0
- package/src/components/AppContext.ts +111 -0
- package/src/components/BackgroundContext.ts +7 -0
- package/src/components/Box.tsx +100 -0
- package/src/components/CursorContext.ts +19 -0
- package/src/components/ErrorBoundary.tsx +39 -0
- package/src/components/ErrorOverview.tsx +135 -0
- package/src/components/FocusContext.ts +30 -0
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/Newline.tsx +16 -0
- package/src/components/Spacer.tsx +11 -0
- package/src/components/Static.tsx +60 -0
- package/src/components/StderrContext.ts +24 -0
- package/src/components/StdinContext.ts +48 -0
- package/src/components/StdoutContext.ts +26 -0
- package/src/components/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +122 -0
- package/src/components/Transform.tsx +38 -0
- package/src/components/VirtualList.tsx +128 -0
- package/src/cursor-position.ts +103 -0
- package/src/devtools.ts +103 -0
- package/src/dom.ts +301 -0
- package/src/env.ts +12 -0
- package/src/get-max-width.ts +11 -0
- package/src/global.d.ts +38 -0
- package/src/glyphs.ts +99 -0
- package/src/hooks/use-animation.ts +142 -0
- package/src/hooks/use-app.ts +8 -0
- package/src/hooks/use-box-metrics.ts +134 -0
- package/src/hooks/use-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +33 -0
- package/src/hooks/use-focus-manager.ts +62 -0
- package/src/hooks/use-focus.ts +82 -0
- package/src/hooks/use-input.ts +267 -0
- package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
- package/src/hooks/use-paste.ts +78 -0
- package/src/hooks/use-stderr.ts +8 -0
- package/src/hooks/use-stdin.ts +10 -0
- package/src/hooks/use-stdout.ts +8 -0
- package/src/hooks/use-terminal-osc.ts +59 -0
- package/src/hooks/use-virtual-scroll.ts +84 -0
- package/src/hooks/use-window-size.ts +37 -0
- package/src/index.ts +96 -0
- package/src/ink.tsx +1452 -0
- package/src/input-parser.ts +303 -0
- package/src/instances.ts +9 -0
- package/src/kitty-keyboard.ts +185 -0
- package/src/measure-element.ts +62 -0
- package/src/measure-text.ts +31 -0
- package/src/paint-tree.ts +220 -0
- package/src/parse-keypress.ts +515 -0
- package/src/parse-stack-line.ts +138 -0
- package/src/patch-console.ts +106 -0
- package/src/quick-lru.ts +85 -0
- package/src/reconciler.ts +425 -0
- package/src/render-background.ts +59 -0
- package/src/render-border.ts +167 -0
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +146 -0
- package/src/render.ts +284 -0
- package/src/router/components.tsx +343 -0
- package/src/router/context.ts +41 -0
- package/src/router/history.ts +194 -0
- package/src/router/hooks.tsx +391 -0
- package/src/router/index.ts +34 -0
- package/src/router/matcher.ts +571 -0
- package/src/sanitize-ansi.ts +33 -0
- package/src/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +160 -0
- package/src/screen/cell.ts +138 -0
- package/src/screen/color-profile.ts +47 -0
- package/src/screen/geometry.ts +9 -0
- package/src/screen/index.ts +6 -0
- package/src/screen/screen.ts +305 -0
- package/src/screen/serialize.ts +129 -0
- package/src/screen.ts +1 -0
- package/src/semantic-text-style.ts +118 -0
- package/src/signal-exit.ts +106 -0
- package/src/squash-text-nodes.ts +37 -0
- package/src/stream.ts +28 -0
- package/src/structured-text.ts +325 -0
- package/src/styles.ts +753 -0
- package/src/terminal/index.ts +2 -0
- package/src/terminal/inline-presenter.ts +120 -0
- package/src/terminal/input.ts +92 -0
- package/src/terminal/render-scheduler.ts +37 -0
- package/src/terminal/screen-presenter.ts +242 -0
- package/src/terminal/session.ts +408 -0
- package/src/terminal-size.ts +58 -0
- package/src/terminal.ts +1 -0
- package/src/testing/browser.ts +588 -0
- package/src/testing/emulators.ts +205 -0
- package/src/testing/explorer-app/index.html +12 -0
- package/src/testing/explorer-app/main.ts +381 -0
- package/src/testing/explorer-app/style.css +194 -0
- package/src/testing/explorer-app/tsconfig.json +15 -0
- package/src/testing/explorer-app/vite-env.d.ts +1 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/keys.ts +56 -0
- package/src/testing/live.ts +85 -0
- package/src/testing/matchers.ts +70 -0
- package/src/testing/public.ts +94 -0
- package/src/testing/terminal.ts +361 -0
- package/src/testing/vitest.ts +157 -0
- package/src/throttle.ts +73 -0
- package/src/transform-adapter.ts +14 -0
- package/src/types.ts +10 -0
- package/src/virtual-scroll.ts +133 -0
- package/src/wrap-text.ts +54 -0
- package/src/yoga/config.ts +57 -0
- package/src/yoga/core/absoluteLayout.ts +626 -0
- package/src/yoga/core/baseline.ts +66 -0
- package/src/yoga/core/cache.ts +136 -0
- package/src/yoga/core/calculateLayout.ts +2926 -0
- package/src/yoga/core/config.ts +104 -0
- package/src/yoga/core/flexLine.ts +177 -0
- package/src/yoga/core/helpers.ts +293 -0
- package/src/yoga/core/layoutResults.ts +167 -0
- package/src/yoga/core/node.ts +611 -0
- package/src/yoga/core/numeric.ts +44 -0
- package/src/yoga/core/pixelGrid.ts +151 -0
- package/src/yoga/core/style.ts +887 -0
- package/src/yoga/core/types.ts +224 -0
- package/src/yoga/generated/YGEnums.ts +263 -0
- package/src/yoga/index.ts +19 -0
- package/src/yoga/node.ts +1140 -0
- package/dist/index.d.mts +0 -2379
- package/dist/index.mjs +0 -10072
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Derived from `ansi-escapes` (MIT, Sindre Sorhus), reduced to the escapes Ink uses.
|
|
2
|
+
// Control characters and sequence introducers. This module is the single
|
|
3
|
+
// home for escape sequences: everything else imports these instead of
|
|
4
|
+
// spelling out `\u001B[...` inline.
|
|
5
|
+
export const ESC = "\u001B";
|
|
6
|
+
export const BEL = "\u0007";
|
|
7
|
+
export const DEL = "\u007F";
|
|
8
|
+
/** Control Sequence Introducer. */
|
|
9
|
+
export const CSI = `${ESC}[`;
|
|
10
|
+
/** Operating System Command. */
|
|
11
|
+
export const OSC = `${ESC}]`;
|
|
12
|
+
/** String Terminator. */
|
|
13
|
+
export const ST = `${ESC}\\`;
|
|
14
|
+
/** Single-byte C1 forms of CSI and ST. */
|
|
15
|
+
export const C1_CSI = "\u009B";
|
|
16
|
+
export const C1_ST = "\u009C";
|
|
17
|
+
|
|
18
|
+
const sep = ";";
|
|
19
|
+
|
|
20
|
+
export const cursorTo = (x: number, y?: number): string => {
|
|
21
|
+
if (typeof y !== "number") {
|
|
22
|
+
return CSI + (x + 1) + "G";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return CSI + (y + 1) + sep + (x + 1) + "H";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const cursorUp = (count = 1): string => CSI + count + "A";
|
|
29
|
+
export const cursorDown = (count = 1): string => CSI + count + "B";
|
|
30
|
+
export const cursorLeft = CSI + "G";
|
|
31
|
+
export const cursorNextLine = CSI + "E";
|
|
32
|
+
|
|
33
|
+
export const eraseEndLine = CSI + "K";
|
|
34
|
+
export const eraseLine = CSI + "2K";
|
|
35
|
+
export const eraseScreen = CSI + "2J";
|
|
36
|
+
|
|
37
|
+
export const eraseLines = (count: number): string => {
|
|
38
|
+
let clear = "";
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < count; i++) {
|
|
41
|
+
clear += eraseLine + (i < count - 1 ? cursorUp() : "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (count) {
|
|
45
|
+
clear += cursorLeft;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return clear;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 1. Erases the screen (only done in case `2` is not supported)
|
|
52
|
+
// 2. Erases the whole screen including scrollback buffer
|
|
53
|
+
// 3. Moves cursor to the top-left position
|
|
54
|
+
export const clearTerminal = `${eraseScreen}${CSI}3J${CSI}H`;
|
|
55
|
+
|
|
56
|
+
export const enterAlternativeScreen = CSI + "?1049h";
|
|
57
|
+
export const exitAlternativeScreen = CSI + "?1049l";
|
|
58
|
+
|
|
59
|
+
export const cursorShow = CSI + "?25h";
|
|
60
|
+
export const cursorHide = CSI + "?25l";
|
|
61
|
+
export type CursorShape = "block" | "underline" | "bar";
|
|
62
|
+
export const cursorShape = (shape: CursorShape, blinking = true): string => {
|
|
63
|
+
const value = {
|
|
64
|
+
block: blinking ? 1 : 2,
|
|
65
|
+
underline: blinking ? 3 : 4,
|
|
66
|
+
bar: blinking ? 5 : 6,
|
|
67
|
+
}[shape];
|
|
68
|
+
return `${CSI}${value} q`;
|
|
69
|
+
};
|
|
70
|
+
export const cursorColor = (color: string): string => `${OSC}12;${color}${BEL}`;
|
|
71
|
+
export const resetCursorColor = `${OSC}112${BEL}`;
|
|
72
|
+
|
|
73
|
+
export const enableBracketedPaste = CSI + "?2004h";
|
|
74
|
+
export const disableBracketedPaste = CSI + "?2004l";
|
|
75
|
+
|
|
76
|
+
// Markers the terminal wraps around pasted input while bracketed paste is on.
|
|
77
|
+
export const pasteStart = CSI + "200~";
|
|
78
|
+
export const pasteEnd = CSI + "201~";
|
|
79
|
+
|
|
80
|
+
// Synchronized update mode (DEC mode 2026): begin / end synchronized update.
|
|
81
|
+
export const bsu = CSI + "?2026h";
|
|
82
|
+
export const esu = CSI + "?2026l";
|
|
83
|
+
|
|
84
|
+
// Kitty keyboard protocol: query support, push a flag set, pop it off.
|
|
85
|
+
export const kittyQuery = CSI + "?u";
|
|
86
|
+
export const pushKittyKeyboard = (flags: number): string => `${CSI}>${flags}u`;
|
|
87
|
+
export const popKittyKeyboard = CSI + "<u";
|
|
88
|
+
|
|
89
|
+
export const link = (text: string, url: string): string =>
|
|
90
|
+
[OSC, "8", sep, sep, url, BEL, text, OSC, "8", sep, sep, BEL].join("");
|
|
91
|
+
|
|
92
|
+
export const ansiEscapes = {
|
|
93
|
+
cursorTo,
|
|
94
|
+
cursorUp,
|
|
95
|
+
cursorDown,
|
|
96
|
+
cursorLeft,
|
|
97
|
+
cursorNextLine,
|
|
98
|
+
eraseEndLine,
|
|
99
|
+
eraseLine,
|
|
100
|
+
eraseScreen,
|
|
101
|
+
eraseLines,
|
|
102
|
+
clearTerminal,
|
|
103
|
+
enterAlternativeScreen,
|
|
104
|
+
exitAlternativeScreen,
|
|
105
|
+
cursorShow,
|
|
106
|
+
cursorHide,
|
|
107
|
+
cursorShape,
|
|
108
|
+
cursorColor,
|
|
109
|
+
resetCursorColor,
|
|
110
|
+
enableBracketedPaste,
|
|
111
|
+
disableBracketedPaste,
|
|
112
|
+
pasteStart,
|
|
113
|
+
pasteEnd,
|
|
114
|
+
bsu,
|
|
115
|
+
esu,
|
|
116
|
+
kittyQuery,
|
|
117
|
+
pushKittyKeyboard,
|
|
118
|
+
popKittyKeyboard,
|
|
119
|
+
link,
|
|
120
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
2
|
+
|
|
3
|
+
/** The canonical grapheme segmentation service for measurement and rasterization. */
|
|
4
|
+
export function* graphemes(text: string): Generator<string> {
|
|
5
|
+
for (const { segment } of segmenter.segment(text)) {
|
|
6
|
+
yield segment;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Standalone OSC 8 hyperlink builder with fallback — the string counterpart
|
|
2
|
+
// to the <Hyperlink> component, for use outside the React renderer.
|
|
3
|
+
import { link } from "#/ansi/escapes.ts";
|
|
4
|
+
import { detectHyperlinkSupport } from "#/capabilities/detect.ts";
|
|
5
|
+
|
|
6
|
+
type HyperlinkOptions = {
|
|
7
|
+
/**
|
|
8
|
+
When the terminal does not support OSC 8 hyperlinks, append the URL in
|
|
9
|
+
parentheses after the text so it stays reachable. Set to `false` to
|
|
10
|
+
return the text alone.
|
|
11
|
+
|
|
12
|
+
@default true
|
|
13
|
+
*/
|
|
14
|
+
fallback?: boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
The stream the text will be written to, for support detection.
|
|
18
|
+
|
|
19
|
+
@default process.stdout
|
|
20
|
+
*/
|
|
21
|
+
stream?: { isTTY?: boolean };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Wraps text in a clickable OSC 8 hyperlink when the terminal supports it,
|
|
26
|
+
falling back to `text (url)`.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { hyperlink } from "@alchemy.run/sigil/ansi";
|
|
30
|
+
|
|
31
|
+
console.log(hyperlink("Documentation", "https://example.com"));
|
|
32
|
+
```
|
|
33
|
+
*/
|
|
34
|
+
export const hyperlink = (
|
|
35
|
+
text: string,
|
|
36
|
+
url: string,
|
|
37
|
+
{ fallback = true, stream = process.stdout }: HyperlinkOptions = {},
|
|
38
|
+
): string => {
|
|
39
|
+
if (detectHyperlinkSupport(stream)) {
|
|
40
|
+
return link(text, url);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return fallback ? `${text} (${url})` : text;
|
|
44
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Standalone ANSI toolkit — styling, measurement, and manipulation of
|
|
2
|
+
// terminal strings. Importable without the React renderer via
|
|
3
|
+
// `@alchemy.run/sigil/ansi`.
|
|
4
|
+
|
|
5
|
+
// Styling
|
|
6
|
+
export { chalk } from "#/ansi/chalk.ts";
|
|
7
|
+
export { supportsColor } from "#/ansi/chalk.ts";
|
|
8
|
+
export { hyperlink } from "#/ansi/hyperlink.ts";
|
|
9
|
+
export * from "#/ansi/sgr.ts";
|
|
10
|
+
|
|
11
|
+
// Escape sequences (raw building blocks + named sequences)
|
|
12
|
+
export * from "#/ansi/escapes.ts";
|
|
13
|
+
export * from "#/ansi/osc.ts";
|
|
14
|
+
export { cliCursor } from "#/ansi/cursor.ts";
|
|
15
|
+
|
|
16
|
+
// Measurement
|
|
17
|
+
export { stringWidth, widestLine } from "#/ansi/string-width.ts";
|
|
18
|
+
export * from "#/ansi/east-asian-width.ts";
|
|
19
|
+
|
|
20
|
+
// String manipulation
|
|
21
|
+
export { stripAnsi } from "#/ansi/strip.ts";
|
|
22
|
+
export { sliceAnsi } from "#/ansi/slice.ts";
|
|
23
|
+
export { cliTruncate as truncateAnsi } from "#/ansi/truncate.ts";
|
|
24
|
+
export { wrapAnsi } from "#/ansi/wrap.ts";
|
|
25
|
+
|
|
26
|
+
// Tokenizer: style-aware parsing and minimal re-emission
|
|
27
|
+
export * from "#/ansi/tokenize.ts";
|
package/src/ansi/osc.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
|
|
3
|
+
import { ESC, OSC, ST } from "#/ansi/escapes.ts";
|
|
4
|
+
|
|
5
|
+
export type ClipboardSelection = "clipboard" | "primary" | "selection";
|
|
6
|
+
export type TerminalProgressState = "inactive" | "normal" | "error" | "indeterminate" | "paused";
|
|
7
|
+
|
|
8
|
+
const clipboardCode: Record<ClipboardSelection, string> = {
|
|
9
|
+
clipboard: "c",
|
|
10
|
+
primary: "p",
|
|
11
|
+
selection: "s",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const progressCode: Record<TerminalProgressState, number> = {
|
|
15
|
+
inactive: 0,
|
|
16
|
+
normal: 1,
|
|
17
|
+
error: 2,
|
|
18
|
+
indeterminate: 3,
|
|
19
|
+
paused: 4,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const field = (value: string): string => value.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
|
23
|
+
|
|
24
|
+
const command = (code: number, payload: string): string => `${OSC}${code};${payload}${ST}`;
|
|
25
|
+
|
|
26
|
+
/** Change the terminal window title (OSC 2). */
|
|
27
|
+
export const setWindowTitle = (title: string): string => command(2, field(title));
|
|
28
|
+
|
|
29
|
+
/** Announce the current directory as a file URI (OSC 7). */
|
|
30
|
+
export const setWorkingDirectory = (directory: URL | string): string => {
|
|
31
|
+
const uri =
|
|
32
|
+
directory instanceof URL
|
|
33
|
+
? directory
|
|
34
|
+
: directory.startsWith("file:")
|
|
35
|
+
? new URL(directory)
|
|
36
|
+
: pathToFileURL(directory);
|
|
37
|
+
if (uri.protocol !== "file:") throw new Error("Terminal working directory must be a file URI");
|
|
38
|
+
return command(7, uri.href);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Show a desktop notification (OSC 9). */
|
|
42
|
+
export const notify = (title: string): string => {
|
|
43
|
+
const safe = field(title);
|
|
44
|
+
return command(9, /^\d+;/.test(safe) ? ` ${safe}` : safe);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Change the terminal pointer shape (OSC 22; terminal support varies). */
|
|
48
|
+
export const setPointerShape = (shape: string): string => command(22, field(shape));
|
|
49
|
+
|
|
50
|
+
/** Write UTF-8 text to a terminal clipboard using OSC 52. */
|
|
51
|
+
export const setClipboard = (text: string, selection: ClipboardSelection = "clipboard"): string =>
|
|
52
|
+
command(52, `${clipboardCode[selection]};${Buffer.from(text).toString("base64")}`);
|
|
53
|
+
|
|
54
|
+
/** Request clipboard contents. The response is another OSC 52 sequence. */
|
|
55
|
+
export const queryClipboard = (selection: ClipboardSelection = "clipboard"): string =>
|
|
56
|
+
command(52, `${clipboardCode[selection]};?`);
|
|
57
|
+
|
|
58
|
+
/** Clear a terminal clipboard. */
|
|
59
|
+
export const clearClipboard = (selection: ClipboardSelection = "clipboard"): string =>
|
|
60
|
+
command(52, `${clipboardCode[selection]};`);
|
|
61
|
+
|
|
62
|
+
/** Report terminal-native progress using OSC 9;4. */
|
|
63
|
+
export const setTerminalProgress = (state: TerminalProgressState, value?: number): string => {
|
|
64
|
+
const code = progressCode[state];
|
|
65
|
+
if (state === "inactive" || state === "indeterminate") {
|
|
66
|
+
return command(9, `4;${code}`);
|
|
67
|
+
}
|
|
68
|
+
if (value === undefined && (state === "error" || state === "paused")) {
|
|
69
|
+
return command(9, `4;${code}`);
|
|
70
|
+
}
|
|
71
|
+
const percentage = Math.max(0, Math.min(100, Math.round(value ?? 0)));
|
|
72
|
+
return command(9, `4;${code};${percentage}`);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Wrap a sequence for transport through tmux's DCS passthrough. */
|
|
76
|
+
export const tmuxPassthrough = (sequence: string): string =>
|
|
77
|
+
`${ESC}Ptmux;${sequence.replaceAll(ESC, `${ESC}${ESC}`)}${ST}`;
|
package/src/ansi/sgr.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// ANSI SGR style tables and color-space conversions.
|
|
2
|
+
// Ported from `ansi-styles` (MIT, Sindre Sorhus).
|
|
3
|
+
import { CSI } from "#/ansi/escapes.ts";
|
|
4
|
+
|
|
5
|
+
const ANSI_BACKGROUND_OFFSET = 10;
|
|
6
|
+
|
|
7
|
+
export type StylePair = {
|
|
8
|
+
readonly open: string;
|
|
9
|
+
readonly close: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const wrapAnsi16 =
|
|
13
|
+
(offset = 0) =>
|
|
14
|
+
(code: number): string =>
|
|
15
|
+
`${CSI}${code + offset}m`;
|
|
16
|
+
|
|
17
|
+
const wrapAnsi256 =
|
|
18
|
+
(offset = 0) =>
|
|
19
|
+
(code: number): string =>
|
|
20
|
+
`${CSI}${38 + offset};5;${code}m`;
|
|
21
|
+
|
|
22
|
+
const wrapAnsi16m =
|
|
23
|
+
(offset = 0) =>
|
|
24
|
+
(red: number, green: number, blue: number): string =>
|
|
25
|
+
`${CSI}${38 + offset};2;${red};${green};${blue}m`;
|
|
26
|
+
|
|
27
|
+
const modifierCodes = {
|
|
28
|
+
reset: [0, 0],
|
|
29
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
30
|
+
bold: [1, 22],
|
|
31
|
+
dim: [2, 22],
|
|
32
|
+
italic: [3, 23],
|
|
33
|
+
underline: [4, 24],
|
|
34
|
+
overline: [53, 55],
|
|
35
|
+
inverse: [7, 27],
|
|
36
|
+
hidden: [8, 28],
|
|
37
|
+
strikethrough: [9, 29],
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
const colorCodes = {
|
|
41
|
+
black: [30, 39],
|
|
42
|
+
red: [31, 39],
|
|
43
|
+
green: [32, 39],
|
|
44
|
+
yellow: [33, 39],
|
|
45
|
+
blue: [34, 39],
|
|
46
|
+
magenta: [35, 39],
|
|
47
|
+
cyan: [36, 39],
|
|
48
|
+
white: [37, 39],
|
|
49
|
+
|
|
50
|
+
// Bright colors
|
|
51
|
+
blackBright: [90, 39],
|
|
52
|
+
gray: [90, 39], // Alias of `blackBright`
|
|
53
|
+
grey: [90, 39], // Alias of `blackBright`
|
|
54
|
+
redBright: [91, 39],
|
|
55
|
+
greenBright: [92, 39],
|
|
56
|
+
yellowBright: [93, 39],
|
|
57
|
+
blueBright: [94, 39],
|
|
58
|
+
magentaBright: [95, 39],
|
|
59
|
+
cyanBright: [96, 39],
|
|
60
|
+
whiteBright: [97, 39],
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
const bgColorCodes = {
|
|
64
|
+
bgBlack: [40, 49],
|
|
65
|
+
bgRed: [41, 49],
|
|
66
|
+
bgGreen: [42, 49],
|
|
67
|
+
bgYellow: [43, 49],
|
|
68
|
+
bgBlue: [44, 49],
|
|
69
|
+
bgMagenta: [45, 49],
|
|
70
|
+
bgCyan: [46, 49],
|
|
71
|
+
bgWhite: [47, 49],
|
|
72
|
+
|
|
73
|
+
// Bright colors
|
|
74
|
+
bgBlackBright: [100, 49],
|
|
75
|
+
bgGray: [100, 49], // Alias of `bgBlackBright`
|
|
76
|
+
bgGrey: [100, 49], // Alias of `bgBlackBright`
|
|
77
|
+
bgRedBright: [101, 49],
|
|
78
|
+
bgGreenBright: [102, 49],
|
|
79
|
+
bgYellowBright: [103, 49],
|
|
80
|
+
bgBlueBright: [104, 49],
|
|
81
|
+
bgMagentaBright: [105, 49],
|
|
82
|
+
bgCyanBright: [106, 49],
|
|
83
|
+
bgWhiteBright: [107, 49],
|
|
84
|
+
} as const;
|
|
85
|
+
|
|
86
|
+
export type ModifierName = keyof typeof modifierCodes;
|
|
87
|
+
export type ForegroundColorName = keyof typeof colorCodes;
|
|
88
|
+
export type BackgroundColorName = keyof typeof bgColorCodes;
|
|
89
|
+
export type StyleName = ModifierName | ForegroundColorName | BackgroundColorName;
|
|
90
|
+
|
|
91
|
+
export const modifierNames = Object.keys(modifierCodes) as ModifierName[];
|
|
92
|
+
export const foregroundColorNames = Object.keys(colorCodes) as ForegroundColorName[];
|
|
93
|
+
export const backgroundColorNames = Object.keys(bgColorCodes) as BackgroundColorName[];
|
|
94
|
+
|
|
95
|
+
const toPairs = <Name extends string>(
|
|
96
|
+
codes: Record<Name, readonly [number, number]>,
|
|
97
|
+
): Record<Name, StylePair> => {
|
|
98
|
+
const result = {} as Record<Name, StylePair>;
|
|
99
|
+
|
|
100
|
+
for (const [name, [open, close]] of Object.entries(codes) as Array<
|
|
101
|
+
[Name, readonly [number, number]]
|
|
102
|
+
>) {
|
|
103
|
+
result[name] = { open: `${CSI}${open}m`, close: `${CSI}${close}m` };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
Named SGR styles as `{open, close}` escape sequence pairs.
|
|
111
|
+
*/
|
|
112
|
+
export const styles: Record<StyleName, StylePair> = {
|
|
113
|
+
...toPairs(modifierCodes),
|
|
114
|
+
...toPairs(colorCodes),
|
|
115
|
+
...toPairs(bgColorCodes),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
Raw SGR code numbers: open code → close code.
|
|
120
|
+
*/
|
|
121
|
+
export const codes: ReadonlyMap<number, number> = new Map(
|
|
122
|
+
[
|
|
123
|
+
...Object.values<readonly [number, number]>(modifierCodes),
|
|
124
|
+
...Object.values<readonly [number, number]>(colorCodes),
|
|
125
|
+
...Object.values<readonly [number, number]>(bgColorCodes),
|
|
126
|
+
].map(([open, close]) => [open, close]),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
export const foreground = {
|
|
130
|
+
close: `${CSI}39m`,
|
|
131
|
+
ansi: wrapAnsi16(),
|
|
132
|
+
ansi256: wrapAnsi256(),
|
|
133
|
+
ansi16m: wrapAnsi16m(),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const background = {
|
|
137
|
+
close: `${CSI}49m`,
|
|
138
|
+
ansi: wrapAnsi16(ANSI_BACKGROUND_OFFSET),
|
|
139
|
+
ansi256: wrapAnsi256(ANSI_BACKGROUND_OFFSET),
|
|
140
|
+
ansi16m: wrapAnsi16m(ANSI_BACKGROUND_OFFSET),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Conversions from https://github.com/Qix-/color-convert
|
|
144
|
+
export const rgbToAnsi256 = (red: number, green: number, blue: number): number => {
|
|
145
|
+
// We use the extended greyscale palette here, with the exception of
|
|
146
|
+
// black and white. The normal palette only has 4 greyscale shades.
|
|
147
|
+
if (red === green && green === blue) {
|
|
148
|
+
if (red < 8) {
|
|
149
|
+
return 16;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (red > 248) {
|
|
153
|
+
return 231;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return Math.round(((red - 8) / 247) * 24) + 232;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
16 +
|
|
161
|
+
36 * Math.round((red / 255) * 5) +
|
|
162
|
+
6 * Math.round((green / 255) * 5) +
|
|
163
|
+
Math.round((blue / 255) * 5)
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const hexToRgb = (hex: string): [number, number, number] => {
|
|
168
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex);
|
|
169
|
+
if (!matches) {
|
|
170
|
+
return [0, 0, 0];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let [colorString] = matches;
|
|
174
|
+
|
|
175
|
+
if (colorString.length === 3) {
|
|
176
|
+
colorString = colorString.replaceAll(/./g, "$&$&");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const integer = Number.parseInt(colorString, 16);
|
|
180
|
+
|
|
181
|
+
/* eslint-disable no-bitwise */
|
|
182
|
+
return [(integer >> 16) & 0xff, (integer >> 8) & 0xff, integer & 0xff];
|
|
183
|
+
/* eslint-enable no-bitwise */
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const hexToAnsi256 = (hex: string): number => rgbToAnsi256(...hexToRgb(hex));
|
|
187
|
+
|
|
188
|
+
export const ansi256ToAnsi = (code: number): number => {
|
|
189
|
+
if (code < 8) {
|
|
190
|
+
return 30 + code;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (code < 16) {
|
|
194
|
+
return 90 + (code - 8);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let red;
|
|
198
|
+
let green;
|
|
199
|
+
let blue;
|
|
200
|
+
|
|
201
|
+
if (code >= 232) {
|
|
202
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
203
|
+
green = red;
|
|
204
|
+
blue = red;
|
|
205
|
+
} else {
|
|
206
|
+
code -= 16;
|
|
207
|
+
|
|
208
|
+
const remainder = code % 36;
|
|
209
|
+
|
|
210
|
+
red = Math.floor(code / 36) / 5;
|
|
211
|
+
green = Math.floor(remainder / 6) / 5;
|
|
212
|
+
blue = (remainder % 6) / 5;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const value = Math.max(red, green, blue) * 2;
|
|
216
|
+
|
|
217
|
+
if (value === 0) {
|
|
218
|
+
return 30;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// eslint-disable-next-line no-bitwise
|
|
222
|
+
let result = 30 + ((Math.round(blue) << 2) | (Math.round(green) << 1) | Math.round(red));
|
|
223
|
+
|
|
224
|
+
if (value === 2) {
|
|
225
|
+
result += 60;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const rgbToAnsi = (red: number, green: number, blue: number): number =>
|
|
232
|
+
ansi256ToAnsi(rgbToAnsi256(red, green, blue));
|
|
233
|
+
|
|
234
|
+
export const hexToAnsi = (hex: string): number => ansi256ToAnsi(hexToAnsi256(hex));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Slice a string by visible column positions, preserving active ANSI styles.
|
|
2
|
+
// Replaces `slice-ansi`, built on the shared tokenizer instead of a second
|
|
3
|
+
// escape parser: characters keep their computed style lists, and the result
|
|
4
|
+
// is re-emitted with minimal escape codes. A full-width character that would
|
|
5
|
+
// be cut in half at either boundary is dropped, like `slice-ansi` does.
|
|
6
|
+
import {
|
|
7
|
+
styledCharsFromTokens,
|
|
8
|
+
styledCharsToString,
|
|
9
|
+
tokenize,
|
|
10
|
+
type StyledChar,
|
|
11
|
+
} from "#/ansi/tokenize.ts";
|
|
12
|
+
|
|
13
|
+
export function sliceAnsi(string: string, start: number, end?: number): string {
|
|
14
|
+
const sliceEnd = end ?? Number.POSITIVE_INFINITY;
|
|
15
|
+
|
|
16
|
+
if (start >= sliceEnd || string === "") {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (start === 0 && sliceEnd === Number.POSITIVE_INFINITY) {
|
|
21
|
+
return string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const chars = styledCharsFromTokens(tokenize(string));
|
|
25
|
+
const included: StyledChar[] = [];
|
|
26
|
+
let column = 0;
|
|
27
|
+
|
|
28
|
+
for (const char of chars) {
|
|
29
|
+
const width = char.fullWidth ? 2 : 1;
|
|
30
|
+
|
|
31
|
+
if (column + width > sliceEnd) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (column >= start) {
|
|
36
|
+
included.push(char);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
column += width;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return styledCharsToString(included);
|
|
43
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Derived from `fast-string-truncated-width` (MIT, Fabio Spampinato),
|
|
2
|
+
// reduced to plain width measurement: Sigil truncates via `truncate.ts`, and
|
|
3
|
+
// no caller customizes per-class widths, so those are constants here.
|
|
4
|
+
// Width tables live in east-asian-width.ts, the single width-data module.
|
|
5
|
+
|
|
6
|
+
import { isFullWidth, isWideNotCJKTNotEmoji } from "#/ansi/east-asian-width.ts";
|
|
7
|
+
|
|
8
|
+
const getCodePointsLength = (input: string): number => {
|
|
9
|
+
let length = 0;
|
|
10
|
+
|
|
11
|
+
for (const _ of input) {
|
|
12
|
+
length += 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return length;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Unlike the original port, CSI parameters also allow `:` so ITU T.416
|
|
19
|
+
// colon-parameter SGR sequences (which Sigil preserves) measure as width 0.
|
|
20
|
+
const ANSI_RE =
|
|
21
|
+
/[\u001b\u009b][[()#;?]*[0-9;:]*[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
|
|
22
|
+
const CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
|
|
23
|
+
const CJKT_WIDE_RE =
|
|
24
|
+
/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/uy;
|
|
25
|
+
const TAB_RE = /\t{1,1000}/y;
|
|
26
|
+
const EMOJI_RE =
|
|
27
|
+
/[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/uy;
|
|
28
|
+
const LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
|
|
29
|
+
const MODIFIER_RE = /\p{M}+/gu;
|
|
30
|
+
|
|
31
|
+
const ANSI_WIDTH = 0;
|
|
32
|
+
const CONTROL_WIDTH = 0;
|
|
33
|
+
const TAB_WIDTH = 8;
|
|
34
|
+
const EMOJI_WIDTH = 2;
|
|
35
|
+
const FULL_WIDTH_WIDTH = 2;
|
|
36
|
+
const REGULAR_WIDTH = 1;
|
|
37
|
+
const WIDE_WIDTH = 2;
|
|
38
|
+
|
|
39
|
+
const PARSE_BLOCKS: [RegExp, number][] = [
|
|
40
|
+
[LATIN_RE, REGULAR_WIDTH],
|
|
41
|
+
[ANSI_RE, ANSI_WIDTH],
|
|
42
|
+
[CONTROL_RE, CONTROL_WIDTH],
|
|
43
|
+
[TAB_RE, TAB_WIDTH],
|
|
44
|
+
[EMOJI_RE, EMOJI_WIDTH],
|
|
45
|
+
[CJKT_WIDE_RE, WIDE_WIDTH],
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export const stringWidth = (input: string): number => {
|
|
49
|
+
let indexPrev = 0;
|
|
50
|
+
let index = 0;
|
|
51
|
+
const length = input.length;
|
|
52
|
+
let unmatchedStart = 0;
|
|
53
|
+
let unmatchedEnd = 0;
|
|
54
|
+
let width = 0;
|
|
55
|
+
|
|
56
|
+
outer: while (true) {
|
|
57
|
+
/* UNMATCHED */
|
|
58
|
+
|
|
59
|
+
if (unmatchedEnd > unmatchedStart || (index >= length && index > indexPrev)) {
|
|
60
|
+
const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
|
|
61
|
+
|
|
62
|
+
for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
|
|
63
|
+
const codePoint = char.codePointAt(0) || 0;
|
|
64
|
+
|
|
65
|
+
width += isFullWidth(codePoint)
|
|
66
|
+
? FULL_WIDTH_WIDTH
|
|
67
|
+
: isWideNotCJKTNotEmoji(codePoint)
|
|
68
|
+
? WIDE_WIDTH
|
|
69
|
+
: REGULAR_WIDTH;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
unmatchedStart = unmatchedEnd = 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* EXITING */
|
|
76
|
+
|
|
77
|
+
if (index >= length) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* PARSE BLOCKS */
|
|
82
|
+
|
|
83
|
+
for (let i = 0, l = PARSE_BLOCKS.length; i < l; i++) {
|
|
84
|
+
const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i]!;
|
|
85
|
+
|
|
86
|
+
BLOCK_RE.lastIndex = index;
|
|
87
|
+
|
|
88
|
+
if (BLOCK_RE.test(input)) {
|
|
89
|
+
const lengthExtra =
|
|
90
|
+
BLOCK_RE === CJKT_WIDE_RE
|
|
91
|
+
? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex))
|
|
92
|
+
: BLOCK_RE === EMOJI_RE
|
|
93
|
+
? 1
|
|
94
|
+
: BLOCK_RE.lastIndex - index;
|
|
95
|
+
|
|
96
|
+
width += lengthExtra * BLOCK_WIDTH;
|
|
97
|
+
unmatchedStart = indexPrev;
|
|
98
|
+
unmatchedEnd = index;
|
|
99
|
+
index = indexPrev = BLOCK_RE.lastIndex;
|
|
100
|
+
|
|
101
|
+
continue outer;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* UNMATCHED INDEX */
|
|
106
|
+
|
|
107
|
+
index += 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return width;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const widestLine = (text: string): number => {
|
|
114
|
+
let lineWidth = 0;
|
|
115
|
+
|
|
116
|
+
for (const line of text.split("\n")) {
|
|
117
|
+
lineWidth = Math.max(lineWidth, stringWidth(line));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lineWidth;
|
|
121
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
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, ""));
|