@alchemy.run/sigil 0.0.0-alpha.3 → 0.0.0-alpha.5
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 +23 -13
- package/THIRD_PARTY_NOTICES.md +70 -23
- package/dist/Text-D5HUf3Fj.d.ts +452 -0
- package/dist/ansi.d.ts +137 -143
- package/dist/ansi.js +87 -2
- 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-BMzMwV7Q.d.ts +22 -0
- package/dist/color-policy-SVj1pYTA.js +560 -0
- package/dist/color-profile-DHhQHY55.js +36 -0
- package/dist/color-profile-u0Nhe9Nv.d.ts +97 -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-Bh4yGP6w.d.ts +186 -0
- package/dist/detect-BuTXtY6e.js +373 -0
- package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
- 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-Bmc2tRPk.d.ts +21 -0
- package/dist/index.d.ts +235 -754
- package/dist/index.js +1362 -3001
- package/dist/osc-CCH7xDoS.js +71 -0
- package/dist/osc-Cn0fw77g.d.ts +23 -0
- package/dist/paint-C19minOS.d.ts +81 -0
- package/dist/query-vaIeGOkH.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-BOh__-65.js +324 -0
- package/dist/screen-BReKIheE.d.ts +40 -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-BJmzX2NJ.js +664 -0
- package/dist/sgr-BhwaWAJB.js +246 -0
- package/dist/store-CgrG9K4y.d.ts +72 -0
- package/dist/string-width-CijQwpIk.js +69 -0
- package/dist/strip-BvU4toXG.js +6 -0
- package/dist/terminal.d.ts +118 -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-C2sciZOo.js +1335 -0
- package/package.json +43 -17
- package/src/ansi/chalk.ts +12 -53
- package/src/ansi/cursor.ts +2 -4
- package/src/ansi/east-asian-width.ts +44 -0
- package/src/ansi/escapes.ts +18 -26
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +14 -14
- package/src/ansi/osc.ts +77 -0
- 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/tokenize.ts +96 -169
- package/src/ansi/truncate.ts +5 -3
- package/src/ansi/wrap.ts +13 -14
- package/src/ansi-tokenizer.ts +1 -1
- package/src/capabilities/color-policy.ts +34 -0
- package/src/capabilities/detect.ts +594 -0
- package/src/capabilities/index.ts +37 -0
- package/src/capabilities/query.ts +657 -0
- package/src/capabilities/store.ts +379 -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/AnsiText.tsx +42 -0
- package/src/components/App.tsx +109 -22
- package/src/components/BackgroundContext.ts +2 -3
- package/src/components/Box.tsx +43 -60
- package/src/components/CursorContext.ts +1 -1
- package/src/components/ErrorBoundary.tsx +1 -1
- package/src/components/ErrorOverview.tsx +8 -7
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/Spacer.tsx +1 -1
- package/src/components/Static.tsx +1 -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/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +23 -46
- package/src/components/Transform.tsx +2 -2
- package/src/cursor-position.ts +1 -1
- package/src/devtools.ts +84 -24
- package/src/dom.ts +11 -8
- package/src/env.ts +12 -0
- package/src/get-max-width.ts +1 -1
- package/src/global.d.ts +6 -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-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +3 -3
- 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-terminal-osc.ts +59 -0
- package/src/hooks/use-window-size.ts +2 -2
- package/src/index.ts +88 -44
- package/src/ink.tsx +620 -745
- package/src/input-parser.ts +1 -1
- package/src/instances.ts +1 -1
- package/src/kitty-keyboard.ts +128 -0
- package/src/measure-element.ts +1 -1
- package/src/measure-text.ts +2 -2
- package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
- 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 +36 -62
- package/src/render-background.ts +37 -16
- package/src/render-border.ts +97 -64
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +24 -9
- package/src/render.ts +24 -16
- 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 +1 -1
- package/src/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +137 -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 +272 -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 +4 -5
- package/src/squash-text-nodes.ts +4 -7
- package/src/stream.ts +2 -4
- package/src/structured-text.ts +321 -0
- package/src/styles.ts +16 -17
- package/src/terminal/index.ts +2 -0
- package/src/terminal/inline-presenter.ts +120 -0
- package/src/terminal/input.ts +86 -0
- package/src/terminal/render-scheduler.ts +37 -0
- package/src/terminal/screen-presenter.ts +188 -0
- package/src/terminal/session.ts +407 -0
- package/src/terminal-size.ts +9 -8
- 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 +349 -0
- package/src/testing/vitest.ts +157 -0
- package/src/transform-adapter.ts +14 -0
- 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/dist/sgr-CMfEpjSk.d.ts +0 -91
- package/dist/truncate-CBiyyZzw.js +0 -2156
- package/src/ansi/supports-color.ts +0 -213
- package/src/ansi/widest-line.ts +0 -12
- package/src/auto-bind.ts +0 -41
- package/src/colorize.ts +0 -60
- 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/log-update.ts +0 -370
- package/src/output.ts +0 -308
- package/src/renderer.ts +0 -73
- package/src/write-synchronized.ts +0 -9
package/src/input-parser.ts
CHANGED
package/src/instances.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
// This map has to be stored in a separate file, because render.js creates instances,
|
|
5
5
|
// but instance.js should delete itself from the map on unmount
|
|
6
6
|
|
|
7
|
-
import type { Ink } from "
|
|
7
|
+
import type { Ink } from "#/ink.tsx";
|
|
8
8
|
|
|
9
9
|
export const instances = new WeakMap<NodeJS.WritableStream, Ink>();
|
package/src/kitty-keyboard.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { kittyQuery } from "#/ansi/escapes.ts";
|
|
2
|
+
|
|
1
3
|
// Kitty keyboard protocol flags.
|
|
2
4
|
// @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
3
5
|
export const kittyFlags = {
|
|
@@ -55,3 +57,129 @@ export type KittyKeyboardOptions = {
|
|
|
55
57
|
// - 'reportAssociatedText' - Report associated text with key events
|
|
56
58
|
flags?: KittyFlagName[];
|
|
57
59
|
};
|
|
60
|
+
|
|
61
|
+
const textEncoder = new TextEncoder();
|
|
62
|
+
|
|
63
|
+
const kittyQueryEscapeByte = 0x1b;
|
|
64
|
+
const kittyQueryOpenBracketByte = 0x5b;
|
|
65
|
+
const kittyQueryQuestionMarkByte = 0x3f;
|
|
66
|
+
const kittyQueryLetterByte = 0x75;
|
|
67
|
+
const zeroByte = 0x30;
|
|
68
|
+
const nineByte = 0x39;
|
|
69
|
+
|
|
70
|
+
type KittyQueryResponseMatch = { state: "complete"; endIndex: number } | { state: "partial" };
|
|
71
|
+
|
|
72
|
+
const isDigitByte = (byte: number): boolean => byte >= zeroByte && byte <= nineByte;
|
|
73
|
+
|
|
74
|
+
const matchKittyQueryResponse = (
|
|
75
|
+
buffer: number[],
|
|
76
|
+
startIndex: number,
|
|
77
|
+
): KittyQueryResponseMatch | undefined => {
|
|
78
|
+
if (
|
|
79
|
+
buffer[startIndex] !== kittyQueryEscapeByte ||
|
|
80
|
+
buffer[startIndex + 1] !== kittyQueryOpenBracketByte ||
|
|
81
|
+
buffer[startIndex + 2] !== kittyQueryQuestionMarkByte
|
|
82
|
+
) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let index = startIndex + 3;
|
|
87
|
+
const digitsStartIndex = index;
|
|
88
|
+
while (index < buffer.length && isDigitByte(buffer[index]!)) {
|
|
89
|
+
index++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (index === digitsStartIndex) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (index === buffer.length) {
|
|
97
|
+
return { state: "partial" };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (buffer[index] === kittyQueryLetterByte) {
|
|
101
|
+
return { state: "complete", endIndex: index };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const hasCompleteKittyQueryResponse = (buffer: number[]): boolean => {
|
|
108
|
+
for (let index = 0; index < buffer.length; index++) {
|
|
109
|
+
const match = matchKittyQueryResponse(buffer, index);
|
|
110
|
+
if (match?.state === "complete") {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const stripKittyQueryResponsesAndTrailingPartial = (buffer: number[]): number[] => {
|
|
119
|
+
const keptBytes: number[] = [];
|
|
120
|
+
let index = 0;
|
|
121
|
+
while (index < buffer.length) {
|
|
122
|
+
const match = matchKittyQueryResponse(buffer, index);
|
|
123
|
+
if (match?.state === "complete") {
|
|
124
|
+
index = match.endIndex + 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (match?.state === "partial") {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
keptBytes.push(buffer[index]!);
|
|
133
|
+
index++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return keptBytes;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Query the terminal for kitty keyboard protocol support. The CSI ? u query is
|
|
140
|
+
// safe to send to any terminal — unsupporting terminals simply won't respond,
|
|
141
|
+
// and the 200ms timeout handles that. Stdin bytes that aren't part of the
|
|
142
|
+
// protocol response are re-emitted so they aren't lost from the caller's input
|
|
143
|
+
// pipeline. Returns a cancel function; cancelling (or the timeout firing)
|
|
144
|
+
// means `onSupported` never runs.
|
|
145
|
+
export const detectKittySupport = (
|
|
146
|
+
stdin: NodeJS.ReadableStream,
|
|
147
|
+
stdout: { write: (data: string) => unknown },
|
|
148
|
+
onSupported: () => void,
|
|
149
|
+
): (() => void) => {
|
|
150
|
+
let responseBuffer: number[] = [];
|
|
151
|
+
|
|
152
|
+
const cleanup = (): void => {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
stdin.removeListener("data", onData);
|
|
155
|
+
|
|
156
|
+
// Re-emit any buffered data that wasn't the protocol response.
|
|
157
|
+
// Clear responseBuffer afterwards to make cleanup idempotent.
|
|
158
|
+
const remaining = stripKittyQueryResponsesAndTrailingPartial(responseBuffer);
|
|
159
|
+
responseBuffer = [];
|
|
160
|
+
if (remaining.length > 0) {
|
|
161
|
+
stdin.unshift(Uint8Array.from(remaining));
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const onData = (data: Uint8Array | string): void => {
|
|
166
|
+
const chunk = typeof data === "string" ? textEncoder.encode(data) : data;
|
|
167
|
+
for (const byte of chunk) {
|
|
168
|
+
responseBuffer.push(byte);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (hasCompleteKittyQueryResponse(responseBuffer)) {
|
|
172
|
+
cleanup();
|
|
173
|
+
onSupported();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Attach listener before writing the query so that synchronous
|
|
178
|
+
// or immediate responses are not missed.
|
|
179
|
+
stdin.on("data", onData);
|
|
180
|
+
const timer = setTimeout(cleanup, 200);
|
|
181
|
+
|
|
182
|
+
stdout.write(kittyQuery);
|
|
183
|
+
|
|
184
|
+
return cleanup;
|
|
185
|
+
};
|
package/src/measure-element.ts
CHANGED
package/src/measure-text.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { widestLine } from "
|
|
2
|
-
import { QuickLru as QuickLRU } from "
|
|
1
|
+
import { widestLine } from "#/ansi/string-width.ts";
|
|
2
|
+
import { QuickLru as QuickLRU } from "#/quick-lru.ts";
|
|
3
3
|
|
|
4
4
|
const cache = new QuickLRU<string, Output>({ maxSize: 4096 });
|
|
5
5
|
|
|
@@ -1,35 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const yogaNode = node.childNodes[0]?.yogaNode;
|
|
20
|
-
|
|
21
|
-
if (yogaNode) {
|
|
22
|
-
const offsetX = yogaNode.getComputedLeft();
|
|
23
|
-
const offsetY = yogaNode.getComputedTop();
|
|
24
|
-
text = "\n".repeat(offsetY) + indentString(text, offsetX);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return text;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type OutputTransformer = (s: string, index: number) => string;
|
|
31
|
-
|
|
32
|
-
export const renderNodeToScreenReaderOutput = (
|
|
1
|
+
import { type DOMElement } from "#/dom.ts";
|
|
2
|
+
import { getMaxWidth } from "#/get-max-width.ts";
|
|
3
|
+
import { renderBackground } from "#/render-background.ts";
|
|
4
|
+
import { renderBorder } from "#/render-border.ts";
|
|
5
|
+
import type { Canvas } from "#/screen/canvas.ts";
|
|
6
|
+
import { serializeLine } from "#/screen/serialize.ts";
|
|
7
|
+
import { squashTextNodes } from "#/squash-text-nodes.ts";
|
|
8
|
+
import {
|
|
9
|
+
hasCompatibilityText,
|
|
10
|
+
structuredTextBaseStyle,
|
|
11
|
+
structuredTextLines,
|
|
12
|
+
wrapStructuredText,
|
|
13
|
+
sampleStructuredText,
|
|
14
|
+
} from "#/structured-text.ts";
|
|
15
|
+
import type { AnsiTransformer } from "#/transform-adapter.ts";
|
|
16
|
+
import { Yoga } from "#/yoga/index.ts";
|
|
17
|
+
|
|
18
|
+
export const renderAccessibleText = (
|
|
33
19
|
node: DOMElement,
|
|
34
20
|
options: {
|
|
35
21
|
parentRole?: string;
|
|
@@ -59,7 +45,7 @@ export const renderNodeToScreenReaderOutput = (
|
|
|
59
45
|
|
|
60
46
|
output = childNodes
|
|
61
47
|
.map((childNode) => {
|
|
62
|
-
const screenReaderOutput =
|
|
48
|
+
const screenReaderOutput = renderAccessibleText(childNode as DOMElement, {
|
|
63
49
|
parentRole: node.internal_accessibility?.role,
|
|
64
50
|
skipStaticElements: options.skipStaticElements,
|
|
65
51
|
});
|
|
@@ -90,13 +76,13 @@ export const renderNodeToScreenReaderOutput = (
|
|
|
90
76
|
};
|
|
91
77
|
|
|
92
78
|
// After nodes are laid out, render each to output object, which later gets rendered to terminal
|
|
93
|
-
export const
|
|
79
|
+
export const paintTree = (
|
|
94
80
|
node: DOMElement,
|
|
95
|
-
output:
|
|
81
|
+
output: Canvas,
|
|
96
82
|
options: {
|
|
97
83
|
offsetX?: number;
|
|
98
84
|
offsetY?: number;
|
|
99
|
-
transformers?:
|
|
85
|
+
transformers?: AnsiTransformer[];
|
|
100
86
|
skipStaticElements: boolean;
|
|
101
87
|
},
|
|
102
88
|
) => {
|
|
@@ -118,28 +104,54 @@ export const renderNodeToOutput = (
|
|
|
118
104
|
const y = offsetY + yogaNode.getComputedTop();
|
|
119
105
|
|
|
120
106
|
// Transformers are functions that transform final text output of each component
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
newTransformers = [node.internal_transform, ...transformers];
|
|
126
|
-
}
|
|
107
|
+
// The canvas applies explicit compatibility transformers at this boundary.
|
|
108
|
+
const newTransformers = node.internal_transform
|
|
109
|
+
? [node.internal_transform, ...transformers]
|
|
110
|
+
: transformers;
|
|
127
111
|
|
|
128
112
|
if (node.nodeName === "ink-text") {
|
|
129
|
-
|
|
113
|
+
const text = squashTextNodes(node);
|
|
130
114
|
|
|
131
115
|
if (text.length > 0) {
|
|
132
|
-
const currentWidth = widestLine(text);
|
|
133
116
|
const maxWidth = getMaxWidth(yogaNode);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
const firstChildYoga = node.childNodes[0]?.yogaNode;
|
|
118
|
+
const paddingX = firstChildYoga?.getComputedLeft() ?? 0;
|
|
119
|
+
const paddingY = firstChildYoga?.getComputedTop() ?? 0;
|
|
120
|
+
const lines = wrapStructuredText(
|
|
121
|
+
structuredTextLines(node),
|
|
122
|
+
maxWidth,
|
|
123
|
+
node.style.textWrap ?? "wrap",
|
|
124
|
+
structuredTextBaseStyle(node),
|
|
125
|
+
);
|
|
126
|
+
const paintBounds = {
|
|
127
|
+
x: x + paddingX,
|
|
128
|
+
y: y + paddingY,
|
|
129
|
+
width: Math.max(
|
|
130
|
+
1,
|
|
131
|
+
...lines.map((line) => line.reduce((width, cell) => width + cell.width, 0)),
|
|
132
|
+
),
|
|
133
|
+
height: lines.length,
|
|
134
|
+
};
|
|
135
|
+
const sampled = sampleStructuredText(lines, paintBounds, output.paintContext);
|
|
136
|
+
|
|
137
|
+
if (hasCompatibilityText(node)) {
|
|
138
|
+
const textTransformers = collectTransformers(node, transformers);
|
|
139
|
+
output.writeAnsi(
|
|
140
|
+
paintBounds.x,
|
|
141
|
+
paintBounds.y,
|
|
142
|
+
sampled
|
|
143
|
+
.map((line) =>
|
|
144
|
+
serializeLine(line, {
|
|
145
|
+
colorProfile: output.paintContext.profile ?? "truecolor",
|
|
146
|
+
trimEnd: false,
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
.join("\n"),
|
|
150
|
+
{ transformers: textTransformers },
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
output.writeCells(paintBounds.x, paintBounds.y, sampled);
|
|
138
154
|
}
|
|
139
|
-
|
|
140
|
-
text = applyPaddingToText(node, text);
|
|
141
|
-
|
|
142
|
-
output.write(x, y, text, { transformers: newTransformers });
|
|
143
155
|
}
|
|
144
156
|
|
|
145
157
|
return;
|
|
@@ -175,7 +187,7 @@ export const renderNodeToOutput = (
|
|
|
175
187
|
|
|
176
188
|
if (node.nodeName === "ink-root" || node.nodeName === "ink-box") {
|
|
177
189
|
for (const childNode of node.childNodes) {
|
|
178
|
-
|
|
190
|
+
paintTree(childNode as DOMElement, output, {
|
|
179
191
|
offsetX: x,
|
|
180
192
|
offsetY: y,
|
|
181
193
|
transformers: newTransformers,
|
|
@@ -189,3 +201,16 @@ export const renderNodeToOutput = (
|
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
203
|
};
|
|
204
|
+
|
|
205
|
+
function collectTransformers(
|
|
206
|
+
node: DOMElement,
|
|
207
|
+
inherited: readonly AnsiTransformer[],
|
|
208
|
+
): AnsiTransformer[] {
|
|
209
|
+
return [
|
|
210
|
+
...node.childNodes.flatMap((child) =>
|
|
211
|
+
child.nodeName === "#text" ? [] : collectTransformers(child, []),
|
|
212
|
+
),
|
|
213
|
+
...(node.internal_transform ? [node.internal_transform] : []),
|
|
214
|
+
...inherited,
|
|
215
|
+
];
|
|
216
|
+
}
|
package/src/parse-keypress.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copied from https://github.com/enquirer/enquirer/blob/36785f3399a41cd61e9d28d1eb9c2fcd73d69b4c/lib/keypress.js
|
|
2
|
-
import { DEL, ESC } from "
|
|
3
|
-
import { kittyModifiers } from "
|
|
2
|
+
import { DEL, ESC } from "#/ansi/escapes.ts";
|
|
3
|
+
import { kittyModifiers } from "#/kitty-keyboard.ts";
|
|
4
4
|
|
|
5
5
|
const textDecoder = new TextDecoder();
|
|
6
6
|
|
|
@@ -396,8 +396,7 @@ export const parseKeypress = (s: Uint8Array | string = ""): ParsedKey => {
|
|
|
396
396
|
|
|
397
397
|
if (s instanceof Uint8Array) {
|
|
398
398
|
if (s[0]! > 127 && s[1] === undefined) {
|
|
399
|
-
(s[0]
|
|
400
|
-
s = ESC + textDecoder.decode(s);
|
|
399
|
+
s = ESC + textDecoder.decode(Uint8Array.of(s[0]! - 128));
|
|
401
400
|
} else {
|
|
402
401
|
s = textDecoder.decode(s);
|
|
403
402
|
}
|
|
@@ -499,7 +498,7 @@ export const parseKeypress = (s: Uint8Array | string = ""): ParsedKey => {
|
|
|
499
498
|
// the modifier key bitflag and any meaningless "1;" sequence
|
|
500
499
|
const code = [parts[1], parts[2], parts[4], parts[6]].filter(Boolean).join("");
|
|
501
500
|
|
|
502
|
-
const modifier = (
|
|
501
|
+
const modifier = Number(parts[3] || parts[5] || 1) - 1;
|
|
503
502
|
|
|
504
503
|
// Parse the key modifier
|
|
505
504
|
key.ctrl = !!(modifier & 4);
|
package/src/parse-stack-line.ts
CHANGED
package/src/patch-console.ts
CHANGED
|
@@ -60,3 +60,47 @@ export const patchConsole = (callback: Callback): Restore => {
|
|
|
60
60
|
originalMethods.clear();
|
|
61
61
|
};
|
|
62
62
|
};
|
|
63
|
+
|
|
64
|
+
// Intercept direct `write` calls on a stream. The write is swallowed and the
|
|
65
|
+
// decoded chunk is handed to `onData`; write callbacks still fire so callers
|
|
66
|
+
// awaiting flushes don't hang.
|
|
67
|
+
export const patchStreamWrite = (
|
|
68
|
+
stream: NodeJS.WritableStream,
|
|
69
|
+
onData: (data: string) => void,
|
|
70
|
+
): Restore => {
|
|
71
|
+
// Keep the unbound reference: restore below must reassign the exact
|
|
72
|
+
// original function object, not a bound copy.
|
|
73
|
+
// oxlint-disable-next-line typescript/unbound-method
|
|
74
|
+
const originalWrite = stream.write;
|
|
75
|
+
|
|
76
|
+
const patchedWrite = (
|
|
77
|
+
chunk: unknown,
|
|
78
|
+
encodingOrCallback?: unknown,
|
|
79
|
+
callback?: unknown,
|
|
80
|
+
): boolean => {
|
|
81
|
+
const data =
|
|
82
|
+
typeof chunk === "string"
|
|
83
|
+
? chunk
|
|
84
|
+
: chunk instanceof Uint8Array
|
|
85
|
+
? Buffer.from(chunk).toString()
|
|
86
|
+
: String(chunk);
|
|
87
|
+
|
|
88
|
+
onData(data);
|
|
89
|
+
|
|
90
|
+
const done =
|
|
91
|
+
typeof encodingOrCallback === "function"
|
|
92
|
+
? encodingOrCallback
|
|
93
|
+
: typeof callback === "function"
|
|
94
|
+
? callback
|
|
95
|
+
: undefined;
|
|
96
|
+
done?.();
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
stream.write = patchedWrite;
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
stream.write = originalWrite;
|
|
105
|
+
};
|
|
106
|
+
};
|
package/src/reconciler.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { createContext, version as reactVersion } from "react";
|
|
1
|
+
import { createContext } from "react";
|
|
4
2
|
import createReconciler, { type ReactContext } from "react-reconciler";
|
|
5
3
|
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants.js";
|
|
6
4
|
import * as Scheduler from "scheduler";
|
|
@@ -20,27 +18,23 @@ import {
|
|
|
20
18
|
type TextNode,
|
|
21
19
|
type ElementNames,
|
|
22
20
|
type DOMElement,
|
|
23
|
-
} from "
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
21
|
+
} from "#/dom.ts";
|
|
22
|
+
import { isSigilDev } from "#/env.ts";
|
|
23
|
+
import type { SemanticTextStyle } from "#/semantic-text-style.ts";
|
|
24
|
+
import { styles as applyStyles, type Styles } from "#/styles.ts";
|
|
25
|
+
import { type AnsiTransformer } from "#/transform-adapter.ts";
|
|
26
|
+
import { Yoga } from "#/yoga/index.ts";
|
|
27
|
+
|
|
28
|
+
import pkg from "../package.json" with { type: "json" };
|
|
27
29
|
|
|
28
30
|
// We need to conditionally perform devtools connection to avoid
|
|
29
31
|
// accidentally breaking other third-party code.
|
|
30
32
|
// See https://github.com/vadimdemedes/ink/issues/384
|
|
31
33
|
// See https://github.com/vadimdemedes/ink/issues/648
|
|
32
|
-
if (
|
|
34
|
+
if (isSigilDev) {
|
|
33
35
|
// Intentionally no warning when the package is missing.
|
|
34
36
|
// SIGIL_DEV may be set for other reasons; devtools is opt-in via installing the package.
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
import.meta.resolve("react-devtools-core");
|
|
38
|
-
isDevtoolsInstalled = true;
|
|
39
|
-
} catch {}
|
|
40
|
-
|
|
41
|
-
if (isDevtoolsInstalled) {
|
|
42
|
-
await import("./devtools.ts");
|
|
43
|
-
}
|
|
37
|
+
await import("#/devtools.ts").catch(() => {});
|
|
44
38
|
}
|
|
45
39
|
|
|
46
40
|
type AnyObject = Record<string, unknown>;
|
|
@@ -138,46 +132,6 @@ type HostContext = {
|
|
|
138
132
|
|
|
139
133
|
let currentUpdatePriority = NoEventPriority;
|
|
140
134
|
|
|
141
|
-
async function loadPackageJson() {
|
|
142
|
-
const fs = await import("node:fs");
|
|
143
|
-
const content = fs.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
144
|
-
|
|
145
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
146
|
-
const parsedContent = JSON.parse(content) as
|
|
147
|
-
| {
|
|
148
|
-
name?: string;
|
|
149
|
-
version?: string;
|
|
150
|
-
}
|
|
151
|
-
| undefined;
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
name: parsedContent?.name,
|
|
155
|
-
version: parsedContent?.version,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
let packageInfo = {
|
|
160
|
-
name: "ink",
|
|
161
|
-
version: reactVersion,
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
if (process.env["DEV"] === "true") {
|
|
165
|
-
try {
|
|
166
|
-
const loaded = await loadPackageJson();
|
|
167
|
-
packageInfo = {
|
|
168
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
169
|
-
name: loaded.name || packageInfo.name,
|
|
170
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
171
|
-
version: loaded.version || packageInfo.version,
|
|
172
|
-
};
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.warn(
|
|
175
|
-
"Failed to load package.json in development mode. Falling back to default renderer metadata.",
|
|
176
|
-
error,
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
135
|
export const reconciler = createReconciler<
|
|
182
136
|
ElementNames,
|
|
183
137
|
Props,
|
|
@@ -246,7 +200,7 @@ export const reconciler = createReconciler<
|
|
|
246
200
|
shouldSetTextContent: () => false,
|
|
247
201
|
createInstance(originalType, newProps, rootNode, hostContext) {
|
|
248
202
|
if (hostContext.isInsideText && originalType === "ink-box") {
|
|
249
|
-
throw new Error(`<Box> can
|
|
203
|
+
throw new Error(`<Box> can't be nested inside <Text> component`);
|
|
250
204
|
}
|
|
251
205
|
|
|
252
206
|
const type =
|
|
@@ -273,7 +227,17 @@ export const reconciler = createReconciler<
|
|
|
273
227
|
|
|
274
228
|
if (key === "internal_transform") {
|
|
275
229
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
276
|
-
node.internal_transform = value as
|
|
230
|
+
node.internal_transform = value as AnsiTransformer;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (key === "internal_ansi") {
|
|
235
|
+
node.internal_ansi = value === true;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (key === "internal_textStyle") {
|
|
240
|
+
node.internal_textStyle = value as SemanticTextStyle;
|
|
277
241
|
continue;
|
|
278
242
|
}
|
|
279
243
|
|
|
@@ -378,7 +342,17 @@ export const reconciler = createReconciler<
|
|
|
378
342
|
|
|
379
343
|
if (key === "internal_transform") {
|
|
380
344
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
381
|
-
node.internal_transform = value as
|
|
345
|
+
node.internal_transform = value as AnsiTransformer;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (key === "internal_ansi") {
|
|
350
|
+
node.internal_ansi = value === true;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (key === "internal_textStyle") {
|
|
355
|
+
node.internal_textStyle = value as SemanticTextStyle | undefined;
|
|
382
356
|
continue;
|
|
383
357
|
}
|
|
384
358
|
|
|
@@ -446,6 +420,6 @@ export const reconciler = createReconciler<
|
|
|
446
420
|
waitForCommitToBeReady() {
|
|
447
421
|
return null;
|
|
448
422
|
},
|
|
449
|
-
rendererPackageName:
|
|
450
|
-
rendererVersion:
|
|
423
|
+
rendererPackageName: pkg.name,
|
|
424
|
+
rendererVersion: pkg.version,
|
|
451
425
|
});
|
package/src/render-background.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type DOMNode } from "
|
|
3
|
-
import type {
|
|
1
|
+
import { samplePaint } from "#/color/sample.ts";
|
|
2
|
+
import { type DOMNode } from "#/dom.ts";
|
|
3
|
+
import type { Canvas } from "#/screen/canvas.ts";
|
|
4
|
+
import { cellAttributes, createCell } from "#/screen/index.ts";
|
|
4
5
|
|
|
5
|
-
export const renderBackground = (x: number, y: number, node: DOMNode, output:
|
|
6
|
-
if (
|
|
6
|
+
export const renderBackground = (x: number, y: number, node: DOMNode, output: Canvas): void => {
|
|
7
|
+
if (node.style.backgroundColor === undefined) {
|
|
7
8
|
return;
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -23,16 +24,36 @@ export const renderBackground = (x: number, y: number, node: DOMNode, output: Ou
|
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const bounds = {
|
|
28
|
+
x: x + leftBorderWidth,
|
|
29
|
+
y: y + topBorderHeight,
|
|
30
|
+
width: contentWidth,
|
|
31
|
+
height: contentHeight,
|
|
32
|
+
};
|
|
33
|
+
const lines = Array.from({ length: contentHeight }, (_unused, row) =>
|
|
34
|
+
Array.from({ length: contentWidth }, (_empty, column) => {
|
|
35
|
+
const resetBackground = node.style.backgroundColor === "";
|
|
36
|
+
const background = resetBackground
|
|
37
|
+
? undefined
|
|
38
|
+
: samplePaint(
|
|
39
|
+
node.style.backgroundColor!,
|
|
40
|
+
bounds.x + column,
|
|
41
|
+
bounds.y + row,
|
|
42
|
+
bounds,
|
|
43
|
+
output.paintContext,
|
|
44
|
+
);
|
|
45
|
+
return createCell(
|
|
46
|
+
" ",
|
|
47
|
+
1,
|
|
48
|
+
{
|
|
49
|
+
...(background ? { background } : {}),
|
|
50
|
+
underline: "none",
|
|
51
|
+
attributes: cellAttributes.none,
|
|
52
|
+
},
|
|
53
|
+
undefined,
|
|
54
|
+
resetBackground ? { background: true } : undefined,
|
|
55
|
+
);
|
|
56
|
+
}),
|
|
31
57
|
);
|
|
32
|
-
|
|
33
|
-
for (let row = 0; row < contentHeight; row++) {
|
|
34
|
-
output.write(x + leftBorderWidth, y + topBorderHeight + row, backgroundLine, {
|
|
35
|
-
transformers: [],
|
|
36
|
-
});
|
|
37
|
-
}
|
|
58
|
+
output.writeCells(bounds.x, bounds.y, lines);
|
|
38
59
|
};
|