@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/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/log-update.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cliCursor } from "
|
|
2
|
-
import { ansiEscapes } from "
|
|
1
|
+
import { cliCursor } from "#/ansi/cursor.ts";
|
|
2
|
+
import { ansiEscapes } from "#/ansi/escapes.ts";
|
|
3
3
|
import {
|
|
4
4
|
type CursorPosition,
|
|
5
5
|
cursorPositionChanged,
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
buildCursorOnlySequence,
|
|
8
8
|
buildReturnToBottomPrefix,
|
|
9
9
|
hideCursorEscape,
|
|
10
|
-
} from "
|
|
10
|
+
} from "#/cursor-position.ts";
|
|
11
11
|
|
|
12
|
-
export type { CursorPosition } from "
|
|
12
|
+
export type { CursorPosition } from "#/cursor-position.ts";
|
|
13
13
|
|
|
14
14
|
export type LogUpdate = {
|
|
15
15
|
clear: () => void;
|
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
|
|
package/src/output.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { sliceAnsi } from "
|
|
2
|
-
import { stringWidth } from "
|
|
1
|
+
import { sliceAnsi } from "#/ansi/slice.ts";
|
|
2
|
+
import { stringWidth } from "#/ansi/string-width.ts";
|
|
3
3
|
import {
|
|
4
4
|
type StyledChar,
|
|
5
5
|
styledCharsFromTokens,
|
|
6
6
|
styledCharsToString,
|
|
7
7
|
tokenize,
|
|
8
|
-
} from "
|
|
9
|
-
import { type OutputTransformer } from "
|
|
8
|
+
} from "#/ansi/tokenize.ts";
|
|
9
|
+
import { type OutputTransformer } from "#/render-node-to-output.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
"Virtual" output class
|
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,22 @@ 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 OutputTransformer } from "#/render-node-to-output.ts";
|
|
24
|
+
import { styles as applyStyles, type Styles } from "#/styles.ts";
|
|
25
|
+
import { Yoga } from "#/yoga/index.ts";
|
|
26
|
+
|
|
27
|
+
import pkg from "../package.json" with { type: "json" };
|
|
27
28
|
|
|
28
29
|
// We need to conditionally perform devtools connection to avoid
|
|
29
30
|
// accidentally breaking other third-party code.
|
|
30
31
|
// See https://github.com/vadimdemedes/ink/issues/384
|
|
31
32
|
// See https://github.com/vadimdemedes/ink/issues/648
|
|
32
|
-
if (
|
|
33
|
+
if (isSigilDev) {
|
|
33
34
|
// Intentionally no warning when the package is missing.
|
|
34
35
|
// 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
|
-
}
|
|
36
|
+
await import("#/devtools.ts").catch(() => {});
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
type AnyObject = Record<string, unknown>;
|
|
@@ -138,46 +131,6 @@ type HostContext = {
|
|
|
138
131
|
|
|
139
132
|
let currentUpdatePriority = NoEventPriority;
|
|
140
133
|
|
|
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
134
|
export const reconciler = createReconciler<
|
|
182
135
|
ElementNames,
|
|
183
136
|
Props,
|
|
@@ -246,7 +199,7 @@ export const reconciler = createReconciler<
|
|
|
246
199
|
shouldSetTextContent: () => false,
|
|
247
200
|
createInstance(originalType, newProps, rootNode, hostContext) {
|
|
248
201
|
if (hostContext.isInsideText && originalType === "ink-box") {
|
|
249
|
-
throw new Error(`<Box> can
|
|
202
|
+
throw new Error(`<Box> can't be nested inside <Text> component`);
|
|
250
203
|
}
|
|
251
204
|
|
|
252
205
|
const type =
|
|
@@ -446,6 +399,6 @@ export const reconciler = createReconciler<
|
|
|
446
399
|
waitForCommitToBeReady() {
|
|
447
400
|
return null;
|
|
448
401
|
},
|
|
449
|
-
rendererPackageName:
|
|
450
|
-
rendererVersion:
|
|
402
|
+
rendererPackageName: pkg.name,
|
|
403
|
+
rendererVersion: pkg.version,
|
|
451
404
|
});
|
package/src/render-background.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { colorize } from "
|
|
2
|
-
import { type DOMNode } from "
|
|
3
|
-
import type { Output } from "
|
|
1
|
+
import { colorize } from "#/colorize.ts";
|
|
2
|
+
import { type DOMNode } from "#/dom.ts";
|
|
3
|
+
import type { Output } from "#/output.ts";
|
|
4
4
|
|
|
5
5
|
export const renderBackground = (x: number, y: number, node: DOMNode, output: Output): void => {
|
|
6
6
|
if (!node.style.backgroundColor) {
|
package/src/render-border.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { chalk } from "
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type { Output } from "
|
|
1
|
+
import { chalk } from "#/ansi/chalk.ts";
|
|
2
|
+
import { colorize } from "#/colorize.ts";
|
|
3
|
+
import { type DOMNode } from "#/dom.ts";
|
|
4
|
+
import { BOXES } from "#/glyphs.ts";
|
|
5
|
+
import type { Output } from "#/output.ts";
|
|
6
6
|
|
|
7
7
|
const stylePiece = (segment: string, fg?: string, bg?: string, dim?: boolean): string => {
|
|
8
8
|
let styled = colorize(segment, fg, "foreground");
|
|
@@ -20,7 +20,7 @@ export const renderBorder = (x: number, y: number, node: DOMNode, output: Output
|
|
|
20
20
|
const height = node.yogaNode!.getComputedHeight();
|
|
21
21
|
const box =
|
|
22
22
|
typeof node.style.borderStyle === "string"
|
|
23
|
-
?
|
|
23
|
+
? BOXES[node.style.borderStyle]
|
|
24
24
|
: node.style.borderStyle;
|
|
25
25
|
|
|
26
26
|
const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { widestLine } from "
|
|
2
|
-
import { type DOMElement } from "
|
|
3
|
-
import { getMaxWidth } from "
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { Yoga } from "./yoga/index.ts";
|
|
1
|
+
import { widestLine } from "#/ansi/string-width.ts";
|
|
2
|
+
import { type DOMElement } from "#/dom.ts";
|
|
3
|
+
import { getMaxWidth } from "#/get-max-width.ts";
|
|
4
|
+
import type { Output } from "#/output.ts";
|
|
5
|
+
import { renderBackground } from "#/render-background.ts";
|
|
6
|
+
import { renderBorder } from "#/render-border.ts";
|
|
7
|
+
import { squashTextNodes } from "#/squash-text-nodes.ts";
|
|
8
|
+
import { wrapText } from "#/wrap-text.ts";
|
|
9
|
+
import { Yoga } from "#/yoga/index.ts";
|
|
11
10
|
|
|
12
11
|
// If parent container is `<Box>`, text nodes will be treated as separate nodes in
|
|
13
12
|
// the tree and will have their own coordinates in the layout.
|
|
@@ -21,7 +20,10 @@ const applyPaddingToText = (node: DOMElement, text: string): string => {
|
|
|
21
20
|
if (yogaNode) {
|
|
22
21
|
const offsetX = yogaNode.getComputedLeft();
|
|
23
22
|
const offsetY = yogaNode.getComputedTop();
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
text =
|
|
25
|
+
"\n".repeat(offsetY) +
|
|
26
|
+
(offsetX > 0 ? text.replace(/^(?!\s*$)/gm, " ".repeat(offsetX)) : text);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
return text;
|
package/src/render-to-string.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { LegacyRoot } from "react-reconciler/constants.js";
|
|
3
3
|
|
|
4
|
-
import { createNode, type DOMElement } from "
|
|
5
|
-
import { reconciler } from "
|
|
6
|
-
import { renderer } from "
|
|
7
|
-
import { Yoga } from "
|
|
4
|
+
import { createNode, type DOMElement } from "#/dom.ts";
|
|
5
|
+
import { reconciler } from "#/reconciler.ts";
|
|
6
|
+
import { renderer } from "#/renderer.ts";
|
|
7
|
+
import { Yoga } from "#/yoga/index.ts";
|
|
8
8
|
|
|
9
9
|
export type RenderToStringOptions = {
|
|
10
10
|
/**
|
package/src/render.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { Stream, type Writable } from "node:stream";
|
|
3
2
|
|
|
4
3
|
import type { ReactNode } from "react";
|
|
5
4
|
|
|
6
|
-
import { Ink, type Options as InkOptions, type RenderMetrics } from "
|
|
7
|
-
import { instances } from "
|
|
8
|
-
import { type KittyKeyboardOptions } from "
|
|
5
|
+
import { createInk, type Ink, type Options as InkOptions, type RenderMetrics } from "#/ink.tsx";
|
|
6
|
+
import { instances } from "#/instances.ts";
|
|
7
|
+
import { type KittyKeyboardOptions } from "#/kitty-keyboard.ts";
|
|
9
8
|
|
|
10
9
|
export type RenderOptions = {
|
|
11
10
|
/**
|
|
@@ -122,7 +121,7 @@ export type RenderOptions = {
|
|
|
122
121
|
/**
|
|
123
122
|
Override automatic interactive mode detection.
|
|
124
123
|
|
|
125
|
-
By default, Ink detects whether the environment is interactive based on CI detection (
|
|
124
|
+
By default, Ink detects whether the environment is interactive based on CI detection (the `CI` environment variable) and `stdout.isTTY`. Most users should not need to set this.
|
|
126
125
|
|
|
127
126
|
When non-interactive, Ink disables ANSI erase sequences, cursor manipulation, synchronized output, resize handling, and kitty keyboard auto-detection, writing only the final frame at unmount.
|
|
128
127
|
|
|
@@ -226,20 +225,20 @@ export const render = (node: ReactNode, options?: Writable | RenderOptions): Ins
|
|
|
226
225
|
...getOptions(options),
|
|
227
226
|
};
|
|
228
227
|
|
|
229
|
-
const instance: Ink = getInstance(inkOptions.stdout, () =>
|
|
228
|
+
const instance: Ink = getInstance(inkOptions.stdout, () => createInk(inkOptions));
|
|
230
229
|
instance.render(node);
|
|
231
230
|
|
|
232
231
|
return {
|
|
233
|
-
rerender: instance.render
|
|
232
|
+
rerender: instance.render,
|
|
234
233
|
unmount() {
|
|
235
234
|
instance.unmount();
|
|
236
235
|
},
|
|
237
|
-
waitUntilExit: instance.waitUntilExit
|
|
238
|
-
waitUntilRenderFlush: instance.waitUntilRenderFlush
|
|
236
|
+
waitUntilExit: instance.waitUntilExit,
|
|
237
|
+
waitUntilRenderFlush: instance.waitUntilRenderFlush,
|
|
239
238
|
cleanup() {
|
|
240
239
|
instance.unmount();
|
|
241
240
|
},
|
|
242
|
-
clear: instance.clear
|
|
241
|
+
clear: instance.clear,
|
|
243
242
|
};
|
|
244
243
|
};
|
|
245
244
|
|
package/src/renderer.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type DOMElement } from "
|
|
2
|
-
import { Output } from "
|
|
3
|
-
import { renderNodeToOutput, renderNodeToScreenReaderOutput } from "
|
|
1
|
+
import { type DOMElement } from "#/dom.ts";
|
|
2
|
+
import { Output } from "#/output.ts";
|
|
3
|
+
import { renderNodeToOutput, renderNodeToScreenReaderOutput } from "#/render-node-to-output.ts";
|
|
4
4
|
|
|
5
5
|
type Result = {
|
|
6
6
|
output: string;
|
package/src/sanitize-ansi.ts
CHANGED
package/src/signal-exit.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Handlers receive `null` (not `undefined`) like the original package did —
|
|
5
5
|
// Ink's unmount() relies on that to detect process shutdown.
|
|
6
6
|
/* eslint-disable @typescript-eslint/no-restricted-types */
|
|
7
|
-
import
|
|
7
|
+
import { isWindows } from "#/env.ts";
|
|
8
8
|
|
|
9
9
|
type ExitHandler = (code: number | null, signal: NodeJS.Signals | null) => void;
|
|
10
10
|
|
|
@@ -13,10 +13,9 @@ type Registration = {
|
|
|
13
13
|
alwaysLast: boolean;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
const signals: NodeJS.Signals[] =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: ["SIGHUP", "SIGINT", "SIGTERM", "SIGQUIT", "SIGUSR2"];
|
|
16
|
+
const signals: NodeJS.Signals[] = isWindows
|
|
17
|
+
? ["SIGHUP", "SIGINT", "SIGTERM", "SIGBREAK"]
|
|
18
|
+
: ["SIGHUP", "SIGINT", "SIGTERM", "SIGQUIT", "SIGUSR2"];
|
|
20
19
|
|
|
21
20
|
const registrations = new Set<Registration>();
|
|
22
21
|
const signalListeners = new Map<NodeJS.Signals, () => void>();
|
package/src/squash-text-nodes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type DOMElement } from "
|
|
2
|
-
import { sanitizeAnsi } from "
|
|
1
|
+
import { type DOMElement } from "#/dom.ts";
|
|
2
|
+
import { sanitizeAnsi } from "#/sanitize-ansi.ts";
|
|
3
3
|
|
|
4
4
|
// Squashing text nodes allows to combine multiple text nodes into one and write
|
|
5
5
|
// to `Output` instance only once. For example, <Text>hello{' '}world</Text>
|
package/src/stream.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isTty } from "#/env.ts";
|
|
2
|
+
|
|
1
3
|
export type OutputStream = NodeJS.WritableStream & {
|
|
2
4
|
isTTY?: boolean;
|
|
3
5
|
columns?: number;
|
|
@@ -13,10 +15,6 @@ type RawModeStream = NodeJS.ReadableStream & {
|
|
|
13
15
|
unref?: () => void;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
export const isTty = (stream: NodeJS.ReadableStream): boolean => {
|
|
17
|
-
return "isTTY" in stream && stream.isTTY === true;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
18
|
const isRawModeStream = (stdin: NodeJS.ReadableStream): stdin is RawModeStream => {
|
|
21
19
|
return isTty(stdin) && "setRawMode" in stdin && typeof stdin.setRawMode === "function";
|
|
22
20
|
};
|
package/src/styles.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type ForegroundColorName } from "
|
|
2
|
-
import { type
|
|
3
|
-
import { type LiteralUnion } from "
|
|
4
|
-
import { Yoga, type Node as YogaNode, type PositionType } from "
|
|
1
|
+
import { type ForegroundColorName } from "#/ansi/sgr.ts";
|
|
2
|
+
import { type BoxGlyphs, type BoxStyle } from "#/glyphs.ts";
|
|
3
|
+
import { type LiteralUnion } from "#/types.ts";
|
|
4
|
+
import { Yoga, type Node as YogaNode, type PositionType } from "#/yoga/index.ts";
|
|
5
5
|
|
|
6
6
|
export type Styles = {
|
|
7
7
|
/*
|
|
@@ -134,7 +134,7 @@ export type Styles = {
|
|
|
134
134
|
readonly flexGrow?: number;
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
It specifies the “flex shrink factor”, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when there isn
|
|
137
|
+
It specifies the “flex shrink factor”, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when there isn't enough space on the row.
|
|
138
138
|
See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
|
|
139
139
|
*/
|
|
140
140
|
readonly flexShrink?: number;
|
|
@@ -241,7 +241,7 @@ export type Styles = {
|
|
|
241
241
|
/**
|
|
242
242
|
Add a border with a specified style. If `borderStyle` is `undefined` (the default), no border will be added.
|
|
243
243
|
*/
|
|
244
|
-
readonly borderStyle?:
|
|
244
|
+
readonly borderStyle?: BoxStyle | BoxGlyphs;
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
247
|
Determines whether the top border is visible.
|