@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.2
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 +299 -299
- package/dist/ansi.d.ts +223 -0
- package/dist/ansi.js +2 -0
- package/dist/{devtools-QpCMm9JH.mjs → devtools-BhYGjb7h.js} +1 -1
- package/dist/index-DDVME65c.d.ts +919 -0
- package/dist/index.d.ts +1657 -0
- package/dist/index.js +4643 -0
- package/dist/sgr-CMfEpjSk.d.ts +91 -0
- package/dist/truncate-CBiyyZzw.js +2156 -0
- package/dist/yoga-5jKhYCJC.js +3465 -0
- package/dist/yoga.d.ts +2 -0
- package/dist/yoga.js +2 -0
- package/package.json +37 -17
- package/src/ansi/chalk.ts +179 -0
- package/src/ansi/cursor.ts +48 -0
- package/src/ansi/east-asian-width.ts +215 -0
- package/src/ansi/escapes.ts +128 -0
- package/src/ansi/index.ts +27 -0
- package/src/ansi/sgr.ts +237 -0
- package/src/ansi/slice.ts +43 -0
- package/src/ansi/string-width.ts +236 -0
- package/src/ansi/strip.ts +33 -0
- package/src/ansi/supports-color.ts +213 -0
- package/src/ansi/tokenize.ts +453 -0
- package/src/ansi/truncate.ts +194 -0
- package/src/ansi/widest-line.ts +12 -0
- package/src/ansi/wrap.ts +766 -0
- package/src/ansi-tokenizer.ts +510 -0
- package/src/auto-bind.ts +41 -0
- package/src/boxes.ts +100 -0
- package/src/code-excerpt.ts +39 -0
- package/src/colorize.ts +60 -0
- package/src/components/AccessibilityContext.ts +5 -0
- package/src/components/AnimationContext.ts +24 -0
- package/src/components/App.tsx +781 -0
- package/src/components/AppContext.ts +111 -0
- package/src/components/BackgroundContext.ts +8 -0
- package/src/components/Box.tsx +116 -0
- package/src/components/CursorContext.ts +19 -0
- package/src/components/ErrorBoundary.tsx +38 -0
- package/src/components/ErrorOverview.tsx +133 -0
- package/src/components/FocusContext.ts +30 -0
- package/src/components/Newline.tsx +15 -0
- package/src/components/Spacer.tsx +10 -0
- package/src/components/Static.tsx +59 -0
- package/src/components/StderrContext.ts +26 -0
- package/src/components/StdinContext.ts +49 -0
- package/src/components/StdoutContext.ts +28 -0
- package/src/components/Text.tsx +144 -0
- package/src/components/Transform.tsx +37 -0
- package/src/cursor-position.ts +103 -0
- package/src/devtools-window-polyfill.ts +73 -0
- package/src/devtools.ts +43 -0
- package/src/dom.ts +292 -0
- package/src/get-max-width.ts +11 -0
- package/src/global.d.ts +36 -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-cursor.ts +33 -0
- package/src/hooks/use-focus-manager.ts +62 -0
- package/src/hooks/use-focus.ts +83 -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-window-size.ts +41 -0
- package/src/indent-string.ts +16 -0
- package/src/index.ts +44 -0
- package/src/ink.tsx +1506 -0
- package/src/input-parser.ts +283 -0
- package/src/instances.ts +9 -0
- package/src/is-in-ci.ts +7 -0
- package/src/kitty-keyboard.ts +57 -0
- package/src/log-update.ts +370 -0
- package/src/measure-element.ts +62 -0
- package/src/measure-text.ts +31 -0
- package/src/output.ts +308 -0
- package/src/parse-keypress.ts +516 -0
- package/src/parse-stack-line.ts +139 -0
- package/src/patch-console.ts +62 -0
- package/src/quick-lru.ts +85 -0
- package/src/reconciler.ts +451 -0
- package/src/render-background.ts +38 -0
- package/src/render-border.ts +134 -0
- package/src/render-node-to-output.ts +191 -0
- package/src/render-to-string.ts +131 -0
- package/src/render.ts +276 -0
- package/src/renderer.ts +73 -0
- package/src/sanitize-ansi.ts +33 -0
- package/src/signal-exit.ts +107 -0
- package/src/squash-text-nodes.ts +40 -0
- package/src/stream.ts +30 -0
- package/src/styles.ts +748 -0
- package/src/terminal-size.ts +57 -0
- package/src/throttle.ts +73 -0
- package/src/types.ts +15 -0
- package/src/utils.ts +40 -0
- package/src/wrap-text.ts +50 -0
- package/src/write-synchronized.ts +9 -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 +2920 -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
package/dist/index.js
ADDED
|
@@ -0,0 +1,4643 @@
|
|
|
1
|
+
import { At as esu, Ft as pasteStart, Pt as pasteEnd, b as wrapAnsi, ct as CSI, d as styledCharsFromTokens, f as styledCharsToString, g as chalk, h as widestLine, mt as bsu, n as sliceAnsi, p as tokenize, pt as ansiEscapes, t as cliTruncate, v as cliCursor, x as stringWidth, y as signalExit } from "./truncate-CBiyyZzw.js";
|
|
2
|
+
import { t as Yoga } from "./yoga-5jKhYCJC.js";
|
|
3
|
+
import process, { cwd } from "node:process";
|
|
4
|
+
import { PassThrough, Stream } from "node:stream";
|
|
5
|
+
import { ConcurrentRoot, DefaultEventPriority, LegacyRoot, NoEventPriority } from "react-reconciler/constants.js";
|
|
6
|
+
import { PureComponent, createContext, forwardRef, useCallback, useContext, useEffect, useEffectEvent, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, version } from "react";
|
|
7
|
+
import { EventEmitter } from "node:events";
|
|
8
|
+
import * as fs$1 from "node:fs";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import tty from "node:tty";
|
|
12
|
+
import { Console } from "node:console";
|
|
13
|
+
import createReconciler from "react-reconciler";
|
|
14
|
+
import * as Scheduler from "scheduler";
|
|
15
|
+
//#region src/auto-bind.ts
|
|
16
|
+
const getAllProperties = (object) => {
|
|
17
|
+
const properties = [];
|
|
18
|
+
let current = object;
|
|
19
|
+
do {
|
|
20
|
+
for (const key of Reflect.ownKeys(current)) properties.push([current, key]);
|
|
21
|
+
current = Reflect.getPrototypeOf(current);
|
|
22
|
+
} while (current && current !== Object.prototype);
|
|
23
|
+
return properties;
|
|
24
|
+
};
|
|
25
|
+
const autoBind = (self) => {
|
|
26
|
+
for (const [object, key] of getAllProperties(self.constructor.prototype)) {
|
|
27
|
+
if (key === "constructor") continue;
|
|
28
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(object, key);
|
|
29
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
30
|
+
const value = self[key];
|
|
31
|
+
if (typeof value === "function") self[key] = value.bind(self);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return self;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/components/AccessibilityContext.ts
|
|
38
|
+
const accessibilityContext = createContext({ isScreenReaderEnabled: false });
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/input-parser.ts
|
|
41
|
+
const escape$1 = "\x1B";
|
|
42
|
+
const isCsiParameterByte = (byte) => {
|
|
43
|
+
return byte >= 48 && byte <= 63;
|
|
44
|
+
};
|
|
45
|
+
const isCsiIntermediateByte = (byte) => {
|
|
46
|
+
return byte >= 32 && byte <= 47;
|
|
47
|
+
};
|
|
48
|
+
const isCsiFinalByte = (byte) => {
|
|
49
|
+
return byte >= 64 && byte <= 126;
|
|
50
|
+
};
|
|
51
|
+
const parseCsiSequence = (input, startIndex, prefixLength) => {
|
|
52
|
+
const csiPayloadStart = startIndex + prefixLength + 1;
|
|
53
|
+
let index = csiPayloadStart;
|
|
54
|
+
for (; index < input.length; index++) {
|
|
55
|
+
const byte = input.codePointAt(index);
|
|
56
|
+
if (byte === void 0) return "pending";
|
|
57
|
+
if (isCsiParameterByte(byte) || isCsiIntermediateByte(byte)) continue;
|
|
58
|
+
if (byte === 91 && index === csiPayloadStart) continue;
|
|
59
|
+
if (isCsiFinalByte(byte)) return {
|
|
60
|
+
sequence: input.slice(startIndex, index + 1),
|
|
61
|
+
nextIndex: index + 1
|
|
62
|
+
};
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
return "pending";
|
|
66
|
+
};
|
|
67
|
+
const parseSs3Sequence = (input, startIndex, prefixLength) => {
|
|
68
|
+
const nextIndex = startIndex + prefixLength + 2;
|
|
69
|
+
if (nextIndex > input.length) return "pending";
|
|
70
|
+
const finalByte = input.codePointAt(nextIndex - 1);
|
|
71
|
+
if (finalByte === void 0 || !isCsiFinalByte(finalByte)) return;
|
|
72
|
+
return {
|
|
73
|
+
sequence: input.slice(startIndex, nextIndex),
|
|
74
|
+
nextIndex
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const parseControlSequence = (input, startIndex, prefixLength) => {
|
|
78
|
+
const sequenceType = input[startIndex + prefixLength];
|
|
79
|
+
if (sequenceType === void 0) return "pending";
|
|
80
|
+
if (sequenceType === "[") return parseCsiSequence(input, startIndex, prefixLength);
|
|
81
|
+
if (sequenceType === "O") return parseSs3Sequence(input, startIndex, prefixLength);
|
|
82
|
+
};
|
|
83
|
+
const parseEscapedCodePoint = (input, escapeIndex) => {
|
|
84
|
+
const nextCodePoint = input.codePointAt(escapeIndex + 1);
|
|
85
|
+
const nextCodePointLength = nextCodePoint !== void 0 && nextCodePoint > 65535 ? 2 : 1;
|
|
86
|
+
const nextIndex = escapeIndex + 1 + nextCodePointLength;
|
|
87
|
+
return {
|
|
88
|
+
sequence: input.slice(escapeIndex, nextIndex),
|
|
89
|
+
nextIndex
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const parseEscapeSequence = (input, escapeIndex) => {
|
|
93
|
+
if (escapeIndex === input.length - 1) return "pending";
|
|
94
|
+
if (input[escapeIndex + 1] === escape$1) {
|
|
95
|
+
if (escapeIndex + 2 >= input.length) return "pending";
|
|
96
|
+
const doubleEscapeSequence = parseControlSequence(input, escapeIndex, 2);
|
|
97
|
+
if (doubleEscapeSequence === "pending") return "pending";
|
|
98
|
+
if (doubleEscapeSequence) return doubleEscapeSequence;
|
|
99
|
+
return {
|
|
100
|
+
sequence: input.slice(escapeIndex, escapeIndex + 2),
|
|
101
|
+
nextIndex: escapeIndex + 2
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const controlSequence = parseControlSequence(input, escapeIndex, 1);
|
|
105
|
+
if (controlSequence === "pending") return "pending";
|
|
106
|
+
if (controlSequence) return controlSequence;
|
|
107
|
+
return parseEscapedCodePoint(input, escapeIndex);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
Split a chunk of non-escape text so that backspace bytes (`0x7F` and `0x08`) become individual events. When a user holds the backspace key, the terminal sends repeated bytes in a single stdin chunk. Without splitting, `parseKeypress` receives the multi-byte string and fails to recognize it as a key event, corrupting the input state.
|
|
111
|
+
|
|
112
|
+
Other control characters like `\r` and `\t` are NOT split because they can legitimately appear inside pasted text.
|
|
113
|
+
*/
|
|
114
|
+
const splitBackspaceBytes = (text, events) => {
|
|
115
|
+
let textSegmentStart = 0;
|
|
116
|
+
for (let index = 0; index < text.length; index++) {
|
|
117
|
+
const character = text[index];
|
|
118
|
+
if (character === "" || character === "\b") {
|
|
119
|
+
if (index > textSegmentStart) events.push(text.slice(textSegmentStart, index));
|
|
120
|
+
events.push(character);
|
|
121
|
+
textSegmentStart = index + 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (textSegmentStart < text.length) events.push(text.slice(textSegmentStart));
|
|
125
|
+
};
|
|
126
|
+
const parseKeypresses = (input) => {
|
|
127
|
+
const events = [];
|
|
128
|
+
let index = 0;
|
|
129
|
+
const pendingFrom = (pendingStartIndex) => ({
|
|
130
|
+
events,
|
|
131
|
+
pending: input.slice(pendingStartIndex)
|
|
132
|
+
});
|
|
133
|
+
while (index < input.length) {
|
|
134
|
+
const escapeIndex = input.indexOf(escape$1, index);
|
|
135
|
+
if (escapeIndex === -1) {
|
|
136
|
+
splitBackspaceBytes(input.slice(index), events);
|
|
137
|
+
return {
|
|
138
|
+
events,
|
|
139
|
+
pending: ""
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (escapeIndex > index) splitBackspaceBytes(input.slice(index, escapeIndex), events);
|
|
143
|
+
const parsedEscapeSequence = parseEscapeSequence(input, escapeIndex);
|
|
144
|
+
if (parsedEscapeSequence === "pending") return pendingFrom(escapeIndex);
|
|
145
|
+
if (parsedEscapeSequence.sequence === pasteStart) {
|
|
146
|
+
const afterStart = parsedEscapeSequence.nextIndex;
|
|
147
|
+
const endIndex = input.indexOf(pasteEnd, afterStart);
|
|
148
|
+
if (endIndex === -1) return pendingFrom(escapeIndex);
|
|
149
|
+
events.push({ paste: input.slice(afterStart, endIndex) });
|
|
150
|
+
index = endIndex + pasteEnd.length;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
events.push(parsedEscapeSequence.sequence);
|
|
154
|
+
index = parsedEscapeSequence.nextIndex;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
events,
|
|
158
|
+
pending: ""
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
const createInputParser = () => {
|
|
162
|
+
let pending = "";
|
|
163
|
+
return {
|
|
164
|
+
push(chunk) {
|
|
165
|
+
const parsedInput = parseKeypresses(pending + chunk);
|
|
166
|
+
pending = parsedInput.pending;
|
|
167
|
+
return parsedInput.events;
|
|
168
|
+
},
|
|
169
|
+
hasPendingEscape() {
|
|
170
|
+
return pending.startsWith(escape$1) && !pending.startsWith(pasteStart) && pending !== pasteStart.slice(0, -1);
|
|
171
|
+
},
|
|
172
|
+
flushPendingEscape() {
|
|
173
|
+
if (!pending.startsWith(escape$1)) return;
|
|
174
|
+
const pendingEscape = pending;
|
|
175
|
+
pending = "";
|
|
176
|
+
return pendingEscape;
|
|
177
|
+
},
|
|
178
|
+
reset() {
|
|
179
|
+
pending = "";
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/stream.ts
|
|
185
|
+
const isTty = (stream) => {
|
|
186
|
+
return "isTTY" in stream && stream.isTTY === true;
|
|
187
|
+
};
|
|
188
|
+
const isRawModeStream = (stdin) => {
|
|
189
|
+
return isTty(stdin) && "setRawMode" in stdin && typeof stdin.setRawMode === "function";
|
|
190
|
+
};
|
|
191
|
+
const getRawModeStream = (stdin) => {
|
|
192
|
+
if (!isRawModeStream(stdin)) return;
|
|
193
|
+
return stdin;
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/components/AnimationContext.ts
|
|
197
|
+
const animationContext = createContext({
|
|
198
|
+
renderThrottleMs: 0,
|
|
199
|
+
subscribe() {
|
|
200
|
+
return {
|
|
201
|
+
startTime: 0,
|
|
202
|
+
unsubscribe() {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
animationContext.displayName = "InternalAnimationContext";
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/components/AppContext.ts
|
|
209
|
+
/**
|
|
210
|
+
`AppContext` is a React context that exposes lifecycle methods for the app.
|
|
211
|
+
*/
|
|
212
|
+
const noopSuspension = {
|
|
213
|
+
async resume() {},
|
|
214
|
+
async [Symbol.asyncDispose]() {}
|
|
215
|
+
};
|
|
216
|
+
const AppContext = createContext({
|
|
217
|
+
exit(_errorOrResult) {},
|
|
218
|
+
async waitUntilRenderFlush() {},
|
|
219
|
+
suspendTerminal: (async (callback) => {
|
|
220
|
+
if (callback) {
|
|
221
|
+
await callback();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
return noopSuspension;
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
AppContext.displayName = "InternalAppContext";
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/components/CursorContext.ts
|
|
230
|
+
const CursorContext = createContext({ setCursorPosition() {} });
|
|
231
|
+
CursorContext.displayName = "InternalCursorContext";
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/code-excerpt.ts
|
|
234
|
+
const tabsToSpaces = (input, spaces = 2) => input.replaceAll(/^\t+/gm, (tabs) => " ".repeat(tabs.length * spaces));
|
|
235
|
+
const generateLineNumbers = (line, around) => {
|
|
236
|
+
const lineNumbers = [];
|
|
237
|
+
for (let lineNumber = line - around; lineNumber <= line + around; lineNumber++) lineNumbers.push(lineNumber);
|
|
238
|
+
return lineNumbers;
|
|
239
|
+
};
|
|
240
|
+
const codeExcerpt = (source, line, options = {}) => {
|
|
241
|
+
if (!line || line < 1) throw new TypeError("Line number must start from `1`.");
|
|
242
|
+
const lines = tabsToSpaces(source).split(/\r?\n/);
|
|
243
|
+
if (line > lines.length) return;
|
|
244
|
+
return generateLineNumbers(line, options.around ?? 3).filter((lineNumber) => lines[lineNumber - 1] !== void 0).map((lineNumber) => ({
|
|
245
|
+
line: lineNumber,
|
|
246
|
+
value: lines[lineNumber - 1]
|
|
247
|
+
}));
|
|
248
|
+
};
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/parse-stack-line.ts
|
|
251
|
+
const lineRegExp = new RegExp("^" + String.raw`(?:\s*at )?` + "(?:(new) )?" + String.raw`(?:(.*?) \()?` + String.raw`(?:eval at ([^ ]+) \((.+?):(\d+):(\d+)\), )?` + String.raw`(?:(.+?):(\d+):(\d+)|(native))` + String.raw`(\)?)$`);
|
|
252
|
+
const methodRegExp = /^(.*?) \[as (.*?)]$/;
|
|
253
|
+
const cwd$1 = process.cwd().replaceAll("\\", "/");
|
|
254
|
+
const setFile = (result, filename) => {
|
|
255
|
+
if (filename) {
|
|
256
|
+
filename = filename.replaceAll("\\", "/");
|
|
257
|
+
if (filename.startsWith(`${cwd$1}/`)) filename = filename.slice(cwd$1.length + 1);
|
|
258
|
+
result.file = filename;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const parseStackLine = (line) => {
|
|
262
|
+
const match = lineRegExp.exec(line);
|
|
263
|
+
if (!match) return;
|
|
264
|
+
let functionName = match[2];
|
|
265
|
+
const evalOrigin = match[3];
|
|
266
|
+
const evalFile = match[4];
|
|
267
|
+
const evalLine = Number(match[5]);
|
|
268
|
+
const evalColumn = Number(match[6]);
|
|
269
|
+
let file = match[7];
|
|
270
|
+
const lineNumber = match[8];
|
|
271
|
+
const columnNumber = match[9];
|
|
272
|
+
const native = match[10] === "native";
|
|
273
|
+
const closeParen = match[11] === ")";
|
|
274
|
+
let method;
|
|
275
|
+
const result = {};
|
|
276
|
+
if (lineNumber) result.line = Number(lineNumber);
|
|
277
|
+
if (columnNumber) result.column = Number(columnNumber);
|
|
278
|
+
if (closeParen && file) {
|
|
279
|
+
let closes = 0;
|
|
280
|
+
for (let index = file.length - 1; index > 0; index--) if (file.charAt(index) === ")") closes++;
|
|
281
|
+
else if (file.charAt(index) === "(" && file.charAt(index - 1) === " ") {
|
|
282
|
+
closes--;
|
|
283
|
+
if (closes === -1 && file.charAt(index - 1) === " ") {
|
|
284
|
+
const before = file.slice(0, index - 1);
|
|
285
|
+
file = file.slice(index + 1);
|
|
286
|
+
functionName += ` (${before}`;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (functionName) {
|
|
292
|
+
const methodMatch = methodRegExp.exec(functionName);
|
|
293
|
+
if (methodMatch) {
|
|
294
|
+
functionName = methodMatch[1];
|
|
295
|
+
method = methodMatch[2];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (file) setFile(result, file);
|
|
299
|
+
if (evalOrigin) {
|
|
300
|
+
result.evalOrigin = evalOrigin;
|
|
301
|
+
result.evalLine = evalLine;
|
|
302
|
+
result.evalColumn = evalColumn;
|
|
303
|
+
result.evalFile = evalFile?.replaceAll("\\", "/");
|
|
304
|
+
}
|
|
305
|
+
if (native) result.native = true;
|
|
306
|
+
if (functionName) result.function = functionName;
|
|
307
|
+
if (method && functionName !== method) result.method = method;
|
|
308
|
+
return result;
|
|
309
|
+
};
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/components/BackgroundContext.ts
|
|
312
|
+
const backgroundContext = createContext(void 0);
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/components/Box.tsx
|
|
315
|
+
/**
|
|
316
|
+
`<Box>` is an essential Ink component to build your layout. It's like `<div style="display: flex">` in the browser.
|
|
317
|
+
*/
|
|
318
|
+
const Box = forwardRef(({ children, backgroundColor, "aria-label": ariaLabel, "aria-hidden": ariaHidden, "aria-role": role, "aria-state": ariaState, ...style }, ref) => {
|
|
319
|
+
const { isScreenReaderEnabled } = useContext(accessibilityContext);
|
|
320
|
+
const label = ariaLabel ? /* @__PURE__ */ jsx("ink-text", { children: ariaLabel }) : void 0;
|
|
321
|
+
if (isScreenReaderEnabled && ariaHidden) return null;
|
|
322
|
+
const boxElement = /* @__PURE__ */ jsx("ink-box", {
|
|
323
|
+
ref,
|
|
324
|
+
style: {
|
|
325
|
+
flexWrap: "nowrap",
|
|
326
|
+
flexDirection: "row",
|
|
327
|
+
flexGrow: 0,
|
|
328
|
+
flexShrink: 1,
|
|
329
|
+
...style,
|
|
330
|
+
backgroundColor,
|
|
331
|
+
overflowX: style.overflowX ?? style.overflow ?? "visible",
|
|
332
|
+
overflowY: style.overflowY ?? style.overflow ?? "visible"
|
|
333
|
+
},
|
|
334
|
+
internal_accessibility: {
|
|
335
|
+
role,
|
|
336
|
+
state: ariaState
|
|
337
|
+
},
|
|
338
|
+
children: isScreenReaderEnabled && label ? label : children
|
|
339
|
+
});
|
|
340
|
+
if (backgroundColor) return /* @__PURE__ */ jsx(backgroundContext.Provider, {
|
|
341
|
+
value: backgroundColor,
|
|
342
|
+
children: boxElement
|
|
343
|
+
});
|
|
344
|
+
return boxElement;
|
|
345
|
+
});
|
|
346
|
+
Box.displayName = "Box";
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/colorize.ts
|
|
349
|
+
const rgbRegex = /^rgb\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/;
|
|
350
|
+
const ansiRegex = /^ansi256\(\s?(\d+)\s?\)$/;
|
|
351
|
+
const isNamedColor = (color) => {
|
|
352
|
+
return color in chalk;
|
|
353
|
+
};
|
|
354
|
+
const colorize = (str, color, type) => {
|
|
355
|
+
if (!color) return str;
|
|
356
|
+
if (isNamedColor(color)) {
|
|
357
|
+
if (type === "foreground") return chalk[color](str);
|
|
358
|
+
const methodName = `bg${color[0].toUpperCase() + color.slice(1)}`;
|
|
359
|
+
return chalk[methodName](str);
|
|
360
|
+
}
|
|
361
|
+
if (color.startsWith("#")) return type === "foreground" ? chalk.hex(color)(str) : chalk.bgHex(color)(str);
|
|
362
|
+
if (color.startsWith("ansi256")) {
|
|
363
|
+
const matches = ansiRegex.exec(color);
|
|
364
|
+
if (!matches) return str;
|
|
365
|
+
const value = Number(matches[1]);
|
|
366
|
+
return type === "foreground" ? chalk.ansi256(value)(str) : chalk.bgAnsi256(value)(str);
|
|
367
|
+
}
|
|
368
|
+
if (color.startsWith("rgb")) {
|
|
369
|
+
const matches = rgbRegex.exec(color);
|
|
370
|
+
if (!matches) return str;
|
|
371
|
+
const firstValue = Number(matches[1]);
|
|
372
|
+
const secondValue = Number(matches[2]);
|
|
373
|
+
const thirdValue = Number(matches[3]);
|
|
374
|
+
return type === "foreground" ? chalk.rgb(firstValue, secondValue, thirdValue)(str) : chalk.bgRgb(firstValue, secondValue, thirdValue)(str);
|
|
375
|
+
}
|
|
376
|
+
return str;
|
|
377
|
+
};
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/components/Text.tsx
|
|
380
|
+
/**
|
|
381
|
+
This component can display text and change its style to make it bold, underlined, italic, or strikethrough.
|
|
382
|
+
*/
|
|
383
|
+
function Text({ color, backgroundColor, dimColor = false, bold = false, italic = false, underline = false, strikethrough = false, inverse = false, wrap = "wrap", children, "aria-label": ariaLabel, "aria-hidden": ariaHidden = false }) {
|
|
384
|
+
const { isScreenReaderEnabled } = useContext(accessibilityContext);
|
|
385
|
+
const inheritedBackgroundColor = useContext(backgroundContext);
|
|
386
|
+
const childrenOrAriaLabel = isScreenReaderEnabled && ariaLabel ? ariaLabel : children;
|
|
387
|
+
if (childrenOrAriaLabel === void 0 || childrenOrAriaLabel === null) return null;
|
|
388
|
+
const transform = (text) => {
|
|
389
|
+
if (dimColor) text = chalk.dim(text);
|
|
390
|
+
if (color) text = colorize(text, color, "foreground");
|
|
391
|
+
const effectiveBackgroundColor = backgroundColor ?? inheritedBackgroundColor;
|
|
392
|
+
if (effectiveBackgroundColor) text = colorize(text, effectiveBackgroundColor, "background");
|
|
393
|
+
if (bold) text = chalk.bold(text);
|
|
394
|
+
if (italic) text = chalk.italic(text);
|
|
395
|
+
if (underline) text = chalk.underline(text);
|
|
396
|
+
if (strikethrough) text = chalk.strikethrough(text);
|
|
397
|
+
if (inverse) text = chalk.inverse(text);
|
|
398
|
+
return text;
|
|
399
|
+
};
|
|
400
|
+
if (isScreenReaderEnabled && ariaHidden) return null;
|
|
401
|
+
return /* @__PURE__ */ jsx("ink-text", {
|
|
402
|
+
style: {
|
|
403
|
+
flexGrow: 0,
|
|
404
|
+
flexShrink: 1,
|
|
405
|
+
flexDirection: "row",
|
|
406
|
+
textWrap: wrap
|
|
407
|
+
},
|
|
408
|
+
internal_transform: transform,
|
|
409
|
+
children: childrenOrAriaLabel
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/components/ErrorOverview.tsx
|
|
414
|
+
const cleanupPath = (path) => {
|
|
415
|
+
return path?.replace(`file://${cwd()}/`, "");
|
|
416
|
+
};
|
|
417
|
+
function ErrorOverview({ error }) {
|
|
418
|
+
const stack = error.stack ? error.stack.split("\n").slice(1) : void 0;
|
|
419
|
+
const origin = stack ? parseStackLine(stack[0]) : void 0;
|
|
420
|
+
const filePath = cleanupPath(origin?.file);
|
|
421
|
+
let excerpt;
|
|
422
|
+
let lineWidth = 0;
|
|
423
|
+
const stackLineCounts = /* @__PURE__ */ new Map();
|
|
424
|
+
if (filePath && origin?.line && fs$1.existsSync(filePath)) {
|
|
425
|
+
const sourceCode = fs$1.readFileSync(filePath, "utf8");
|
|
426
|
+
excerpt = codeExcerpt(sourceCode, origin.line);
|
|
427
|
+
if (excerpt) for (const { line } of excerpt) lineWidth = Math.max(lineWidth, String(line).length);
|
|
428
|
+
}
|
|
429
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
430
|
+
flexDirection: "column",
|
|
431
|
+
padding: 1,
|
|
432
|
+
children: [
|
|
433
|
+
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
434
|
+
backgroundColor: "red",
|
|
435
|
+
color: "white",
|
|
436
|
+
children: [
|
|
437
|
+
" ",
|
|
438
|
+
"ERROR",
|
|
439
|
+
" "
|
|
440
|
+
]
|
|
441
|
+
}), /* @__PURE__ */ jsxs(Text, { children: [" ", error.message] })] }),
|
|
442
|
+
origin && filePath ? /* @__PURE__ */ jsx(Box, {
|
|
443
|
+
marginTop: 1,
|
|
444
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
445
|
+
dimColor: true,
|
|
446
|
+
children: [
|
|
447
|
+
filePath,
|
|
448
|
+
":",
|
|
449
|
+
origin.line,
|
|
450
|
+
":",
|
|
451
|
+
origin.column
|
|
452
|
+
]
|
|
453
|
+
})
|
|
454
|
+
}) : null,
|
|
455
|
+
origin && excerpt ? /* @__PURE__ */ jsx(Box, {
|
|
456
|
+
marginTop: 1,
|
|
457
|
+
flexDirection: "column",
|
|
458
|
+
children: excerpt.map(({ line, value }) => /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Box, {
|
|
459
|
+
width: lineWidth + 1,
|
|
460
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
461
|
+
dimColor: line !== origin.line,
|
|
462
|
+
backgroundColor: line === origin.line ? "red" : void 0,
|
|
463
|
+
color: line === origin.line ? "white" : void 0,
|
|
464
|
+
"aria-label": line === origin.line ? `Line ${line}, error` : `Line ${line}`,
|
|
465
|
+
children: [String(line).padStart(lineWidth, " "), ":"]
|
|
466
|
+
})
|
|
467
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
468
|
+
backgroundColor: line === origin.line ? "red" : void 0,
|
|
469
|
+
color: line === origin.line ? "white" : void 0,
|
|
470
|
+
children: " " + value
|
|
471
|
+
}, line)] }, line))
|
|
472
|
+
}) : null,
|
|
473
|
+
error.stack ? /* @__PURE__ */ jsx(Box, {
|
|
474
|
+
marginTop: 1,
|
|
475
|
+
flexDirection: "column",
|
|
476
|
+
children: error.stack.split("\n").slice(1).map((line) => {
|
|
477
|
+
const parsedLine = parseStackLine(line);
|
|
478
|
+
const lineCount = stackLineCounts.get(line) ?? 0;
|
|
479
|
+
stackLineCounts.set(line, lineCount + 1);
|
|
480
|
+
const key = `${line}-${lineCount}`;
|
|
481
|
+
if (!parsedLine?.file || !parsedLine.line || !parsedLine.column) return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
482
|
+
dimColor: true,
|
|
483
|
+
children: "- "
|
|
484
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
485
|
+
dimColor: true,
|
|
486
|
+
bold: true,
|
|
487
|
+
children: [
|
|
488
|
+
line,
|
|
489
|
+
"\\t",
|
|
490
|
+
" "
|
|
491
|
+
]
|
|
492
|
+
})] }, key);
|
|
493
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
494
|
+
/* @__PURE__ */ jsx(Text, {
|
|
495
|
+
dimColor: true,
|
|
496
|
+
children: "- "
|
|
497
|
+
}),
|
|
498
|
+
/* @__PURE__ */ jsx(Text, {
|
|
499
|
+
dimColor: true,
|
|
500
|
+
bold: true,
|
|
501
|
+
children: parsedLine.function
|
|
502
|
+
}),
|
|
503
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
504
|
+
dimColor: true,
|
|
505
|
+
color: "gray",
|
|
506
|
+
"aria-label": `at ${cleanupPath(parsedLine.file) ?? ""} line ${parsedLine.line} column ${parsedLine.column}`,
|
|
507
|
+
children: [
|
|
508
|
+
" ",
|
|
509
|
+
"(",
|
|
510
|
+
cleanupPath(parsedLine.file) ?? "",
|
|
511
|
+
":",
|
|
512
|
+
parsedLine.line,
|
|
513
|
+
":",
|
|
514
|
+
parsedLine.column,
|
|
515
|
+
")"
|
|
516
|
+
]
|
|
517
|
+
})
|
|
518
|
+
] }, key);
|
|
519
|
+
})
|
|
520
|
+
}) : null
|
|
521
|
+
]
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/components/ErrorBoundary.tsx
|
|
526
|
+
var ErrorBoundary = class extends PureComponent {
|
|
527
|
+
static displayName = "InternalErrorBoundary";
|
|
528
|
+
static getDerivedStateFromError(error) {
|
|
529
|
+
return { error };
|
|
530
|
+
}
|
|
531
|
+
state = { error: void 0 };
|
|
532
|
+
componentDidCatch(error) {
|
|
533
|
+
this.props.onError(error);
|
|
534
|
+
}
|
|
535
|
+
render() {
|
|
536
|
+
if (this.state.error) return /* @__PURE__ */ jsx(ErrorOverview, { error: this.state.error });
|
|
537
|
+
return this.props.children;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/components/FocusContext.ts
|
|
542
|
+
const FocusContext = createContext({
|
|
543
|
+
activeId: void 0,
|
|
544
|
+
add() {},
|
|
545
|
+
remove() {},
|
|
546
|
+
activate() {},
|
|
547
|
+
deactivate() {},
|
|
548
|
+
enableFocus() {},
|
|
549
|
+
disableFocus() {},
|
|
550
|
+
focusNext() {},
|
|
551
|
+
focusPrevious() {},
|
|
552
|
+
focus() {}
|
|
553
|
+
});
|
|
554
|
+
FocusContext.displayName = "InternalFocusContext";
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/components/StderrContext.ts
|
|
557
|
+
/**
|
|
558
|
+
`StderrContext` is a React context that exposes the stderr stream.
|
|
559
|
+
*/
|
|
560
|
+
const StderrContext = createContext({
|
|
561
|
+
stderr: process.stderr,
|
|
562
|
+
write() {}
|
|
563
|
+
});
|
|
564
|
+
StderrContext.displayName = "InternalStderrContext";
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/components/StdinContext.ts
|
|
567
|
+
/**
|
|
568
|
+
`StdinContext` is a React context that exposes the input stream.
|
|
569
|
+
*/
|
|
570
|
+
const StdinContext = createContext({
|
|
571
|
+
stdin: process.stdin,
|
|
572
|
+
internal_eventEmitter: new EventEmitter(),
|
|
573
|
+
setRawMode() {},
|
|
574
|
+
setBracketedPasteMode() {},
|
|
575
|
+
isRawModeSupported: false,
|
|
576
|
+
internal_exitOnCtrlC: true
|
|
577
|
+
});
|
|
578
|
+
StdinContext.displayName = "InternalStdinContext";
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/components/StdoutContext.ts
|
|
581
|
+
/**
|
|
582
|
+
`StdoutContext` is a React context that exposes the stdout stream where Ink renders your app.
|
|
583
|
+
*/
|
|
584
|
+
const StdoutContext = createContext({
|
|
585
|
+
stdout: process.stdout,
|
|
586
|
+
write() {}
|
|
587
|
+
});
|
|
588
|
+
StdoutContext.displayName = "InternalStdoutContext";
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/components/App.tsx
|
|
591
|
+
const tab = " ";
|
|
592
|
+
const shiftTab = `${CSI}Z`;
|
|
593
|
+
const escape = "\x1B";
|
|
594
|
+
function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, exitOnCtrlC, onExit, onWaitUntilRenderFlush, onSuspendTerminal, onRegisterInputControl, setCursorPosition, interactive, renderThrottleMs }) {
|
|
595
|
+
const [isFocusEnabled, setIsFocusEnabled] = useState(true);
|
|
596
|
+
const [activeFocusId, setActiveFocusId] = useState(void 0);
|
|
597
|
+
const [, setFocusables] = useState([]);
|
|
598
|
+
const focusablesCountRef = useRef(0);
|
|
599
|
+
const animationSubscribersRef = useRef(/* @__PURE__ */ new Map());
|
|
600
|
+
const animationTimerRef = useRef(void 0);
|
|
601
|
+
const rawModeEnabledCount = useRef(0);
|
|
602
|
+
const pendingDisableRawModeRef = useRef(false);
|
|
603
|
+
const bracketedPasteModeEnabledCount = useRef(0);
|
|
604
|
+
const internal_eventEmitter = useRef(new EventEmitter());
|
|
605
|
+
internal_eventEmitter.current.setMaxListeners(Infinity);
|
|
606
|
+
const readableListenerRef = useRef(void 0);
|
|
607
|
+
const inputParserRef = useRef(createInputParser());
|
|
608
|
+
const pendingInputFlushRef = useRef(void 0);
|
|
609
|
+
const pendingInputFlushDelayMilliseconds = 20;
|
|
610
|
+
const clearPendingInputFlush = useCallback(() => {
|
|
611
|
+
if (!pendingInputFlushRef.current) return;
|
|
612
|
+
clearTimeout(pendingInputFlushRef.current);
|
|
613
|
+
pendingInputFlushRef.current = void 0;
|
|
614
|
+
}, []);
|
|
615
|
+
const clearAnimationTimer = useCallback(() => {
|
|
616
|
+
if (!animationTimerRef.current) return;
|
|
617
|
+
clearTimeout(animationTimerRef.current);
|
|
618
|
+
animationTimerRef.current = void 0;
|
|
619
|
+
}, []);
|
|
620
|
+
const scheduleAnimationTick = useCallback(() => {
|
|
621
|
+
clearAnimationTimer();
|
|
622
|
+
if (animationSubscribersRef.current.size === 0) return;
|
|
623
|
+
let nextDueTime = Number.POSITIVE_INFINITY;
|
|
624
|
+
for (const subscriber of animationSubscribersRef.current.values()) nextDueTime = Math.min(nextDueTime, subscriber.nextDueTime);
|
|
625
|
+
const delay = Math.max(0, nextDueTime - performance.now());
|
|
626
|
+
animationTimerRef.current = setTimeout(() => {
|
|
627
|
+
animationTimerRef.current = void 0;
|
|
628
|
+
const currentTime = performance.now();
|
|
629
|
+
for (const subscriber of animationSubscribersRef.current.values()) {
|
|
630
|
+
if (currentTime < subscriber.nextDueTime) continue;
|
|
631
|
+
subscriber.callback(currentTime);
|
|
632
|
+
const elapsedTime = currentTime - subscriber.startTime;
|
|
633
|
+
const elapsedFrames = Math.floor(elapsedTime / subscriber.interval) + 1;
|
|
634
|
+
subscriber.nextDueTime = subscriber.startTime + elapsedFrames * subscriber.interval;
|
|
635
|
+
}
|
|
636
|
+
scheduleAnimationTick();
|
|
637
|
+
}, delay);
|
|
638
|
+
}, [clearAnimationTimer]);
|
|
639
|
+
const animationSubscribe = useCallback((callback, interval) => {
|
|
640
|
+
const startTime = performance.now();
|
|
641
|
+
animationSubscribersRef.current.set(callback, {
|
|
642
|
+
callback,
|
|
643
|
+
interval,
|
|
644
|
+
startTime,
|
|
645
|
+
nextDueTime: startTime + interval
|
|
646
|
+
});
|
|
647
|
+
scheduleAnimationTick();
|
|
648
|
+
return {
|
|
649
|
+
startTime,
|
|
650
|
+
unsubscribe() {
|
|
651
|
+
animationSubscribersRef.current.delete(callback);
|
|
652
|
+
if (animationSubscribersRef.current.size === 0) {
|
|
653
|
+
clearAnimationTimer();
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
scheduleAnimationTick();
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}, [clearAnimationTimer, scheduleAnimationTick]);
|
|
660
|
+
useEffect(() => {
|
|
661
|
+
return () => {
|
|
662
|
+
clearAnimationTimer();
|
|
663
|
+
};
|
|
664
|
+
}, [clearAnimationTimer]);
|
|
665
|
+
const rawModeStdin = getRawModeStream(stdin);
|
|
666
|
+
const isRawModeSupported = rawModeStdin !== void 0;
|
|
667
|
+
const detachReadableListener = useCallback(() => {
|
|
668
|
+
if (!readableListenerRef.current) return;
|
|
669
|
+
stdin.removeListener("readable", readableListenerRef.current);
|
|
670
|
+
readableListenerRef.current = void 0;
|
|
671
|
+
}, [stdin]);
|
|
672
|
+
const clearInputState = useCallback(() => {
|
|
673
|
+
inputParserRef.current.reset();
|
|
674
|
+
clearPendingInputFlush();
|
|
675
|
+
detachReadableListener();
|
|
676
|
+
}, [clearPendingInputFlush, detachReadableListener]);
|
|
677
|
+
const disableRawMode = useCallback(() => {
|
|
678
|
+
if (!rawModeStdin) return;
|
|
679
|
+
pendingDisableRawModeRef.current = false;
|
|
680
|
+
rawModeStdin.setRawMode(false);
|
|
681
|
+
rawModeStdin.unref?.();
|
|
682
|
+
rawModeEnabledCount.current = 0;
|
|
683
|
+
clearInputState();
|
|
684
|
+
}, [rawModeStdin, clearInputState]);
|
|
685
|
+
const handleExit = useCallback((errorOrResult) => {
|
|
686
|
+
if (isRawModeSupported && (rawModeEnabledCount.current > 0 || pendingDisableRawModeRef.current)) disableRawMode();
|
|
687
|
+
onExit(errorOrResult);
|
|
688
|
+
}, [
|
|
689
|
+
isRawModeSupported,
|
|
690
|
+
disableRawMode,
|
|
691
|
+
onExit
|
|
692
|
+
]);
|
|
693
|
+
const handleInput = useCallback((input) => {
|
|
694
|
+
if (input === "" && exitOnCtrlC) {
|
|
695
|
+
handleExit();
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (input === escape && isFocusEnabled) setActiveFocusId(void 0);
|
|
699
|
+
}, [
|
|
700
|
+
exitOnCtrlC,
|
|
701
|
+
handleExit,
|
|
702
|
+
isFocusEnabled
|
|
703
|
+
]);
|
|
704
|
+
const emitInput = useCallback((input) => {
|
|
705
|
+
handleInput(input);
|
|
706
|
+
internal_eventEmitter.current.emit("input", input);
|
|
707
|
+
}, [handleInput]);
|
|
708
|
+
const schedulePendingInputFlush = useCallback(() => {
|
|
709
|
+
clearPendingInputFlush();
|
|
710
|
+
pendingInputFlushRef.current = setTimeout(() => {
|
|
711
|
+
pendingInputFlushRef.current = void 0;
|
|
712
|
+
const pendingEscape = inputParserRef.current.flushPendingEscape();
|
|
713
|
+
if (!pendingEscape) return;
|
|
714
|
+
emitInput(pendingEscape);
|
|
715
|
+
}, pendingInputFlushDelayMilliseconds);
|
|
716
|
+
}, [clearPendingInputFlush, emitInput]);
|
|
717
|
+
const handleReadable = useCallback(() => {
|
|
718
|
+
clearPendingInputFlush();
|
|
719
|
+
let chunk;
|
|
720
|
+
while ((chunk = stdin.read()) !== null) {
|
|
721
|
+
const inputEvents = inputParserRef.current.push(chunk);
|
|
722
|
+
for (const event of inputEvents) if (typeof event === "string") emitInput(event);
|
|
723
|
+
else {
|
|
724
|
+
if (internal_eventEmitter.current.listenerCount("paste") === 0) {
|
|
725
|
+
emitInput(event.paste);
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
internal_eventEmitter.current.emit("paste", event.paste);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (inputParserRef.current.hasPendingEscape()) schedulePendingInputFlush();
|
|
732
|
+
}, [
|
|
733
|
+
stdin,
|
|
734
|
+
emitInput,
|
|
735
|
+
clearPendingInputFlush,
|
|
736
|
+
schedulePendingInputFlush
|
|
737
|
+
]);
|
|
738
|
+
const attachReadableListener = useCallback(() => {
|
|
739
|
+
if (readableListenerRef.current) return;
|
|
740
|
+
readableListenerRef.current = handleReadable;
|
|
741
|
+
stdin.addListener("readable", handleReadable);
|
|
742
|
+
}, [stdin, handleReadable]);
|
|
743
|
+
const handleSetRawMode = useCallback((isEnabled) => {
|
|
744
|
+
if (!rawModeStdin) {
|
|
745
|
+
if (stdin === process.stdin) throw new Error("Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported");
|
|
746
|
+
else throw new Error("Raw mode is not supported on the stdin provided to Ink.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported");
|
|
747
|
+
}
|
|
748
|
+
rawModeStdin.setEncoding("utf8");
|
|
749
|
+
if (isEnabled) {
|
|
750
|
+
if (rawModeEnabledCount.current === 0) {
|
|
751
|
+
const isRawModeAlreadyEnabled = pendingDisableRawModeRef.current;
|
|
752
|
+
pendingDisableRawModeRef.current = false;
|
|
753
|
+
if (!isRawModeAlreadyEnabled) {
|
|
754
|
+
rawModeStdin.ref?.();
|
|
755
|
+
rawModeStdin.setRawMode(true);
|
|
756
|
+
}
|
|
757
|
+
attachReadableListener();
|
|
758
|
+
}
|
|
759
|
+
rawModeEnabledCount.current++;
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (rawModeEnabledCount.current === 0) return;
|
|
763
|
+
if (--rawModeEnabledCount.current === 0) {
|
|
764
|
+
clearInputState();
|
|
765
|
+
pendingDisableRawModeRef.current = true;
|
|
766
|
+
queueMicrotask(() => {
|
|
767
|
+
if (!pendingDisableRawModeRef.current) return;
|
|
768
|
+
disableRawMode();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}, [
|
|
772
|
+
rawModeStdin,
|
|
773
|
+
stdin,
|
|
774
|
+
attachReadableListener,
|
|
775
|
+
clearInputState,
|
|
776
|
+
disableRawMode
|
|
777
|
+
]);
|
|
778
|
+
const handleSetBracketedPasteMode = useCallback((isEnabled) => {
|
|
779
|
+
if (!stdout.isTTY) return;
|
|
780
|
+
if (isEnabled) {
|
|
781
|
+
if (bracketedPasteModeEnabledCount.current === 0) stdout.write(ansiEscapes.enableBracketedPaste);
|
|
782
|
+
bracketedPasteModeEnabledCount.current++;
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (bracketedPasteModeEnabledCount.current === 0) return;
|
|
786
|
+
if (--bracketedPasteModeEnabledCount.current === 0) stdout.write(ansiEscapes.disableBracketedPaste);
|
|
787
|
+
}, [stdout]);
|
|
788
|
+
const suspendedInputStateRef = useRef({
|
|
789
|
+
rawMode: false,
|
|
790
|
+
bracketedPaste: false
|
|
791
|
+
});
|
|
792
|
+
const pauseInput = useCallback(() => {
|
|
793
|
+
const wasRawMode = isRawModeSupported && rawModeEnabledCount.current > 0;
|
|
794
|
+
const wasBracketedPaste = bracketedPasteModeEnabledCount.current > 0;
|
|
795
|
+
suspendedInputStateRef.current = {
|
|
796
|
+
rawMode: wasRawMode,
|
|
797
|
+
bracketedPaste: wasBracketedPaste
|
|
798
|
+
};
|
|
799
|
+
if (wasBracketedPaste && stdout.isTTY) try {
|
|
800
|
+
stdout.write(ansiEscapes.disableBracketedPaste);
|
|
801
|
+
} catch {}
|
|
802
|
+
if (wasRawMode) {
|
|
803
|
+
rawModeStdin?.setRawMode(false);
|
|
804
|
+
rawModeStdin?.unref?.();
|
|
805
|
+
clearInputState();
|
|
806
|
+
}
|
|
807
|
+
}, [
|
|
808
|
+
isRawModeSupported,
|
|
809
|
+
rawModeStdin,
|
|
810
|
+
stdout,
|
|
811
|
+
clearInputState
|
|
812
|
+
]);
|
|
813
|
+
const resumeInput = useCallback(() => {
|
|
814
|
+
const { rawMode, bracketedPaste } = suspendedInputStateRef.current;
|
|
815
|
+
if (rawMode) {
|
|
816
|
+
rawModeStdin?.setEncoding("utf8");
|
|
817
|
+
rawModeStdin?.ref?.();
|
|
818
|
+
rawModeStdin?.setRawMode(true);
|
|
819
|
+
attachReadableListener();
|
|
820
|
+
}
|
|
821
|
+
if (bracketedPaste && stdout.isTTY) try {
|
|
822
|
+
stdout.write(ansiEscapes.enableBracketedPaste);
|
|
823
|
+
} catch {}
|
|
824
|
+
}, [
|
|
825
|
+
rawModeStdin,
|
|
826
|
+
stdout,
|
|
827
|
+
attachReadableListener
|
|
828
|
+
]);
|
|
829
|
+
useInsertionEffect(() => {
|
|
830
|
+
onRegisterInputControl(pauseInput, resumeInput);
|
|
831
|
+
}, [
|
|
832
|
+
onRegisterInputControl,
|
|
833
|
+
pauseInput,
|
|
834
|
+
resumeInput
|
|
835
|
+
]);
|
|
836
|
+
const findNextFocusable = useCallback((currentFocusables, currentActiveFocusId) => {
|
|
837
|
+
const activeIndex = currentFocusables.findIndex((focusable) => {
|
|
838
|
+
return focusable.id === currentActiveFocusId;
|
|
839
|
+
});
|
|
840
|
+
for (let index = activeIndex + 1; index < currentFocusables.length; index++) {
|
|
841
|
+
const focusable = currentFocusables[index];
|
|
842
|
+
if (focusable?.isActive) return focusable.id;
|
|
843
|
+
}
|
|
844
|
+
}, []);
|
|
845
|
+
const findPreviousFocusable = useCallback((currentFocusables, currentActiveFocusId) => {
|
|
846
|
+
const activeIndex = currentFocusables.findIndex((focusable) => {
|
|
847
|
+
return focusable.id === currentActiveFocusId;
|
|
848
|
+
});
|
|
849
|
+
for (let index = activeIndex - 1; index >= 0; index--) {
|
|
850
|
+
const focusable = currentFocusables[index];
|
|
851
|
+
if (focusable?.isActive) return focusable.id;
|
|
852
|
+
}
|
|
853
|
+
}, []);
|
|
854
|
+
const focusNext = useCallback(() => {
|
|
855
|
+
setFocusables((currentFocusables) => {
|
|
856
|
+
setActiveFocusId((currentActiveFocusId) => {
|
|
857
|
+
const firstFocusableId = currentFocusables.find((focusable) => focusable.isActive)?.id;
|
|
858
|
+
return findNextFocusable(currentFocusables, currentActiveFocusId) ?? firstFocusableId;
|
|
859
|
+
});
|
|
860
|
+
return currentFocusables;
|
|
861
|
+
});
|
|
862
|
+
}, [findNextFocusable]);
|
|
863
|
+
const focusPrevious = useCallback(() => {
|
|
864
|
+
setFocusables((currentFocusables) => {
|
|
865
|
+
setActiveFocusId((currentActiveFocusId) => {
|
|
866
|
+
const lastFocusableId = currentFocusables.findLast((focusable) => focusable.isActive)?.id;
|
|
867
|
+
return findPreviousFocusable(currentFocusables, currentActiveFocusId) ?? lastFocusableId;
|
|
868
|
+
});
|
|
869
|
+
return currentFocusables;
|
|
870
|
+
});
|
|
871
|
+
}, [findPreviousFocusable]);
|
|
872
|
+
useEffect(() => {
|
|
873
|
+
const handleTabNavigation = (input) => {
|
|
874
|
+
if (!isFocusEnabled || focusablesCountRef.current === 0) return;
|
|
875
|
+
if (input === tab) focusNext();
|
|
876
|
+
if (input === shiftTab) focusPrevious();
|
|
877
|
+
};
|
|
878
|
+
internal_eventEmitter.current.on("input", handleTabNavigation);
|
|
879
|
+
const emitter = internal_eventEmitter.current;
|
|
880
|
+
return () => {
|
|
881
|
+
emitter.off("input", handleTabNavigation);
|
|
882
|
+
};
|
|
883
|
+
}, [
|
|
884
|
+
isFocusEnabled,
|
|
885
|
+
focusNext,
|
|
886
|
+
focusPrevious
|
|
887
|
+
]);
|
|
888
|
+
const enableFocus = useCallback(() => {
|
|
889
|
+
setIsFocusEnabled(true);
|
|
890
|
+
}, []);
|
|
891
|
+
const disableFocus = useCallback(() => {
|
|
892
|
+
setIsFocusEnabled(false);
|
|
893
|
+
}, []);
|
|
894
|
+
const focus = useCallback((id) => {
|
|
895
|
+
setFocusables((currentFocusables) => {
|
|
896
|
+
if (currentFocusables.some((focusable) => focusable?.id === id)) setActiveFocusId(id);
|
|
897
|
+
return currentFocusables;
|
|
898
|
+
});
|
|
899
|
+
}, []);
|
|
900
|
+
const addFocusable = useCallback((id, { autoFocus }) => {
|
|
901
|
+
setFocusables((currentFocusables) => {
|
|
902
|
+
focusablesCountRef.current = currentFocusables.length + 1;
|
|
903
|
+
return [...currentFocusables, {
|
|
904
|
+
id,
|
|
905
|
+
isActive: true
|
|
906
|
+
}];
|
|
907
|
+
});
|
|
908
|
+
if (autoFocus) setActiveFocusId((currentActiveFocusId) => {
|
|
909
|
+
if (!currentActiveFocusId) return id;
|
|
910
|
+
return currentActiveFocusId;
|
|
911
|
+
});
|
|
912
|
+
}, []);
|
|
913
|
+
const removeFocusable = useCallback((id) => {
|
|
914
|
+
setActiveFocusId((currentActiveFocusId) => {
|
|
915
|
+
if (currentActiveFocusId === id) return;
|
|
916
|
+
return currentActiveFocusId;
|
|
917
|
+
});
|
|
918
|
+
setFocusables((currentFocusables) => {
|
|
919
|
+
const filtered = currentFocusables.filter((focusable) => {
|
|
920
|
+
return focusable.id !== id;
|
|
921
|
+
});
|
|
922
|
+
focusablesCountRef.current = filtered.length;
|
|
923
|
+
return filtered;
|
|
924
|
+
});
|
|
925
|
+
}, []);
|
|
926
|
+
const activateFocusable = useCallback((id) => {
|
|
927
|
+
setFocusables((currentFocusables) => currentFocusables.map((focusable) => {
|
|
928
|
+
if (focusable.id !== id) return focusable;
|
|
929
|
+
return {
|
|
930
|
+
id,
|
|
931
|
+
isActive: true
|
|
932
|
+
};
|
|
933
|
+
}));
|
|
934
|
+
}, []);
|
|
935
|
+
const deactivateFocusable = useCallback((id) => {
|
|
936
|
+
setActiveFocusId((currentActiveFocusId) => {
|
|
937
|
+
if (currentActiveFocusId === id) return;
|
|
938
|
+
return currentActiveFocusId;
|
|
939
|
+
});
|
|
940
|
+
setFocusables((currentFocusables) => currentFocusables.map((focusable) => {
|
|
941
|
+
if (focusable.id !== id) return focusable;
|
|
942
|
+
return {
|
|
943
|
+
id,
|
|
944
|
+
isActive: false
|
|
945
|
+
};
|
|
946
|
+
}));
|
|
947
|
+
}, []);
|
|
948
|
+
useEffect(() => {
|
|
949
|
+
return () => {
|
|
950
|
+
const canWriteToStdout = !stdout.destroyed && !stdout.writableEnded;
|
|
951
|
+
if (interactive && canWriteToStdout) cliCursor.show(stdout);
|
|
952
|
+
if (isRawModeSupported && (rawModeEnabledCount.current > 0 || pendingDisableRawModeRef.current)) disableRawMode();
|
|
953
|
+
if (bracketedPasteModeEnabledCount.current > 0) {
|
|
954
|
+
if (stdout.isTTY && canWriteToStdout) stdout.write(ansiEscapes.disableBracketedPaste);
|
|
955
|
+
bracketedPasteModeEnabledCount.current = 0;
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}, [
|
|
959
|
+
stdout,
|
|
960
|
+
isRawModeSupported,
|
|
961
|
+
disableRawMode,
|
|
962
|
+
interactive
|
|
963
|
+
]);
|
|
964
|
+
const appContextValue = useMemo(() => ({
|
|
965
|
+
exit: handleExit,
|
|
966
|
+
waitUntilRenderFlush: onWaitUntilRenderFlush,
|
|
967
|
+
suspendTerminal: onSuspendTerminal
|
|
968
|
+
}), [
|
|
969
|
+
handleExit,
|
|
970
|
+
onWaitUntilRenderFlush,
|
|
971
|
+
onSuspendTerminal
|
|
972
|
+
]);
|
|
973
|
+
const stdinContextValue = useMemo(() => ({
|
|
974
|
+
stdin,
|
|
975
|
+
setRawMode: handleSetRawMode,
|
|
976
|
+
setBracketedPasteMode: handleSetBracketedPasteMode,
|
|
977
|
+
isRawModeSupported,
|
|
978
|
+
internal_exitOnCtrlC: exitOnCtrlC,
|
|
979
|
+
internal_eventEmitter: internal_eventEmitter.current
|
|
980
|
+
}), [
|
|
981
|
+
stdin,
|
|
982
|
+
handleSetRawMode,
|
|
983
|
+
handleSetBracketedPasteMode,
|
|
984
|
+
isRawModeSupported,
|
|
985
|
+
exitOnCtrlC
|
|
986
|
+
]);
|
|
987
|
+
const stdoutContextValue = useMemo(() => ({
|
|
988
|
+
stdout,
|
|
989
|
+
write: writeToStdout
|
|
990
|
+
}), [stdout, writeToStdout]);
|
|
991
|
+
const stderrContextValue = useMemo(() => ({
|
|
992
|
+
stderr,
|
|
993
|
+
write: writeToStderr
|
|
994
|
+
}), [stderr, writeToStderr]);
|
|
995
|
+
const cursorContextValue = useMemo(() => ({ setCursorPosition }), [setCursorPosition]);
|
|
996
|
+
const focusContextValue = useMemo(() => ({
|
|
997
|
+
activeId: activeFocusId,
|
|
998
|
+
add: addFocusable,
|
|
999
|
+
remove: removeFocusable,
|
|
1000
|
+
activate: activateFocusable,
|
|
1001
|
+
deactivate: deactivateFocusable,
|
|
1002
|
+
enableFocus,
|
|
1003
|
+
disableFocus,
|
|
1004
|
+
focusNext,
|
|
1005
|
+
focusPrevious,
|
|
1006
|
+
focus
|
|
1007
|
+
}), [
|
|
1008
|
+
activeFocusId,
|
|
1009
|
+
addFocusable,
|
|
1010
|
+
removeFocusable,
|
|
1011
|
+
activateFocusable,
|
|
1012
|
+
deactivateFocusable,
|
|
1013
|
+
enableFocus,
|
|
1014
|
+
disableFocus,
|
|
1015
|
+
focusNext,
|
|
1016
|
+
focusPrevious,
|
|
1017
|
+
focus
|
|
1018
|
+
]);
|
|
1019
|
+
const animationContextValue = useMemo(() => ({
|
|
1020
|
+
renderThrottleMs,
|
|
1021
|
+
subscribe: animationSubscribe
|
|
1022
|
+
}), [animationSubscribe, renderThrottleMs]);
|
|
1023
|
+
return /* @__PURE__ */ jsx(AppContext.Provider, {
|
|
1024
|
+
value: appContextValue,
|
|
1025
|
+
children: /* @__PURE__ */ jsx(StdinContext.Provider, {
|
|
1026
|
+
value: stdinContextValue,
|
|
1027
|
+
children: /* @__PURE__ */ jsx(StdoutContext.Provider, {
|
|
1028
|
+
value: stdoutContextValue,
|
|
1029
|
+
children: /* @__PURE__ */ jsx(StderrContext.Provider, {
|
|
1030
|
+
value: stderrContextValue,
|
|
1031
|
+
children: /* @__PURE__ */ jsx(FocusContext.Provider, {
|
|
1032
|
+
value: focusContextValue,
|
|
1033
|
+
children: /* @__PURE__ */ jsx(animationContext.Provider, {
|
|
1034
|
+
value: animationContextValue,
|
|
1035
|
+
children: /* @__PURE__ */ jsx(CursorContext.Provider, {
|
|
1036
|
+
value: cursorContextValue,
|
|
1037
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
1038
|
+
onError: handleExit,
|
|
1039
|
+
children
|
|
1040
|
+
})
|
|
1041
|
+
})
|
|
1042
|
+
})
|
|
1043
|
+
})
|
|
1044
|
+
})
|
|
1045
|
+
})
|
|
1046
|
+
})
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
App.displayName = "InternalApp";
|
|
1050
|
+
//#endregion
|
|
1051
|
+
//#region src/cursor-position.ts
|
|
1052
|
+
const showCursorEscape = ansiEscapes.cursorShow;
|
|
1053
|
+
const hideCursorEscape = ansiEscapes.cursorHide;
|
|
1054
|
+
/**
|
|
1055
|
+
Compare two cursor positions. Returns true if they differ.
|
|
1056
|
+
*/
|
|
1057
|
+
const cursorPositionChanged = (a, b) => a?.x !== b?.x || a?.y !== b?.y;
|
|
1058
|
+
/**
|
|
1059
|
+
Build escape sequence to move cursor from the bottom of the output to the target position and show it.
|
|
1060
|
+
|
|
1061
|
+
`bottomLine` is the row the renderer left the cursor on, counted from the top of the output.
|
|
1062
|
+
That is always `lines.length - 1` for `lines = str.split('\n')`, whether or not the output ends
|
|
1063
|
+
with a newline:
|
|
1064
|
+
|
|
1065
|
+
- With a trailing newline, `split` yields one extra empty element and the renderer stops just
|
|
1066
|
+
past the last visible line — which is `lines.length - 1`.
|
|
1067
|
+
- Without one, there is no extra element and the renderer deliberately stops on the last visible
|
|
1068
|
+
line instead of moving past it — which is also `lines.length - 1`.
|
|
1069
|
+
|
|
1070
|
+
This is the same row basis `buildReturnToBottom` measures from, so the two stay in step.
|
|
1071
|
+
*/
|
|
1072
|
+
const buildCursorSuffix = (bottomLine, cursorPosition) => {
|
|
1073
|
+
if (!cursorPosition) return "";
|
|
1074
|
+
const moveUp = bottomLine - cursorPosition.y;
|
|
1075
|
+
return (moveUp > 0 ? ansiEscapes.cursorUp(moveUp) : "") + ansiEscapes.cursorTo(cursorPosition.x) + showCursorEscape;
|
|
1076
|
+
};
|
|
1077
|
+
/**
|
|
1078
|
+
Build escape sequence to move cursor from previousCursorPosition back to the bottom of output.
|
|
1079
|
+
This must be done before eraseLines or any operation that assumes cursor is at the bottom.
|
|
1080
|
+
*/
|
|
1081
|
+
const buildReturnToBottom = (previousLineCount, previousCursorPosition) => {
|
|
1082
|
+
if (!previousCursorPosition) return "";
|
|
1083
|
+
const down = previousLineCount - 1 - previousCursorPosition.y;
|
|
1084
|
+
return (down > 0 ? ansiEscapes.cursorDown(down) : "") + ansiEscapes.cursorTo(0);
|
|
1085
|
+
};
|
|
1086
|
+
/**
|
|
1087
|
+
Build the escape sequence for cursor-only updates (output unchanged, cursor moved).
|
|
1088
|
+
Hides cursor if it was previously shown, returns to bottom, then repositions.
|
|
1089
|
+
|
|
1090
|
+
`buildReturnToBottom` has just placed the cursor on row `previousLineCount - 1`, so the
|
|
1091
|
+
suffix measures from there rather than recomputing the row from the output.
|
|
1092
|
+
*/
|
|
1093
|
+
const buildCursorOnlySequence = (input) => {
|
|
1094
|
+
const hidePrefix = input.cursorWasShown ? hideCursorEscape : "";
|
|
1095
|
+
const returnToBottom = buildReturnToBottom(input.previousLineCount, input.previousCursorPosition);
|
|
1096
|
+
const cursorSuffix = buildCursorSuffix(input.previousLineCount - 1, input.cursorPosition);
|
|
1097
|
+
return hidePrefix + returnToBottom + cursorSuffix;
|
|
1098
|
+
};
|
|
1099
|
+
/**
|
|
1100
|
+
Build the prefix that hides cursor and returns to bottom before erasing or rewriting.
|
|
1101
|
+
Returns empty string if cursor was not shown.
|
|
1102
|
+
*/
|
|
1103
|
+
const buildReturnToBottomPrefix = (cursorWasShown, previousLineCount, previousCursorPosition) => {
|
|
1104
|
+
if (!cursorWasShown) return "";
|
|
1105
|
+
return hideCursorEscape + buildReturnToBottom(previousLineCount, previousCursorPosition);
|
|
1106
|
+
};
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/quick-lru.ts
|
|
1109
|
+
var QuickLru = class {
|
|
1110
|
+
#size = 0;
|
|
1111
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1112
|
+
#oldCache = /* @__PURE__ */ new Map();
|
|
1113
|
+
#maxSize;
|
|
1114
|
+
constructor({ maxSize }) {
|
|
1115
|
+
if (!(maxSize && maxSize > 0)) throw new TypeError("`maxSize` must be a number greater than 0");
|
|
1116
|
+
this.#maxSize = maxSize;
|
|
1117
|
+
}
|
|
1118
|
+
get size() {
|
|
1119
|
+
let oldCacheSize = 0;
|
|
1120
|
+
for (const key of this.#oldCache.keys()) if (!this.#cache.has(key)) oldCacheSize++;
|
|
1121
|
+
return Math.min(this.#size + oldCacheSize, this.#maxSize);
|
|
1122
|
+
}
|
|
1123
|
+
get(key) {
|
|
1124
|
+
if (this.#cache.has(key)) return this.#cache.get(key);
|
|
1125
|
+
if (this.#oldCache.has(key)) {
|
|
1126
|
+
const value = this.#oldCache.get(key);
|
|
1127
|
+
this.#oldCache.delete(key);
|
|
1128
|
+
this.#set(key, value);
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
set(key, value) {
|
|
1133
|
+
if (this.#cache.has(key)) this.#cache.set(key, value);
|
|
1134
|
+
else this.#set(key, value);
|
|
1135
|
+
return this;
|
|
1136
|
+
}
|
|
1137
|
+
has(key) {
|
|
1138
|
+
return this.#cache.has(key) || this.#oldCache.has(key);
|
|
1139
|
+
}
|
|
1140
|
+
delete(key) {
|
|
1141
|
+
const deleted = this.#cache.delete(key);
|
|
1142
|
+
if (deleted) this.#size--;
|
|
1143
|
+
return this.#oldCache.delete(key) || deleted;
|
|
1144
|
+
}
|
|
1145
|
+
clear() {
|
|
1146
|
+
this.#cache.clear();
|
|
1147
|
+
this.#oldCache.clear();
|
|
1148
|
+
this.#size = 0;
|
|
1149
|
+
}
|
|
1150
|
+
#set(key, value) {
|
|
1151
|
+
this.#cache.set(key, value);
|
|
1152
|
+
this.#size++;
|
|
1153
|
+
if (this.#size >= this.#maxSize) {
|
|
1154
|
+
this.#size = 0;
|
|
1155
|
+
this.#oldCache = this.#cache;
|
|
1156
|
+
this.#cache = /* @__PURE__ */ new Map();
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
//#endregion
|
|
1161
|
+
//#region src/measure-text.ts
|
|
1162
|
+
const cache = new QuickLru({ maxSize: 4096 });
|
|
1163
|
+
const measureText = (text) => {
|
|
1164
|
+
if (text.length === 0) return {
|
|
1165
|
+
width: 0,
|
|
1166
|
+
height: 0
|
|
1167
|
+
};
|
|
1168
|
+
const cachedDimensions = cache.get(text);
|
|
1169
|
+
if (cachedDimensions) return cachedDimensions;
|
|
1170
|
+
const dimensions = {
|
|
1171
|
+
width: widestLine(text),
|
|
1172
|
+
height: text.split("\n").length
|
|
1173
|
+
};
|
|
1174
|
+
cache.set(text, dimensions);
|
|
1175
|
+
return dimensions;
|
|
1176
|
+
};
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/ansi-tokenizer.ts
|
|
1179
|
+
const bellCharacter = "\x07";
|
|
1180
|
+
const escapeCharacter = "\x1B";
|
|
1181
|
+
const stringTerminatorCharacter = "";
|
|
1182
|
+
const csiCharacter = "";
|
|
1183
|
+
const oscCharacter = "";
|
|
1184
|
+
const dcsCharacter = "";
|
|
1185
|
+
const pmCharacter = "";
|
|
1186
|
+
const apcCharacter = "";
|
|
1187
|
+
const sosCharacter = "";
|
|
1188
|
+
const isCsiParameterCharacter = (character) => {
|
|
1189
|
+
const codePoint = character.codePointAt(0);
|
|
1190
|
+
return codePoint !== void 0 && codePoint >= 48 && codePoint <= 63;
|
|
1191
|
+
};
|
|
1192
|
+
const isCsiIntermediateCharacter = (character) => {
|
|
1193
|
+
const codePoint = character.codePointAt(0);
|
|
1194
|
+
return codePoint !== void 0 && codePoint >= 32 && codePoint <= 47;
|
|
1195
|
+
};
|
|
1196
|
+
const isCsiFinalCharacter = (character) => {
|
|
1197
|
+
const codePoint = character.codePointAt(0);
|
|
1198
|
+
return codePoint !== void 0 && codePoint >= 64 && codePoint <= 126;
|
|
1199
|
+
};
|
|
1200
|
+
const isEscapeIntermediateCharacter = (character) => {
|
|
1201
|
+
const codePoint = character.codePointAt(0);
|
|
1202
|
+
return codePoint !== void 0 && codePoint >= 32 && codePoint <= 47;
|
|
1203
|
+
};
|
|
1204
|
+
const isEscapeFinalCharacter = (character) => {
|
|
1205
|
+
const codePoint = character.codePointAt(0);
|
|
1206
|
+
return codePoint !== void 0 && codePoint >= 48 && codePoint <= 126;
|
|
1207
|
+
};
|
|
1208
|
+
const isC1ControlCharacter = (character) => {
|
|
1209
|
+
const codePoint = character.codePointAt(0);
|
|
1210
|
+
return codePoint !== void 0 && codePoint >= 128 && codePoint <= 159;
|
|
1211
|
+
};
|
|
1212
|
+
const readCsiSequence = (text, fromIndex) => {
|
|
1213
|
+
let index = fromIndex;
|
|
1214
|
+
while (index < text.length) {
|
|
1215
|
+
const character = text[index];
|
|
1216
|
+
if (!isCsiParameterCharacter(character)) break;
|
|
1217
|
+
index++;
|
|
1218
|
+
}
|
|
1219
|
+
const parameterString = text.slice(fromIndex, index);
|
|
1220
|
+
const intermediateStartIndex = index;
|
|
1221
|
+
while (index < text.length) {
|
|
1222
|
+
const character = text[index];
|
|
1223
|
+
if (!isCsiIntermediateCharacter(character)) break;
|
|
1224
|
+
index++;
|
|
1225
|
+
}
|
|
1226
|
+
const intermediateString = text.slice(intermediateStartIndex, index);
|
|
1227
|
+
const finalCharacter = text[index];
|
|
1228
|
+
if (finalCharacter === void 0 || !isCsiFinalCharacter(finalCharacter)) return;
|
|
1229
|
+
return {
|
|
1230
|
+
endIndex: index + 1,
|
|
1231
|
+
parameterString,
|
|
1232
|
+
intermediateString,
|
|
1233
|
+
finalCharacter
|
|
1234
|
+
};
|
|
1235
|
+
};
|
|
1236
|
+
const findControlStringTerminatorIndex = (text, fromIndex, allowBellTerminator) => {
|
|
1237
|
+
for (let index = fromIndex; index < text.length; index++) {
|
|
1238
|
+
const character = text[index];
|
|
1239
|
+
if (allowBellTerminator && character === bellCharacter) return index + 1;
|
|
1240
|
+
if (character === stringTerminatorCharacter) return index + 1;
|
|
1241
|
+
if (character === escapeCharacter) {
|
|
1242
|
+
const followingCharacter = text[index + 1];
|
|
1243
|
+
if (followingCharacter === escapeCharacter) {
|
|
1244
|
+
index++;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (followingCharacter === "\\") return index + 2;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
const readEscapeSequence = (text, fromIndex) => {
|
|
1252
|
+
let index = fromIndex;
|
|
1253
|
+
while (index < text.length) {
|
|
1254
|
+
const character = text[index];
|
|
1255
|
+
if (!isEscapeIntermediateCharacter(character)) break;
|
|
1256
|
+
index++;
|
|
1257
|
+
}
|
|
1258
|
+
const intermediateString = text.slice(fromIndex, index);
|
|
1259
|
+
const finalCharacter = text[index];
|
|
1260
|
+
if (finalCharacter === void 0 || !isEscapeFinalCharacter(finalCharacter)) return;
|
|
1261
|
+
return {
|
|
1262
|
+
endIndex: index + 1,
|
|
1263
|
+
intermediateString,
|
|
1264
|
+
finalCharacter
|
|
1265
|
+
};
|
|
1266
|
+
};
|
|
1267
|
+
const getControlStringFromEscapeIntroducer = (character) => {
|
|
1268
|
+
switch (character) {
|
|
1269
|
+
case "]": return {
|
|
1270
|
+
type: "osc",
|
|
1271
|
+
allowBellTerminator: true
|
|
1272
|
+
};
|
|
1273
|
+
case "P": return {
|
|
1274
|
+
type: "dcs",
|
|
1275
|
+
allowBellTerminator: false
|
|
1276
|
+
};
|
|
1277
|
+
case "^": return {
|
|
1278
|
+
type: "pm",
|
|
1279
|
+
allowBellTerminator: false
|
|
1280
|
+
};
|
|
1281
|
+
case "_": return {
|
|
1282
|
+
type: "apc",
|
|
1283
|
+
allowBellTerminator: false
|
|
1284
|
+
};
|
|
1285
|
+
case "X": return {
|
|
1286
|
+
type: "sos",
|
|
1287
|
+
allowBellTerminator: false
|
|
1288
|
+
};
|
|
1289
|
+
default: return;
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
const getControlStringFromC1Introducer = (character) => {
|
|
1293
|
+
switch (character) {
|
|
1294
|
+
case oscCharacter: return {
|
|
1295
|
+
type: "osc",
|
|
1296
|
+
allowBellTerminator: true
|
|
1297
|
+
};
|
|
1298
|
+
case dcsCharacter: return {
|
|
1299
|
+
type: "dcs",
|
|
1300
|
+
allowBellTerminator: false
|
|
1301
|
+
};
|
|
1302
|
+
case pmCharacter: return {
|
|
1303
|
+
type: "pm",
|
|
1304
|
+
allowBellTerminator: false
|
|
1305
|
+
};
|
|
1306
|
+
case apcCharacter: return {
|
|
1307
|
+
type: "apc",
|
|
1308
|
+
allowBellTerminator: false
|
|
1309
|
+
};
|
|
1310
|
+
case sosCharacter: return {
|
|
1311
|
+
type: "sos",
|
|
1312
|
+
allowBellTerminator: false
|
|
1313
|
+
};
|
|
1314
|
+
default: return;
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
const hasAnsiControlCharacters = (text) => {
|
|
1318
|
+
if (text.includes(escapeCharacter)) return true;
|
|
1319
|
+
for (const character of text) if (isC1ControlCharacter(character)) return true;
|
|
1320
|
+
return false;
|
|
1321
|
+
};
|
|
1322
|
+
const malformedFromIndex = (tokens, text, textStartIndex, fromIndex) => {
|
|
1323
|
+
if (fromIndex > textStartIndex) tokens.push({
|
|
1324
|
+
type: "text",
|
|
1325
|
+
value: text.slice(textStartIndex, fromIndex)
|
|
1326
|
+
});
|
|
1327
|
+
tokens.push({
|
|
1328
|
+
type: "invalid",
|
|
1329
|
+
value: text.slice(fromIndex)
|
|
1330
|
+
});
|
|
1331
|
+
return tokens;
|
|
1332
|
+
};
|
|
1333
|
+
const tokenizeAnsi = (text) => {
|
|
1334
|
+
if (!hasAnsiControlCharacters(text)) return [{
|
|
1335
|
+
type: "text",
|
|
1336
|
+
value: text
|
|
1337
|
+
}];
|
|
1338
|
+
const tokens = [];
|
|
1339
|
+
let textStartIndex = 0;
|
|
1340
|
+
for (let index = 0; index < text.length;) {
|
|
1341
|
+
const character = text[index];
|
|
1342
|
+
if (character === void 0) break;
|
|
1343
|
+
if (character === escapeCharacter) {
|
|
1344
|
+
const followingCharacter = text[index + 1];
|
|
1345
|
+
if (followingCharacter === void 0) return malformedFromIndex(tokens, text, textStartIndex, index);
|
|
1346
|
+
if (followingCharacter === "[") {
|
|
1347
|
+
const csiSequence = readCsiSequence(text, index + 2);
|
|
1348
|
+
if (csiSequence === void 0) return malformedFromIndex(tokens, text, textStartIndex, index);
|
|
1349
|
+
if (index > textStartIndex) tokens.push({
|
|
1350
|
+
type: "text",
|
|
1351
|
+
value: text.slice(textStartIndex, index)
|
|
1352
|
+
});
|
|
1353
|
+
tokens.push({
|
|
1354
|
+
type: "csi",
|
|
1355
|
+
value: text.slice(index, csiSequence.endIndex),
|
|
1356
|
+
parameterString: csiSequence.parameterString,
|
|
1357
|
+
intermediateString: csiSequence.intermediateString,
|
|
1358
|
+
finalCharacter: csiSequence.finalCharacter
|
|
1359
|
+
});
|
|
1360
|
+
index = csiSequence.endIndex;
|
|
1361
|
+
textStartIndex = index;
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
const escapeControlString = getControlStringFromEscapeIntroducer(followingCharacter);
|
|
1365
|
+
if (escapeControlString !== void 0) {
|
|
1366
|
+
const controlStringTerminatorIndex = findControlStringTerminatorIndex(text, index + 2, escapeControlString.allowBellTerminator);
|
|
1367
|
+
if (controlStringTerminatorIndex === void 0) return malformedFromIndex(tokens, text, textStartIndex, index);
|
|
1368
|
+
if (index > textStartIndex) tokens.push({
|
|
1369
|
+
type: "text",
|
|
1370
|
+
value: text.slice(textStartIndex, index)
|
|
1371
|
+
});
|
|
1372
|
+
tokens.push({
|
|
1373
|
+
type: escapeControlString.type,
|
|
1374
|
+
value: text.slice(index, controlStringTerminatorIndex)
|
|
1375
|
+
});
|
|
1376
|
+
index = controlStringTerminatorIndex;
|
|
1377
|
+
textStartIndex = index;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
const escapeSequence = readEscapeSequence(text, index + 1);
|
|
1381
|
+
if (escapeSequence === void 0) {
|
|
1382
|
+
if (isEscapeIntermediateCharacter(followingCharacter)) return malformedFromIndex(tokens, text, textStartIndex, index);
|
|
1383
|
+
if (index > textStartIndex) tokens.push({
|
|
1384
|
+
type: "text",
|
|
1385
|
+
value: text.slice(textStartIndex, index)
|
|
1386
|
+
});
|
|
1387
|
+
index++;
|
|
1388
|
+
textStartIndex = index;
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
if (index > textStartIndex) tokens.push({
|
|
1392
|
+
type: "text",
|
|
1393
|
+
value: text.slice(textStartIndex, index)
|
|
1394
|
+
});
|
|
1395
|
+
tokens.push({
|
|
1396
|
+
type: "esc",
|
|
1397
|
+
value: text.slice(index, escapeSequence.endIndex),
|
|
1398
|
+
intermediateString: escapeSequence.intermediateString,
|
|
1399
|
+
finalCharacter: escapeSequence.finalCharacter
|
|
1400
|
+
});
|
|
1401
|
+
index = escapeSequence.endIndex;
|
|
1402
|
+
textStartIndex = index;
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
if (character === csiCharacter) {
|
|
1406
|
+
const csiSequence = readCsiSequence(text, index + 1);
|
|
1407
|
+
if (csiSequence === void 0) return malformedFromIndex(tokens, text, textStartIndex, index);
|
|
1408
|
+
if (index > textStartIndex) tokens.push({
|
|
1409
|
+
type: "text",
|
|
1410
|
+
value: text.slice(textStartIndex, index)
|
|
1411
|
+
});
|
|
1412
|
+
tokens.push({
|
|
1413
|
+
type: "csi",
|
|
1414
|
+
value: text.slice(index, csiSequence.endIndex),
|
|
1415
|
+
parameterString: csiSequence.parameterString,
|
|
1416
|
+
intermediateString: csiSequence.intermediateString,
|
|
1417
|
+
finalCharacter: csiSequence.finalCharacter
|
|
1418
|
+
});
|
|
1419
|
+
index = csiSequence.endIndex;
|
|
1420
|
+
textStartIndex = index;
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
const c1ControlString = getControlStringFromC1Introducer(character);
|
|
1424
|
+
if (c1ControlString !== void 0) {
|
|
1425
|
+
const controlStringTerminatorIndex = findControlStringTerminatorIndex(text, index + 1, c1ControlString.allowBellTerminator);
|
|
1426
|
+
if (controlStringTerminatorIndex === void 0) return malformedFromIndex(tokens, text, textStartIndex, index);
|
|
1427
|
+
if (index > textStartIndex) tokens.push({
|
|
1428
|
+
type: "text",
|
|
1429
|
+
value: text.slice(textStartIndex, index)
|
|
1430
|
+
});
|
|
1431
|
+
tokens.push({
|
|
1432
|
+
type: c1ControlString.type,
|
|
1433
|
+
value: text.slice(index, controlStringTerminatorIndex)
|
|
1434
|
+
});
|
|
1435
|
+
index = controlStringTerminatorIndex;
|
|
1436
|
+
textStartIndex = index;
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (character === stringTerminatorCharacter) {
|
|
1440
|
+
if (index > textStartIndex) tokens.push({
|
|
1441
|
+
type: "text",
|
|
1442
|
+
value: text.slice(textStartIndex, index)
|
|
1443
|
+
});
|
|
1444
|
+
tokens.push({
|
|
1445
|
+
type: "st",
|
|
1446
|
+
value: character
|
|
1447
|
+
});
|
|
1448
|
+
index++;
|
|
1449
|
+
textStartIndex = index;
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
if (isC1ControlCharacter(character)) {
|
|
1453
|
+
if (index > textStartIndex) tokens.push({
|
|
1454
|
+
type: "text",
|
|
1455
|
+
value: text.slice(textStartIndex, index)
|
|
1456
|
+
});
|
|
1457
|
+
tokens.push({
|
|
1458
|
+
type: "c1",
|
|
1459
|
+
value: character
|
|
1460
|
+
});
|
|
1461
|
+
index++;
|
|
1462
|
+
textStartIndex = index;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
index++;
|
|
1466
|
+
}
|
|
1467
|
+
if (textStartIndex < text.length) tokens.push({
|
|
1468
|
+
type: "text",
|
|
1469
|
+
value: text.slice(textStartIndex)
|
|
1470
|
+
});
|
|
1471
|
+
return tokens;
|
|
1472
|
+
};
|
|
1473
|
+
//#endregion
|
|
1474
|
+
//#region src/sanitize-ansi.ts
|
|
1475
|
+
const sgrParametersRegex = /^[\d:;]*$/;
|
|
1476
|
+
const sanitizeAnsi = (text) => {
|
|
1477
|
+
if (!hasAnsiControlCharacters(text)) return text;
|
|
1478
|
+
let output = "";
|
|
1479
|
+
for (const token of tokenizeAnsi(text)) {
|
|
1480
|
+
if (token.type === "text" || token.type === "osc") {
|
|
1481
|
+
output += token.value;
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (token.type === "csi" && token.finalCharacter === "m" && token.intermediateString === "" && sgrParametersRegex.test(token.parameterString)) output += token.value;
|
|
1485
|
+
}
|
|
1486
|
+
return output;
|
|
1487
|
+
};
|
|
1488
|
+
//#endregion
|
|
1489
|
+
//#region src/squash-text-nodes.ts
|
|
1490
|
+
const squashTextNodes = (node) => {
|
|
1491
|
+
let text = "";
|
|
1492
|
+
for (let index = 0; index < node.childNodes.length; index++) {
|
|
1493
|
+
const childNode = node.childNodes[index];
|
|
1494
|
+
if (childNode === void 0) continue;
|
|
1495
|
+
let nodeText = "";
|
|
1496
|
+
if (childNode.nodeName === "#text") nodeText = childNode.nodeValue;
|
|
1497
|
+
else {
|
|
1498
|
+
if (childNode.nodeName === "ink-text" || childNode.nodeName === "ink-virtual-text") nodeText = squashTextNodes(childNode);
|
|
1499
|
+
if (nodeText.length > 0 && typeof childNode.internal_transform === "function") nodeText = childNode.internal_transform(nodeText, index);
|
|
1500
|
+
}
|
|
1501
|
+
text += nodeText;
|
|
1502
|
+
}
|
|
1503
|
+
return sanitizeAnsi(text);
|
|
1504
|
+
};
|
|
1505
|
+
//#endregion
|
|
1506
|
+
//#region src/wrap-text.ts
|
|
1507
|
+
const wrapTextCache = new QuickLru({ maxSize: 4096 });
|
|
1508
|
+
const wrapText = (text, maxWidth, wrapType) => {
|
|
1509
|
+
const cacheKey = text + String(maxWidth) + String(wrapType);
|
|
1510
|
+
const cachedText = wrapTextCache.get(cacheKey);
|
|
1511
|
+
if (cachedText !== void 0) return cachedText;
|
|
1512
|
+
let wrappedText = text;
|
|
1513
|
+
if (wrapType === "wrap") wrappedText = wrapAnsi(text, maxWidth, {
|
|
1514
|
+
trim: false,
|
|
1515
|
+
hard: true
|
|
1516
|
+
});
|
|
1517
|
+
if (wrapType === "hard") wrappedText = wrapAnsi(text, maxWidth, {
|
|
1518
|
+
trim: false,
|
|
1519
|
+
hard: true,
|
|
1520
|
+
wordWrap: false
|
|
1521
|
+
});
|
|
1522
|
+
if (wrapType.startsWith("truncate")) {
|
|
1523
|
+
let position = "end";
|
|
1524
|
+
if (wrapType === "truncate-middle") position = "middle";
|
|
1525
|
+
if (wrapType === "truncate-start") position = "start";
|
|
1526
|
+
wrappedText = cliTruncate(text, maxWidth, { position });
|
|
1527
|
+
}
|
|
1528
|
+
wrapTextCache.set(cacheKey, wrappedText);
|
|
1529
|
+
return wrappedText;
|
|
1530
|
+
};
|
|
1531
|
+
//#endregion
|
|
1532
|
+
//#region src/dom.ts
|
|
1533
|
+
const createNode = (nodeName) => {
|
|
1534
|
+
const node = {
|
|
1535
|
+
nodeName,
|
|
1536
|
+
style: {},
|
|
1537
|
+
attributes: {},
|
|
1538
|
+
childNodes: [],
|
|
1539
|
+
parentNode: void 0,
|
|
1540
|
+
yogaNode: nodeName === "ink-virtual-text" ? void 0 : Yoga.Node.create(),
|
|
1541
|
+
internal_accessibility: {}
|
|
1542
|
+
};
|
|
1543
|
+
if (nodeName === "ink-text") node.yogaNode?.setMeasureFunc(measureTextNode.bind(null, node));
|
|
1544
|
+
return node;
|
|
1545
|
+
};
|
|
1546
|
+
const appendChildNode = (node, childNode) => {
|
|
1547
|
+
if (childNode.parentNode) removeChildNode(childNode.parentNode, childNode);
|
|
1548
|
+
childNode.parentNode = node;
|
|
1549
|
+
node.childNodes.push(childNode);
|
|
1550
|
+
if (childNode.yogaNode) node.yogaNode?.insertChild(childNode.yogaNode, node.yogaNode.getChildCount());
|
|
1551
|
+
if (node.nodeName === "ink-text" || node.nodeName === "ink-virtual-text") markNodeAsDirty(node);
|
|
1552
|
+
};
|
|
1553
|
+
const insertBeforeNode = (node, newChildNode, beforeChildNode) => {
|
|
1554
|
+
if (newChildNode.parentNode) removeChildNode(newChildNode.parentNode, newChildNode);
|
|
1555
|
+
newChildNode.parentNode = node;
|
|
1556
|
+
const index = node.childNodes.indexOf(beforeChildNode);
|
|
1557
|
+
if (index >= 0) {
|
|
1558
|
+
node.childNodes.splice(index, 0, newChildNode);
|
|
1559
|
+
if (newChildNode.yogaNode) node.yogaNode?.insertChild(newChildNode.yogaNode, index);
|
|
1560
|
+
} else {
|
|
1561
|
+
node.childNodes.push(newChildNode);
|
|
1562
|
+
if (newChildNode.yogaNode) node.yogaNode?.insertChild(newChildNode.yogaNode, node.yogaNode.getChildCount());
|
|
1563
|
+
}
|
|
1564
|
+
if (node.nodeName === "ink-text" || node.nodeName === "ink-virtual-text") markNodeAsDirty(node);
|
|
1565
|
+
};
|
|
1566
|
+
const removeChildNode = (node, removeNode) => {
|
|
1567
|
+
if (removeNode.yogaNode) removeNode.parentNode?.yogaNode?.removeChild(removeNode.yogaNode);
|
|
1568
|
+
removeNode.parentNode = void 0;
|
|
1569
|
+
const index = node.childNodes.indexOf(removeNode);
|
|
1570
|
+
if (index >= 0) node.childNodes.splice(index, 1);
|
|
1571
|
+
if (node.nodeName === "ink-text" || node.nodeName === "ink-virtual-text") markNodeAsDirty(node);
|
|
1572
|
+
};
|
|
1573
|
+
const nullifyYogaNodes = (node) => {
|
|
1574
|
+
node.yogaNode = void 0;
|
|
1575
|
+
if (node.nodeName !== "#text") for (const childNode of node.childNodes) nullifyYogaNodes(childNode);
|
|
1576
|
+
};
|
|
1577
|
+
/**
|
|
1578
|
+
Detach a removed subtree from the layout engine: drop the measure callback
|
|
1579
|
+
and null the `yogaNode` reference on every DOM node within it.
|
|
1580
|
+
|
|
1581
|
+
Nulling the references makes every `?.yogaNode` guard in the codebase
|
|
1582
|
+
effective for removed nodes, turns lingering access into a safe no-op (see
|
|
1583
|
+
QwenLM/qwen-code#6820), and lets the garbage collector reclaim the Yoga
|
|
1584
|
+
tree along with its closures.
|
|
1585
|
+
*/
|
|
1586
|
+
const detachYogaSubtree = (removeNode) => {
|
|
1587
|
+
removeNode.yogaNode?.unsetMeasureFunc();
|
|
1588
|
+
nullifyYogaNodes(removeNode);
|
|
1589
|
+
};
|
|
1590
|
+
const setAttribute = (node, key, value) => {
|
|
1591
|
+
if (key === "internal_accessibility") {
|
|
1592
|
+
node.internal_accessibility = value;
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
node.attributes[key] = value;
|
|
1596
|
+
};
|
|
1597
|
+
const setStyle = (node, style) => {
|
|
1598
|
+
node.style = style ?? {};
|
|
1599
|
+
};
|
|
1600
|
+
const createTextNode = (text) => {
|
|
1601
|
+
const node = {
|
|
1602
|
+
nodeName: "#text",
|
|
1603
|
+
nodeValue: text,
|
|
1604
|
+
yogaNode: void 0,
|
|
1605
|
+
parentNode: void 0,
|
|
1606
|
+
style: {}
|
|
1607
|
+
};
|
|
1608
|
+
setTextNodeValue(node, text);
|
|
1609
|
+
return node;
|
|
1610
|
+
};
|
|
1611
|
+
const measureTextNode = function(node, width) {
|
|
1612
|
+
const text = node.nodeName === "#text" ? node.nodeValue : squashTextNodes(node);
|
|
1613
|
+
const dimensions = measureText(text);
|
|
1614
|
+
if (dimensions.width <= width) return dimensions;
|
|
1615
|
+
if (dimensions.width >= 1 && width > 0 && width < 1) return dimensions;
|
|
1616
|
+
const textWrap = node.style?.textWrap ?? "wrap";
|
|
1617
|
+
const wrappedText = wrapText(text, width, textWrap);
|
|
1618
|
+
return measureText(wrappedText);
|
|
1619
|
+
};
|
|
1620
|
+
const findClosestYogaNode = (node) => {
|
|
1621
|
+
if (!node?.parentNode) return;
|
|
1622
|
+
return node.yogaNode ?? findClosestYogaNode(node.parentNode);
|
|
1623
|
+
};
|
|
1624
|
+
const markNodeAsDirty = (node) => {
|
|
1625
|
+
findClosestYogaNode(node)?.markDirty();
|
|
1626
|
+
};
|
|
1627
|
+
const setTextNodeValue = (node, text) => {
|
|
1628
|
+
if (typeof text !== "string") text = String(text);
|
|
1629
|
+
node.nodeValue = text;
|
|
1630
|
+
markNodeAsDirty(node);
|
|
1631
|
+
};
|
|
1632
|
+
const addLayoutListener = (rootNode, listener) => {
|
|
1633
|
+
if (rootNode.nodeName !== "ink-root") return () => {};
|
|
1634
|
+
rootNode.internal_layoutListeners ??= /* @__PURE__ */ new Set();
|
|
1635
|
+
rootNode.internal_layoutListeners.add(listener);
|
|
1636
|
+
return () => {
|
|
1637
|
+
rootNode.internal_layoutListeners?.delete(listener);
|
|
1638
|
+
};
|
|
1639
|
+
};
|
|
1640
|
+
const emitLayoutListeners = (rootNode) => {
|
|
1641
|
+
if (rootNode.nodeName !== "ink-root" || !rootNode.internal_layoutListeners) return;
|
|
1642
|
+
for (const listener of rootNode.internal_layoutListeners) listener();
|
|
1643
|
+
};
|
|
1644
|
+
//#endregion
|
|
1645
|
+
//#region src/instances.ts
|
|
1646
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
1647
|
+
//#endregion
|
|
1648
|
+
//#region src/is-in-ci.ts
|
|
1649
|
+
const check = (key) => key in process.env && process.env[key] !== "0" && process.env[key] !== "false";
|
|
1650
|
+
const isInCi = check("CI") || check("CONTINUOUS_INTEGRATION");
|
|
1651
|
+
//#endregion
|
|
1652
|
+
//#region src/kitty-keyboard.ts
|
|
1653
|
+
const kittyFlags = {
|
|
1654
|
+
disambiguateEscapeCodes: 1,
|
|
1655
|
+
reportEventTypes: 2,
|
|
1656
|
+
reportAlternateKeys: 4,
|
|
1657
|
+
reportAllKeysAsEscapeCodes: 8,
|
|
1658
|
+
reportAssociatedText: 16
|
|
1659
|
+
};
|
|
1660
|
+
function resolveFlags(flags) {
|
|
1661
|
+
let result = 0;
|
|
1662
|
+
for (const flag of flags) result |= kittyFlags[flag];
|
|
1663
|
+
return result;
|
|
1664
|
+
}
|
|
1665
|
+
const kittyModifiers = {
|
|
1666
|
+
shift: 1,
|
|
1667
|
+
alt: 2,
|
|
1668
|
+
ctrl: 4,
|
|
1669
|
+
super: 8,
|
|
1670
|
+
hyper: 16,
|
|
1671
|
+
meta: 32,
|
|
1672
|
+
capsLock: 64,
|
|
1673
|
+
numLock: 128
|
|
1674
|
+
};
|
|
1675
|
+
//#endregion
|
|
1676
|
+
//#region src/log-update.ts
|
|
1677
|
+
const visibleLineCount = (lines, str) => str.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
1678
|
+
const createStandard = (stream, { showCursor = false } = {}) => {
|
|
1679
|
+
let previousLineCount = 0;
|
|
1680
|
+
let previousOutput = "";
|
|
1681
|
+
let hasHiddenCursor = false;
|
|
1682
|
+
let cursorPosition;
|
|
1683
|
+
let cursorDirty = false;
|
|
1684
|
+
let previousCursorPosition;
|
|
1685
|
+
let cursorWasShown = false;
|
|
1686
|
+
const getActiveCursor = () => cursorDirty ? cursorPosition : void 0;
|
|
1687
|
+
const hasChanges = (str, activeCursor) => {
|
|
1688
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
1689
|
+
return str !== previousOutput || cursorChanged;
|
|
1690
|
+
};
|
|
1691
|
+
const render = (str) => {
|
|
1692
|
+
if (!showCursor && !hasHiddenCursor) {
|
|
1693
|
+
cliCursor.hide(stream);
|
|
1694
|
+
hasHiddenCursor = true;
|
|
1695
|
+
}
|
|
1696
|
+
const activeCursor = getActiveCursor();
|
|
1697
|
+
cursorDirty = false;
|
|
1698
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
1699
|
+
if (!hasChanges(str, activeCursor)) return false;
|
|
1700
|
+
const lines = str.split("\n");
|
|
1701
|
+
const cursorSuffix = buildCursorSuffix(lines.length - 1, activeCursor);
|
|
1702
|
+
if (str === previousOutput && cursorChanged) stream.write(buildCursorOnlySequence({
|
|
1703
|
+
cursorWasShown,
|
|
1704
|
+
previousLineCount,
|
|
1705
|
+
previousCursorPosition,
|
|
1706
|
+
cursorPosition: activeCursor
|
|
1707
|
+
}));
|
|
1708
|
+
else {
|
|
1709
|
+
previousOutput = str;
|
|
1710
|
+
const returnPrefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
|
|
1711
|
+
stream.write(returnPrefix + ansiEscapes.eraseLines(previousLineCount) + str + cursorSuffix);
|
|
1712
|
+
previousLineCount = lines.length;
|
|
1713
|
+
}
|
|
1714
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1715
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1716
|
+
return true;
|
|
1717
|
+
};
|
|
1718
|
+
render.clear = () => {
|
|
1719
|
+
const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
|
|
1720
|
+
stream.write(prefix + ansiEscapes.eraseLines(previousLineCount));
|
|
1721
|
+
previousOutput = "";
|
|
1722
|
+
previousLineCount = 0;
|
|
1723
|
+
previousCursorPosition = void 0;
|
|
1724
|
+
cursorWasShown = false;
|
|
1725
|
+
};
|
|
1726
|
+
render.done = () => {
|
|
1727
|
+
previousOutput = "";
|
|
1728
|
+
previousLineCount = 0;
|
|
1729
|
+
previousCursorPosition = void 0;
|
|
1730
|
+
cursorWasShown = false;
|
|
1731
|
+
if (!showCursor) {
|
|
1732
|
+
cliCursor.show(stream);
|
|
1733
|
+
hasHiddenCursor = false;
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
render.reset = () => {
|
|
1737
|
+
previousOutput = "";
|
|
1738
|
+
previousLineCount = 0;
|
|
1739
|
+
previousCursorPosition = void 0;
|
|
1740
|
+
cursorWasShown = false;
|
|
1741
|
+
};
|
|
1742
|
+
render.sync = (str) => {
|
|
1743
|
+
const activeCursor = cursorDirty ? cursorPosition : void 0;
|
|
1744
|
+
cursorDirty = false;
|
|
1745
|
+
const lines = str.split("\n");
|
|
1746
|
+
previousOutput = str;
|
|
1747
|
+
previousLineCount = lines.length;
|
|
1748
|
+
if (!activeCursor && cursorWasShown) stream.write(hideCursorEscape);
|
|
1749
|
+
if (activeCursor) stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
|
|
1750
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1751
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1752
|
+
};
|
|
1753
|
+
render.setCursorPosition = (position) => {
|
|
1754
|
+
cursorPosition = position;
|
|
1755
|
+
cursorDirty = true;
|
|
1756
|
+
};
|
|
1757
|
+
render.isCursorDirty = () => cursorDirty;
|
|
1758
|
+
render.willRender = (str) => hasChanges(str, getActiveCursor());
|
|
1759
|
+
return render;
|
|
1760
|
+
};
|
|
1761
|
+
const createIncremental = (stream, { showCursor = false } = {}) => {
|
|
1762
|
+
let previousLines = [];
|
|
1763
|
+
let previousOutput = "";
|
|
1764
|
+
let hasHiddenCursor = false;
|
|
1765
|
+
let cursorPosition;
|
|
1766
|
+
let cursorDirty = false;
|
|
1767
|
+
let previousCursorPosition;
|
|
1768
|
+
let cursorWasShown = false;
|
|
1769
|
+
const getActiveCursor = () => cursorDirty ? cursorPosition : void 0;
|
|
1770
|
+
const hasChanges = (str, activeCursor) => {
|
|
1771
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
1772
|
+
return str !== previousOutput || cursorChanged;
|
|
1773
|
+
};
|
|
1774
|
+
const render = (str) => {
|
|
1775
|
+
if (!showCursor && !hasHiddenCursor) {
|
|
1776
|
+
cliCursor.hide(stream);
|
|
1777
|
+
hasHiddenCursor = true;
|
|
1778
|
+
}
|
|
1779
|
+
const activeCursor = getActiveCursor();
|
|
1780
|
+
cursorDirty = false;
|
|
1781
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
1782
|
+
if (!hasChanges(str, activeCursor)) return false;
|
|
1783
|
+
const nextLines = str.split("\n");
|
|
1784
|
+
const visibleCount = visibleLineCount(nextLines, str);
|
|
1785
|
+
const previousVisible = visibleLineCount(previousLines, previousOutput);
|
|
1786
|
+
if (str === previousOutput && cursorChanged) {
|
|
1787
|
+
stream.write(buildCursorOnlySequence({
|
|
1788
|
+
cursorWasShown,
|
|
1789
|
+
previousLineCount: previousLines.length,
|
|
1790
|
+
previousCursorPosition,
|
|
1791
|
+
cursorPosition: activeCursor
|
|
1792
|
+
}));
|
|
1793
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1794
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1795
|
+
return true;
|
|
1796
|
+
}
|
|
1797
|
+
const returnPrefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
|
|
1798
|
+
if (str === "\n" || previousOutput.length === 0) {
|
|
1799
|
+
const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
|
|
1800
|
+
stream.write(returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix);
|
|
1801
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1802
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1803
|
+
previousOutput = str;
|
|
1804
|
+
previousLines = nextLines;
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
if (visibleCount > previousVisible) {
|
|
1808
|
+
const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
|
|
1809
|
+
stream.write(returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix);
|
|
1810
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1811
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1812
|
+
previousOutput = str;
|
|
1813
|
+
previousLines = nextLines;
|
|
1814
|
+
return true;
|
|
1815
|
+
}
|
|
1816
|
+
const hasTrailingNewline = str.endsWith("\n");
|
|
1817
|
+
const buffer = [];
|
|
1818
|
+
buffer.push(returnPrefix);
|
|
1819
|
+
if (visibleCount < previousVisible) {
|
|
1820
|
+
const extraSlot = previousOutput.endsWith("\n") ? 1 : 0;
|
|
1821
|
+
buffer.push(ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot), ansiEscapes.cursorUp(visibleCount));
|
|
1822
|
+
} else buffer.push(ansiEscapes.cursorUp(previousLines.length - 1));
|
|
1823
|
+
for (let i = 0; i < visibleCount; i++) {
|
|
1824
|
+
const isLastLine = i === visibleCount - 1;
|
|
1825
|
+
if (nextLines[i] === previousLines[i]) {
|
|
1826
|
+
if (!isLastLine || hasTrailingNewline) buffer.push(ansiEscapes.cursorNextLine);
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
buffer.push(ansiEscapes.cursorTo(0) + nextLines[i] + ansiEscapes.eraseEndLine + (isLastLine && !hasTrailingNewline ? "" : "\n"));
|
|
1830
|
+
}
|
|
1831
|
+
const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
|
|
1832
|
+
buffer.push(cursorSuffix);
|
|
1833
|
+
stream.write(buffer.join(""));
|
|
1834
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1835
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1836
|
+
previousOutput = str;
|
|
1837
|
+
previousLines = nextLines;
|
|
1838
|
+
return true;
|
|
1839
|
+
};
|
|
1840
|
+
render.clear = () => {
|
|
1841
|
+
const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
|
|
1842
|
+
stream.write(prefix + ansiEscapes.eraseLines(previousLines.length));
|
|
1843
|
+
previousOutput = "";
|
|
1844
|
+
previousLines = [];
|
|
1845
|
+
previousCursorPosition = void 0;
|
|
1846
|
+
cursorWasShown = false;
|
|
1847
|
+
};
|
|
1848
|
+
render.done = () => {
|
|
1849
|
+
previousOutput = "";
|
|
1850
|
+
previousLines = [];
|
|
1851
|
+
previousCursorPosition = void 0;
|
|
1852
|
+
cursorWasShown = false;
|
|
1853
|
+
if (!showCursor) {
|
|
1854
|
+
cliCursor.show(stream);
|
|
1855
|
+
hasHiddenCursor = false;
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
render.reset = () => {
|
|
1859
|
+
previousOutput = "";
|
|
1860
|
+
previousLines = [];
|
|
1861
|
+
previousCursorPosition = void 0;
|
|
1862
|
+
cursorWasShown = false;
|
|
1863
|
+
};
|
|
1864
|
+
render.sync = (str) => {
|
|
1865
|
+
const activeCursor = cursorDirty ? cursorPosition : void 0;
|
|
1866
|
+
cursorDirty = false;
|
|
1867
|
+
const lines = str.split("\n");
|
|
1868
|
+
previousOutput = str;
|
|
1869
|
+
previousLines = lines;
|
|
1870
|
+
if (!activeCursor && cursorWasShown) stream.write(hideCursorEscape);
|
|
1871
|
+
if (activeCursor) stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
|
|
1872
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
|
|
1873
|
+
cursorWasShown = activeCursor !== void 0;
|
|
1874
|
+
};
|
|
1875
|
+
render.setCursorPosition = (position) => {
|
|
1876
|
+
cursorPosition = position;
|
|
1877
|
+
cursorDirty = true;
|
|
1878
|
+
};
|
|
1879
|
+
render.isCursorDirty = () => cursorDirty;
|
|
1880
|
+
render.willRender = (str) => hasChanges(str, getActiveCursor());
|
|
1881
|
+
return render;
|
|
1882
|
+
};
|
|
1883
|
+
const create$1 = (stream, { showCursor = false, incremental = false } = {}) => {
|
|
1884
|
+
if (incremental) return createIncremental(stream, { showCursor });
|
|
1885
|
+
return createStandard(stream, { showCursor });
|
|
1886
|
+
};
|
|
1887
|
+
const logUpdate = { create: create$1 };
|
|
1888
|
+
//#endregion
|
|
1889
|
+
//#region src/patch-console.ts
|
|
1890
|
+
const consoleMethods = [
|
|
1891
|
+
"assert",
|
|
1892
|
+
"count",
|
|
1893
|
+
"countReset",
|
|
1894
|
+
"debug",
|
|
1895
|
+
"dir",
|
|
1896
|
+
"dirxml",
|
|
1897
|
+
"error",
|
|
1898
|
+
"group",
|
|
1899
|
+
"groupCollapsed",
|
|
1900
|
+
"groupEnd",
|
|
1901
|
+
"info",
|
|
1902
|
+
"log",
|
|
1903
|
+
"table",
|
|
1904
|
+
"time",
|
|
1905
|
+
"timeEnd",
|
|
1906
|
+
"timeLog",
|
|
1907
|
+
"trace",
|
|
1908
|
+
"warn"
|
|
1909
|
+
];
|
|
1910
|
+
const patchConsole = (callback) => {
|
|
1911
|
+
const stdout = new PassThrough();
|
|
1912
|
+
const stderr = new PassThrough();
|
|
1913
|
+
stdout.write = (data) => {
|
|
1914
|
+
callback("stdout", String(data));
|
|
1915
|
+
return true;
|
|
1916
|
+
};
|
|
1917
|
+
stderr.write = (data) => {
|
|
1918
|
+
callback("stderr", String(data));
|
|
1919
|
+
return true;
|
|
1920
|
+
};
|
|
1921
|
+
const internalConsole = new Console(stdout, stderr);
|
|
1922
|
+
const originalMethods = /* @__PURE__ */ new Map();
|
|
1923
|
+
for (const method of consoleMethods) {
|
|
1924
|
+
originalMethods.set(method, console[method]);
|
|
1925
|
+
console[method] = internalConsole[method];
|
|
1926
|
+
}
|
|
1927
|
+
return () => {
|
|
1928
|
+
for (const method of consoleMethods) console[method] = originalMethods.get(method);
|
|
1929
|
+
originalMethods.clear();
|
|
1930
|
+
};
|
|
1931
|
+
};
|
|
1932
|
+
//#endregion
|
|
1933
|
+
//#region src/styles.ts
|
|
1934
|
+
const positionEdges = [
|
|
1935
|
+
["top", Yoga.EDGE_TOP],
|
|
1936
|
+
["right", Yoga.EDGE_RIGHT],
|
|
1937
|
+
["bottom", Yoga.EDGE_BOTTOM],
|
|
1938
|
+
["left", Yoga.EDGE_LEFT]
|
|
1939
|
+
];
|
|
1940
|
+
const applyPositionStyles = (node, style) => {
|
|
1941
|
+
if ("position" in style) {
|
|
1942
|
+
let positionType = Yoga.POSITION_TYPE_RELATIVE;
|
|
1943
|
+
if (style.position === "absolute") positionType = Yoga.POSITION_TYPE_ABSOLUTE;
|
|
1944
|
+
else if (style.position === "static") positionType = Yoga.POSITION_TYPE_STATIC;
|
|
1945
|
+
node.setPositionType(positionType);
|
|
1946
|
+
}
|
|
1947
|
+
for (const [property, edge] of positionEdges) {
|
|
1948
|
+
if (!(property in style)) continue;
|
|
1949
|
+
const value = style[property];
|
|
1950
|
+
if (typeof value === "string") {
|
|
1951
|
+
node.setPositionPercent(edge, Number.parseFloat(value));
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
node.setPosition(edge, value);
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
const applyMarginStyles = (node, style) => {
|
|
1958
|
+
if ("margin" in style) node.setMargin(Yoga.EDGE_ALL, style.margin ?? 0);
|
|
1959
|
+
if ("marginX" in style) node.setMargin(Yoga.EDGE_HORIZONTAL, style.marginX ?? 0);
|
|
1960
|
+
if ("marginY" in style) node.setMargin(Yoga.EDGE_VERTICAL, style.marginY ?? 0);
|
|
1961
|
+
if ("marginLeft" in style) node.setMargin(Yoga.EDGE_START, style.marginLeft ?? 0);
|
|
1962
|
+
if ("marginRight" in style) node.setMargin(Yoga.EDGE_END, style.marginRight ?? 0);
|
|
1963
|
+
if ("marginTop" in style) node.setMargin(Yoga.EDGE_TOP, style.marginTop ?? 0);
|
|
1964
|
+
if ("marginBottom" in style) node.setMargin(Yoga.EDGE_BOTTOM, style.marginBottom ?? 0);
|
|
1965
|
+
};
|
|
1966
|
+
const applyPaddingStyles = (node, style) => {
|
|
1967
|
+
if ("padding" in style) node.setPadding(Yoga.EDGE_ALL, style.padding ?? 0);
|
|
1968
|
+
if ("paddingX" in style) node.setPadding(Yoga.EDGE_HORIZONTAL, style.paddingX ?? 0);
|
|
1969
|
+
if ("paddingY" in style) node.setPadding(Yoga.EDGE_VERTICAL, style.paddingY ?? 0);
|
|
1970
|
+
if ("paddingLeft" in style) node.setPadding(Yoga.EDGE_LEFT, style.paddingLeft ?? 0);
|
|
1971
|
+
if ("paddingRight" in style) node.setPadding(Yoga.EDGE_RIGHT, style.paddingRight ?? 0);
|
|
1972
|
+
if ("paddingTop" in style) node.setPadding(Yoga.EDGE_TOP, style.paddingTop ?? 0);
|
|
1973
|
+
if ("paddingBottom" in style) node.setPadding(Yoga.EDGE_BOTTOM, style.paddingBottom ?? 0);
|
|
1974
|
+
};
|
|
1975
|
+
const applyFlexStyles = (node, style) => {
|
|
1976
|
+
if ("flexGrow" in style) node.setFlexGrow(style.flexGrow ?? 0);
|
|
1977
|
+
if ("flexShrink" in style) node.setFlexShrink(typeof style.flexShrink === "number" ? style.flexShrink : 1);
|
|
1978
|
+
if ("flexWrap" in style) {
|
|
1979
|
+
if (style.flexWrap === "nowrap") node.setFlexWrap(Yoga.WRAP_NO_WRAP);
|
|
1980
|
+
if (style.flexWrap === "wrap") node.setFlexWrap(Yoga.WRAP_WRAP);
|
|
1981
|
+
if (style.flexWrap === "wrap-reverse") node.setFlexWrap(Yoga.WRAP_WRAP_REVERSE);
|
|
1982
|
+
}
|
|
1983
|
+
if ("flexDirection" in style) {
|
|
1984
|
+
if (style.flexDirection === "row") node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
|
1985
|
+
if (style.flexDirection === "row-reverse") node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW_REVERSE);
|
|
1986
|
+
if (style.flexDirection === "column") node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
|
1987
|
+
if (style.flexDirection === "column-reverse") node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
|
|
1988
|
+
}
|
|
1989
|
+
if ("flexBasis" in style) {
|
|
1990
|
+
if (typeof style.flexBasis === "number") node.setFlexBasis(style.flexBasis);
|
|
1991
|
+
else if (typeof style.flexBasis === "string") node.setFlexBasisPercent(Number.parseInt(style.flexBasis, 10));
|
|
1992
|
+
else node.setFlexBasisAuto();
|
|
1993
|
+
}
|
|
1994
|
+
if ("alignItems" in style) {
|
|
1995
|
+
if (style.alignItems === "stretch" || !style.alignItems) node.setAlignItems(Yoga.ALIGN_STRETCH);
|
|
1996
|
+
if (style.alignItems === "flex-start") node.setAlignItems(Yoga.ALIGN_FLEX_START);
|
|
1997
|
+
if (style.alignItems === "center") node.setAlignItems(Yoga.ALIGN_CENTER);
|
|
1998
|
+
if (style.alignItems === "flex-end") node.setAlignItems(Yoga.ALIGN_FLEX_END);
|
|
1999
|
+
if (style.alignItems === "baseline") node.setAlignItems(Yoga.ALIGN_BASELINE);
|
|
2000
|
+
}
|
|
2001
|
+
if ("alignSelf" in style) {
|
|
2002
|
+
if (style.alignSelf === "auto" || !style.alignSelf) node.setAlignSelf(Yoga.ALIGN_AUTO);
|
|
2003
|
+
if (style.alignSelf === "flex-start") node.setAlignSelf(Yoga.ALIGN_FLEX_START);
|
|
2004
|
+
if (style.alignSelf === "center") node.setAlignSelf(Yoga.ALIGN_CENTER);
|
|
2005
|
+
if (style.alignSelf === "flex-end") node.setAlignSelf(Yoga.ALIGN_FLEX_END);
|
|
2006
|
+
if (style.alignSelf === "stretch") node.setAlignSelf(Yoga.ALIGN_STRETCH);
|
|
2007
|
+
if (style.alignSelf === "baseline") node.setAlignSelf(Yoga.ALIGN_BASELINE);
|
|
2008
|
+
}
|
|
2009
|
+
if ("alignContent" in style) {
|
|
2010
|
+
if (style.alignContent === "flex-start" || !style.alignContent) node.setAlignContent(Yoga.ALIGN_FLEX_START);
|
|
2011
|
+
if (style.alignContent === "center") node.setAlignContent(Yoga.ALIGN_CENTER);
|
|
2012
|
+
if (style.alignContent === "flex-end") node.setAlignContent(Yoga.ALIGN_FLEX_END);
|
|
2013
|
+
if (style.alignContent === "space-between") node.setAlignContent(Yoga.ALIGN_SPACE_BETWEEN);
|
|
2014
|
+
if (style.alignContent === "space-around") node.setAlignContent(Yoga.ALIGN_SPACE_AROUND);
|
|
2015
|
+
if (style.alignContent === "space-evenly") node.setAlignContent(Yoga.ALIGN_SPACE_EVENLY);
|
|
2016
|
+
if (style.alignContent === "stretch") node.setAlignContent(Yoga.ALIGN_STRETCH);
|
|
2017
|
+
}
|
|
2018
|
+
if ("justifyContent" in style) {
|
|
2019
|
+
if (style.justifyContent === "flex-start" || !style.justifyContent) node.setJustifyContent(Yoga.JUSTIFY_FLEX_START);
|
|
2020
|
+
if (style.justifyContent === "center") node.setJustifyContent(Yoga.JUSTIFY_CENTER);
|
|
2021
|
+
if (style.justifyContent === "flex-end") node.setJustifyContent(Yoga.JUSTIFY_FLEX_END);
|
|
2022
|
+
if (style.justifyContent === "space-between") node.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN);
|
|
2023
|
+
if (style.justifyContent === "space-around") node.setJustifyContent(Yoga.JUSTIFY_SPACE_AROUND);
|
|
2024
|
+
if (style.justifyContent === "space-evenly") node.setJustifyContent(Yoga.JUSTIFY_SPACE_EVENLY);
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
const applyDimensionStyles = (node, style) => {
|
|
2028
|
+
if ("width" in style) {
|
|
2029
|
+
if (typeof style.width === "number") node.setWidth(style.width);
|
|
2030
|
+
else if (typeof style.width === "string") node.setWidthPercent(Number.parseInt(style.width, 10));
|
|
2031
|
+
else node.setWidthAuto();
|
|
2032
|
+
}
|
|
2033
|
+
if ("height" in style) {
|
|
2034
|
+
if (typeof style.height === "number") node.setHeight(style.height);
|
|
2035
|
+
else if (typeof style.height === "string") node.setHeightPercent(Number.parseInt(style.height, 10));
|
|
2036
|
+
else node.setHeightAuto();
|
|
2037
|
+
}
|
|
2038
|
+
if ("minWidth" in style) {
|
|
2039
|
+
if (typeof style.minWidth === "string") node.setMinWidthPercent(Number.parseInt(style.minWidth, 10));
|
|
2040
|
+
else node.setMinWidth(style.minWidth ?? 0);
|
|
2041
|
+
}
|
|
2042
|
+
if ("minHeight" in style) {
|
|
2043
|
+
if (typeof style.minHeight === "string") node.setMinHeightPercent(Number.parseInt(style.minHeight, 10));
|
|
2044
|
+
else node.setMinHeight(style.minHeight ?? 0);
|
|
2045
|
+
}
|
|
2046
|
+
if ("maxWidth" in style) {
|
|
2047
|
+
if (typeof style.maxWidth === "string") node.setMaxWidthPercent(Number.parseInt(style.maxWidth, 10));
|
|
2048
|
+
else node.setMaxWidth(style.maxWidth);
|
|
2049
|
+
}
|
|
2050
|
+
if ("maxHeight" in style) {
|
|
2051
|
+
if (typeof style.maxHeight === "string") node.setMaxHeightPercent(Number.parseInt(style.maxHeight, 10));
|
|
2052
|
+
else node.setMaxHeight(style.maxHeight);
|
|
2053
|
+
}
|
|
2054
|
+
if ("aspectRatio" in style) node.setAspectRatio(style.aspectRatio);
|
|
2055
|
+
};
|
|
2056
|
+
const applyDisplayStyles = (node, style) => {
|
|
2057
|
+
if ("display" in style) node.setDisplay(style.display === "flex" ? Yoga.DISPLAY_FLEX : Yoga.DISPLAY_NONE);
|
|
2058
|
+
};
|
|
2059
|
+
const applyBorderStyles = (node, style, currentStyle) => {
|
|
2060
|
+
if (!("borderStyle" in style || "borderTop" in style || "borderBottom" in style || "borderLeft" in style || "borderRight" in style)) return;
|
|
2061
|
+
const borderWidth = currentStyle.borderStyle ? 1 : 0;
|
|
2062
|
+
node.setBorder(Yoga.EDGE_TOP, currentStyle.borderTop === false ? 0 : borderWidth);
|
|
2063
|
+
node.setBorder(Yoga.EDGE_BOTTOM, currentStyle.borderBottom === false ? 0 : borderWidth);
|
|
2064
|
+
node.setBorder(Yoga.EDGE_LEFT, currentStyle.borderLeft === false ? 0 : borderWidth);
|
|
2065
|
+
node.setBorder(Yoga.EDGE_RIGHT, currentStyle.borderRight === false ? 0 : borderWidth);
|
|
2066
|
+
};
|
|
2067
|
+
const applyGapStyles = (node, style) => {
|
|
2068
|
+
if ("gap" in style) node.setGap(Yoga.GUTTER_ALL, style.gap ?? 0);
|
|
2069
|
+
if ("columnGap" in style) node.setGap(Yoga.GUTTER_COLUMN, style.columnGap ?? 0);
|
|
2070
|
+
if ("rowGap" in style) node.setGap(Yoga.GUTTER_ROW, style.rowGap ?? 0);
|
|
2071
|
+
};
|
|
2072
|
+
const styles = (node, style = {}, currentStyle = style) => {
|
|
2073
|
+
applyPositionStyles(node, style);
|
|
2074
|
+
applyMarginStyles(node, style);
|
|
2075
|
+
applyPaddingStyles(node, style);
|
|
2076
|
+
applyFlexStyles(node, style);
|
|
2077
|
+
applyDimensionStyles(node, style);
|
|
2078
|
+
applyDisplayStyles(node, style);
|
|
2079
|
+
applyBorderStyles(node, style, currentStyle);
|
|
2080
|
+
applyGapStyles(node, style);
|
|
2081
|
+
};
|
|
2082
|
+
//#endregion
|
|
2083
|
+
//#region src/reconciler.ts
|
|
2084
|
+
if (process.env["SIGIL_DEV"] === "true") {
|
|
2085
|
+
let isDevtoolsInstalled = false;
|
|
2086
|
+
try {
|
|
2087
|
+
import.meta.resolve("react-devtools-core");
|
|
2088
|
+
isDevtoolsInstalled = true;
|
|
2089
|
+
} catch {}
|
|
2090
|
+
if (isDevtoolsInstalled) await import("./devtools-BhYGjb7h.js");
|
|
2091
|
+
}
|
|
2092
|
+
const diff = (before, after) => {
|
|
2093
|
+
if (before === after) return;
|
|
2094
|
+
if (!before) return after;
|
|
2095
|
+
const changed = {};
|
|
2096
|
+
let isChanged = false;
|
|
2097
|
+
for (const key of Object.keys(before)) if (after ? !Object.hasOwn(after, key) : true) {
|
|
2098
|
+
changed[key] = void 0;
|
|
2099
|
+
isChanged = true;
|
|
2100
|
+
}
|
|
2101
|
+
if (after) {
|
|
2102
|
+
for (const key of Object.keys(after)) if (after[key] !== before[key]) {
|
|
2103
|
+
changed[key] = after[key];
|
|
2104
|
+
isChanged = true;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
return isChanged ? changed : void 0;
|
|
2108
|
+
};
|
|
2109
|
+
const findRootNode$1 = (node) => {
|
|
2110
|
+
let current = node;
|
|
2111
|
+
while (current) {
|
|
2112
|
+
if (current.nodeName === "ink-root") return current;
|
|
2113
|
+
current = current.parentNode;
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
/**
|
|
2117
|
+
* Clear the root's cached `staticNode` when the node it points at is being
|
|
2118
|
+
* removed as part of a larger subtree.
|
|
2119
|
+
*
|
|
2120
|
+
* The previous identity check (`staticNode === removeNode`) only caught direct
|
|
2121
|
+
* removal of the `<Static>` element. When an *ancestor* of `<Static>` is
|
|
2122
|
+
* removed, the stale `staticNode` reference survives and the next render would
|
|
2123
|
+
* replay stale static output (and, before `detachYogaSubtree`, trap on detached
|
|
2124
|
+
* WASM memory — see QwenLM/qwen-code#6820).
|
|
2125
|
+
*
|
|
2126
|
+
* The owning root is derived from the host parent passed to the removal hook,
|
|
2127
|
+
* not a module-level global, so instances with separate stdout streams don't
|
|
2128
|
+
* clobber each other's pointers.
|
|
2129
|
+
*/
|
|
2130
|
+
const clearStaticNodeIfContained = (rootNode, removeNode) => {
|
|
2131
|
+
if (!rootNode?.staticNode) return;
|
|
2132
|
+
let current = rootNode.staticNode;
|
|
2133
|
+
while (current) {
|
|
2134
|
+
if (current === removeNode) {
|
|
2135
|
+
rootNode.staticNode = void 0;
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
current = current.parentNode;
|
|
2139
|
+
}
|
|
2140
|
+
};
|
|
2141
|
+
let currentUpdatePriority = NoEventPriority;
|
|
2142
|
+
async function loadPackageJson() {
|
|
2143
|
+
const content = (await import("node:fs")).readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
2144
|
+
const parsedContent = JSON.parse(content);
|
|
2145
|
+
return {
|
|
2146
|
+
name: parsedContent?.name,
|
|
2147
|
+
version: parsedContent?.version
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
let packageInfo = {
|
|
2151
|
+
name: "ink",
|
|
2152
|
+
version
|
|
2153
|
+
};
|
|
2154
|
+
if (process.env["DEV"] === "true") try {
|
|
2155
|
+
const loaded = await loadPackageJson();
|
|
2156
|
+
packageInfo = {
|
|
2157
|
+
name: loaded.name || packageInfo.name,
|
|
2158
|
+
version: loaded.version || packageInfo.version
|
|
2159
|
+
};
|
|
2160
|
+
} catch (error) {
|
|
2161
|
+
console.warn("Failed to load package.json in development mode. Falling back to default renderer metadata.", error);
|
|
2162
|
+
}
|
|
2163
|
+
const reconciler = createReconciler({
|
|
2164
|
+
getRootHostContext: () => ({ isInsideText: false }),
|
|
2165
|
+
prepareForCommit: () => null,
|
|
2166
|
+
preparePortalMount: () => null,
|
|
2167
|
+
clearContainer: () => false,
|
|
2168
|
+
resetAfterCommit(rootNode) {
|
|
2169
|
+
if (typeof rootNode.onComputeLayout === "function") rootNode.onComputeLayout();
|
|
2170
|
+
emitLayoutListeners(rootNode);
|
|
2171
|
+
if (rootNode.staticNode !== rootNode.previousStaticNode) {
|
|
2172
|
+
rootNode.previousStaticNode = rootNode.staticNode;
|
|
2173
|
+
if (typeof rootNode.onStaticChange === "function") rootNode.onStaticChange();
|
|
2174
|
+
}
|
|
2175
|
+
if (rootNode.isStaticDirty) {
|
|
2176
|
+
rootNode.isStaticDirty = false;
|
|
2177
|
+
if (typeof rootNode.onImmediateRender === "function") rootNode.onImmediateRender();
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
if (typeof rootNode.onRender === "function") rootNode.onRender();
|
|
2181
|
+
},
|
|
2182
|
+
getChildHostContext(parentHostContext, type) {
|
|
2183
|
+
const previousIsInsideText = parentHostContext.isInsideText;
|
|
2184
|
+
const isInsideText = type === "ink-text" || type === "ink-virtual-text";
|
|
2185
|
+
if (previousIsInsideText === isInsideText) return parentHostContext;
|
|
2186
|
+
return { isInsideText };
|
|
2187
|
+
},
|
|
2188
|
+
shouldSetTextContent: () => false,
|
|
2189
|
+
createInstance(originalType, newProps, rootNode, hostContext) {
|
|
2190
|
+
if (hostContext.isInsideText && originalType === "ink-box") throw new Error(`<Box> can’t be nested inside <Text> component`);
|
|
2191
|
+
const type = originalType === "ink-text" && hostContext.isInsideText ? "ink-virtual-text" : originalType;
|
|
2192
|
+
const node = createNode(type);
|
|
2193
|
+
for (const [key, value] of Object.entries(newProps)) {
|
|
2194
|
+
if (key === "children") continue;
|
|
2195
|
+
if (key === "style") {
|
|
2196
|
+
setStyle(node, value);
|
|
2197
|
+
if (node.yogaNode) styles(node.yogaNode, value);
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
if (key === "internal_transform") {
|
|
2201
|
+
node.internal_transform = value;
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
if (key === "internal_static") {
|
|
2205
|
+
node.internal_static = true;
|
|
2206
|
+
rootNode.isStaticDirty = true;
|
|
2207
|
+
rootNode.staticNode = node;
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
setAttribute(node, key, value);
|
|
2211
|
+
}
|
|
2212
|
+
return node;
|
|
2213
|
+
},
|
|
2214
|
+
createTextInstance(text, _root, hostContext) {
|
|
2215
|
+
if (!hostContext.isInsideText) throw new Error(`Text string "${text}" must be rendered inside <Text> component`);
|
|
2216
|
+
return createTextNode(text);
|
|
2217
|
+
},
|
|
2218
|
+
resetTextContent() {},
|
|
2219
|
+
hideTextInstance(node) {
|
|
2220
|
+
setTextNodeValue(node, "");
|
|
2221
|
+
},
|
|
2222
|
+
unhideTextInstance(node, text) {
|
|
2223
|
+
setTextNodeValue(node, text);
|
|
2224
|
+
},
|
|
2225
|
+
getPublicInstance: (instance) => instance,
|
|
2226
|
+
hideInstance(node) {
|
|
2227
|
+
node.yogaNode?.setDisplay(Yoga.DISPLAY_NONE);
|
|
2228
|
+
},
|
|
2229
|
+
unhideInstance(node) {
|
|
2230
|
+
node.yogaNode?.setDisplay(Yoga.DISPLAY_FLEX);
|
|
2231
|
+
},
|
|
2232
|
+
appendInitialChild: appendChildNode,
|
|
2233
|
+
appendChild: appendChildNode,
|
|
2234
|
+
insertBefore: insertBeforeNode,
|
|
2235
|
+
finalizeInitialChildren() {
|
|
2236
|
+
return false;
|
|
2237
|
+
},
|
|
2238
|
+
isPrimaryRenderer: true,
|
|
2239
|
+
supportsMutation: true,
|
|
2240
|
+
supportsPersistence: false,
|
|
2241
|
+
supportsHydration: false,
|
|
2242
|
+
supportsMicrotasks: true,
|
|
2243
|
+
scheduleMicrotask: queueMicrotask,
|
|
2244
|
+
scheduleCallback: Scheduler.unstable_scheduleCallback,
|
|
2245
|
+
cancelCallback: Scheduler.unstable_cancelCallback,
|
|
2246
|
+
shouldYield: Scheduler.unstable_shouldYield,
|
|
2247
|
+
now: Scheduler.unstable_now,
|
|
2248
|
+
scheduleTimeout: setTimeout,
|
|
2249
|
+
cancelTimeout: clearTimeout,
|
|
2250
|
+
noTimeout: -1,
|
|
2251
|
+
beforeActiveInstanceBlur() {},
|
|
2252
|
+
afterActiveInstanceBlur() {},
|
|
2253
|
+
detachDeletedInstance() {},
|
|
2254
|
+
getInstanceFromNode: () => null,
|
|
2255
|
+
prepareScopeUpdate() {},
|
|
2256
|
+
getInstanceFromScope: () => null,
|
|
2257
|
+
appendChildToContainer: appendChildNode,
|
|
2258
|
+
insertInContainerBefore: insertBeforeNode,
|
|
2259
|
+
removeChildFromContainer(node, removeNode) {
|
|
2260
|
+
clearStaticNodeIfContained(findRootNode$1(node), removeNode);
|
|
2261
|
+
removeChildNode(node, removeNode);
|
|
2262
|
+
detachYogaSubtree(removeNode);
|
|
2263
|
+
},
|
|
2264
|
+
commitUpdate(node, _type, oldProps, newProps) {
|
|
2265
|
+
if (node.internal_static) {
|
|
2266
|
+
const rootNode = findRootNode$1(node);
|
|
2267
|
+
if (rootNode) rootNode.isStaticDirty = true;
|
|
2268
|
+
}
|
|
2269
|
+
const props = diff(oldProps, newProps);
|
|
2270
|
+
const style = diff(oldProps["style"], newProps["style"]);
|
|
2271
|
+
if (!props && !style) return;
|
|
2272
|
+
if (props) for (const [key, value] of Object.entries(props)) {
|
|
2273
|
+
if (key === "style") {
|
|
2274
|
+
setStyle(node, value);
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
if (key === "internal_transform") {
|
|
2278
|
+
node.internal_transform = value;
|
|
2279
|
+
continue;
|
|
2280
|
+
}
|
|
2281
|
+
if (key === "internal_static") {
|
|
2282
|
+
node.internal_static = true;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
setAttribute(node, key, value);
|
|
2286
|
+
}
|
|
2287
|
+
if (style && node.yogaNode) styles(node.yogaNode, style, newProps["style"] ?? {});
|
|
2288
|
+
},
|
|
2289
|
+
commitTextUpdate(node, _oldText, newText) {
|
|
2290
|
+
setTextNodeValue(node, newText);
|
|
2291
|
+
},
|
|
2292
|
+
removeChild(node, removeNode) {
|
|
2293
|
+
clearStaticNodeIfContained(findRootNode$1(node), removeNode);
|
|
2294
|
+
removeChildNode(node, removeNode);
|
|
2295
|
+
detachYogaSubtree(removeNode);
|
|
2296
|
+
},
|
|
2297
|
+
setCurrentUpdatePriority(newPriority) {
|
|
2298
|
+
currentUpdatePriority = newPriority;
|
|
2299
|
+
},
|
|
2300
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
2301
|
+
resolveUpdatePriority() {
|
|
2302
|
+
if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
|
|
2303
|
+
return DefaultEventPriority;
|
|
2304
|
+
},
|
|
2305
|
+
maySuspendCommit() {
|
|
2306
|
+
return true;
|
|
2307
|
+
},
|
|
2308
|
+
NotPendingTransition: void 0,
|
|
2309
|
+
HostTransitionContext: createContext(null),
|
|
2310
|
+
resetFormInstance() {},
|
|
2311
|
+
requestPostPaintCallback() {},
|
|
2312
|
+
shouldAttemptEagerTransition() {
|
|
2313
|
+
return false;
|
|
2314
|
+
},
|
|
2315
|
+
trackSchedulerEvent() {},
|
|
2316
|
+
resolveEventType() {
|
|
2317
|
+
return null;
|
|
2318
|
+
},
|
|
2319
|
+
resolveEventTimeStamp() {
|
|
2320
|
+
return -1.1;
|
|
2321
|
+
},
|
|
2322
|
+
preloadInstance() {
|
|
2323
|
+
return true;
|
|
2324
|
+
},
|
|
2325
|
+
startSuspendingCommit() {},
|
|
2326
|
+
suspendInstance() {},
|
|
2327
|
+
waitForCommitToBeReady() {
|
|
2328
|
+
return null;
|
|
2329
|
+
},
|
|
2330
|
+
rendererPackageName: packageInfo.name,
|
|
2331
|
+
rendererVersion: packageInfo.version
|
|
2332
|
+
});
|
|
2333
|
+
//#endregion
|
|
2334
|
+
//#region src/output.ts
|
|
2335
|
+
var OutputCaches = class {
|
|
2336
|
+
widths = /* @__PURE__ */ new Map();
|
|
2337
|
+
blockWidths = /* @__PURE__ */ new Map();
|
|
2338
|
+
styledChars = /* @__PURE__ */ new Map();
|
|
2339
|
+
getStyledChars(line) {
|
|
2340
|
+
let cached = this.styledChars.get(line);
|
|
2341
|
+
if (cached === void 0) {
|
|
2342
|
+
cached = styledCharsFromTokens(tokenize(line));
|
|
2343
|
+
this.styledChars.set(line, cached);
|
|
2344
|
+
}
|
|
2345
|
+
return cached;
|
|
2346
|
+
}
|
|
2347
|
+
getStringWidth(text) {
|
|
2348
|
+
let cached = this.widths.get(text);
|
|
2349
|
+
if (cached === void 0) {
|
|
2350
|
+
cached = stringWidth(text);
|
|
2351
|
+
this.widths.set(text, cached);
|
|
2352
|
+
}
|
|
2353
|
+
return cached;
|
|
2354
|
+
}
|
|
2355
|
+
getWidestLine(text) {
|
|
2356
|
+
let cached = this.blockWidths.get(text);
|
|
2357
|
+
if (cached === void 0) {
|
|
2358
|
+
let lineWidth = 0;
|
|
2359
|
+
for (const line of text.split("\n")) lineWidth = Math.max(lineWidth, this.getStringWidth(line));
|
|
2360
|
+
cached = lineWidth;
|
|
2361
|
+
this.blockWidths.set(text, cached);
|
|
2362
|
+
}
|
|
2363
|
+
return cached;
|
|
2364
|
+
}
|
|
2365
|
+
};
|
|
2366
|
+
var Output = class {
|
|
2367
|
+
width;
|
|
2368
|
+
height;
|
|
2369
|
+
operations = [];
|
|
2370
|
+
caches = new OutputCaches();
|
|
2371
|
+
constructor(options) {
|
|
2372
|
+
const { width, height } = options;
|
|
2373
|
+
this.width = width;
|
|
2374
|
+
this.height = height;
|
|
2375
|
+
}
|
|
2376
|
+
write(x, y, text, options) {
|
|
2377
|
+
const { transformers } = options;
|
|
2378
|
+
if (!text) return;
|
|
2379
|
+
this.operations.push({
|
|
2380
|
+
type: "write",
|
|
2381
|
+
x,
|
|
2382
|
+
y,
|
|
2383
|
+
text,
|
|
2384
|
+
transformers
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
clip(clip) {
|
|
2388
|
+
this.operations.push({
|
|
2389
|
+
type: "clip",
|
|
2390
|
+
clip
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
unclip() {
|
|
2394
|
+
this.operations.push({ type: "unclip" });
|
|
2395
|
+
}
|
|
2396
|
+
get() {
|
|
2397
|
+
const output = [];
|
|
2398
|
+
for (let y = 0; y < this.height; y++) {
|
|
2399
|
+
const row = [];
|
|
2400
|
+
for (let x = 0; x < this.width; x++) row.push({
|
|
2401
|
+
type: "char",
|
|
2402
|
+
value: " ",
|
|
2403
|
+
fullWidth: false,
|
|
2404
|
+
styles: []
|
|
2405
|
+
});
|
|
2406
|
+
output.push(row);
|
|
2407
|
+
}
|
|
2408
|
+
const clips = [];
|
|
2409
|
+
for (const operation of this.operations) {
|
|
2410
|
+
if (operation.type === "clip") clips.push(operation.clip);
|
|
2411
|
+
if (operation.type === "unclip") clips.pop();
|
|
2412
|
+
if (operation.type === "write") {
|
|
2413
|
+
const { text, transformers } = operation;
|
|
2414
|
+
let { x, y } = operation;
|
|
2415
|
+
let lines = text.split("\n");
|
|
2416
|
+
const clip = clips.at(-1);
|
|
2417
|
+
if (clip) {
|
|
2418
|
+
const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
|
|
2419
|
+
const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
|
|
2420
|
+
if (clipHorizontally) {
|
|
2421
|
+
const width = this.caches.getWidestLine(text);
|
|
2422
|
+
if (x + width < clip.x1 || x > clip.x2) continue;
|
|
2423
|
+
}
|
|
2424
|
+
if (clipVertically) {
|
|
2425
|
+
const height = lines.length;
|
|
2426
|
+
if (y + height < clip.y1 || y > clip.y2) continue;
|
|
2427
|
+
}
|
|
2428
|
+
if (clipHorizontally) {
|
|
2429
|
+
lines = lines.map((line) => {
|
|
2430
|
+
const from = x < clip.x1 ? clip.x1 - x : 0;
|
|
2431
|
+
const width = this.caches.getStringWidth(line);
|
|
2432
|
+
const to = x + width > clip.x2 ? clip.x2 - x : width;
|
|
2433
|
+
return sliceAnsi(line, from, to);
|
|
2434
|
+
});
|
|
2435
|
+
if (x < clip.x1) x = clip.x1;
|
|
2436
|
+
}
|
|
2437
|
+
if (clipVertically) {
|
|
2438
|
+
const from = y < clip.y1 ? clip.y1 - y : 0;
|
|
2439
|
+
const height = lines.length;
|
|
2440
|
+
const to = y + height > clip.y2 ? clip.y2 - y : height;
|
|
2441
|
+
lines = lines.slice(from, to);
|
|
2442
|
+
if (y < clip.y1) y = clip.y1;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
let offsetY = 0;
|
|
2446
|
+
for (let [index, line] of lines.entries()) {
|
|
2447
|
+
const currentLine = output[y + offsetY];
|
|
2448
|
+
if (!currentLine) continue;
|
|
2449
|
+
for (const transformer of transformers) line = transformer(line, index);
|
|
2450
|
+
const characters = this.caches.getStyledChars(line);
|
|
2451
|
+
let offsetX = x;
|
|
2452
|
+
if (characters.length === 0) {
|
|
2453
|
+
offsetY++;
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
const spaceCell = {
|
|
2457
|
+
type: "char",
|
|
2458
|
+
value: " ",
|
|
2459
|
+
fullWidth: false,
|
|
2460
|
+
styles: []
|
|
2461
|
+
};
|
|
2462
|
+
if (currentLine[offsetX]?.value === "" && offsetX > 0 && this.caches.getStringWidth(currentLine[offsetX - 1]?.value ?? "") > 1) currentLine[offsetX - 1] = spaceCell;
|
|
2463
|
+
for (const character of characters) {
|
|
2464
|
+
currentLine[offsetX] = character;
|
|
2465
|
+
const characterWidth = Math.max(1, this.caches.getStringWidth(character.value));
|
|
2466
|
+
if (characterWidth > 1) for (let columnOffset = 1; columnOffset < characterWidth; columnOffset++) currentLine[offsetX + columnOffset] = {
|
|
2467
|
+
type: "char",
|
|
2468
|
+
value: "",
|
|
2469
|
+
fullWidth: false,
|
|
2470
|
+
styles: character.styles
|
|
2471
|
+
};
|
|
2472
|
+
offsetX += characterWidth;
|
|
2473
|
+
}
|
|
2474
|
+
if (currentLine[offsetX]?.value === "") currentLine[offsetX] = spaceCell;
|
|
2475
|
+
offsetY++;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
return {
|
|
2480
|
+
output: output.map((line) => {
|
|
2481
|
+
const lineWithoutEmptyItems = line.filter((item) => item !== void 0);
|
|
2482
|
+
return styledCharsToString(lineWithoutEmptyItems).trimEnd();
|
|
2483
|
+
}).join("\n"),
|
|
2484
|
+
height: output.length
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
//#endregion
|
|
2489
|
+
//#region src/get-max-width.ts
|
|
2490
|
+
const getMaxWidth = (yogaNode) => {
|
|
2491
|
+
return yogaNode.getComputedWidth() - yogaNode.getComputedPadding(Yoga.EDGE_LEFT) - yogaNode.getComputedPadding(Yoga.EDGE_RIGHT) - yogaNode.getComputedBorder(Yoga.EDGE_LEFT) - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT);
|
|
2492
|
+
};
|
|
2493
|
+
//#endregion
|
|
2494
|
+
//#region src/indent-string.ts
|
|
2495
|
+
const indentString = (string, count = 1, options = {}) => {
|
|
2496
|
+
const { indent = " ", includeEmptyLines = false } = options;
|
|
2497
|
+
if (count === 0) return string;
|
|
2498
|
+
const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
|
|
2499
|
+
return string.replace(regex, indent.repeat(count));
|
|
2500
|
+
};
|
|
2501
|
+
//#endregion
|
|
2502
|
+
//#region src/render-background.ts
|
|
2503
|
+
const renderBackground = (x, y, node, output) => {
|
|
2504
|
+
if (!node.style.backgroundColor) return;
|
|
2505
|
+
const width = node.yogaNode.getComputedWidth();
|
|
2506
|
+
const height = node.yogaNode.getComputedHeight();
|
|
2507
|
+
const leftBorderWidth = node.style.borderStyle && node.style.borderLeft !== false ? 1 : 0;
|
|
2508
|
+
const rightBorderWidth = node.style.borderStyle && node.style.borderRight !== false ? 1 : 0;
|
|
2509
|
+
const topBorderHeight = node.style.borderStyle && node.style.borderTop !== false ? 1 : 0;
|
|
2510
|
+
const bottomBorderHeight = node.style.borderStyle && node.style.borderBottom !== false ? 1 : 0;
|
|
2511
|
+
const contentWidth = width - leftBorderWidth - rightBorderWidth;
|
|
2512
|
+
const contentHeight = height - topBorderHeight - bottomBorderHeight;
|
|
2513
|
+
if (!(contentWidth > 0 && contentHeight > 0)) return;
|
|
2514
|
+
const backgroundLine = colorize(" ".repeat(contentWidth), node.style.backgroundColor, "background");
|
|
2515
|
+
for (let row = 0; row < contentHeight; row++) output.write(x + leftBorderWidth, y + topBorderHeight + row, backgroundLine, { transformers: [] });
|
|
2516
|
+
};
|
|
2517
|
+
//#endregion
|
|
2518
|
+
//#region src/boxes.ts
|
|
2519
|
+
const boxes = {
|
|
2520
|
+
single: {
|
|
2521
|
+
topLeft: "┌",
|
|
2522
|
+
top: "─",
|
|
2523
|
+
topRight: "┐",
|
|
2524
|
+
right: "│",
|
|
2525
|
+
bottomRight: "┘",
|
|
2526
|
+
bottom: "─",
|
|
2527
|
+
bottomLeft: "└",
|
|
2528
|
+
left: "│"
|
|
2529
|
+
},
|
|
2530
|
+
double: {
|
|
2531
|
+
topLeft: "╔",
|
|
2532
|
+
top: "═",
|
|
2533
|
+
topRight: "╗",
|
|
2534
|
+
right: "║",
|
|
2535
|
+
bottomRight: "╝",
|
|
2536
|
+
bottom: "═",
|
|
2537
|
+
bottomLeft: "╚",
|
|
2538
|
+
left: "║"
|
|
2539
|
+
},
|
|
2540
|
+
round: {
|
|
2541
|
+
topLeft: "╭",
|
|
2542
|
+
top: "─",
|
|
2543
|
+
topRight: "╮",
|
|
2544
|
+
right: "│",
|
|
2545
|
+
bottomRight: "╯",
|
|
2546
|
+
bottom: "─",
|
|
2547
|
+
bottomLeft: "╰",
|
|
2548
|
+
left: "│"
|
|
2549
|
+
},
|
|
2550
|
+
bold: {
|
|
2551
|
+
topLeft: "┏",
|
|
2552
|
+
top: "━",
|
|
2553
|
+
topRight: "┓",
|
|
2554
|
+
right: "┃",
|
|
2555
|
+
bottomRight: "┛",
|
|
2556
|
+
bottom: "━",
|
|
2557
|
+
bottomLeft: "┗",
|
|
2558
|
+
left: "┃"
|
|
2559
|
+
},
|
|
2560
|
+
singleDouble: {
|
|
2561
|
+
topLeft: "╓",
|
|
2562
|
+
top: "─",
|
|
2563
|
+
topRight: "╖",
|
|
2564
|
+
right: "║",
|
|
2565
|
+
bottomRight: "╜",
|
|
2566
|
+
bottom: "─",
|
|
2567
|
+
bottomLeft: "╙",
|
|
2568
|
+
left: "║"
|
|
2569
|
+
},
|
|
2570
|
+
doubleSingle: {
|
|
2571
|
+
topLeft: "╒",
|
|
2572
|
+
top: "═",
|
|
2573
|
+
topRight: "╕",
|
|
2574
|
+
right: "│",
|
|
2575
|
+
bottomRight: "╛",
|
|
2576
|
+
bottom: "═",
|
|
2577
|
+
bottomLeft: "╘",
|
|
2578
|
+
left: "│"
|
|
2579
|
+
},
|
|
2580
|
+
classic: {
|
|
2581
|
+
topLeft: "+",
|
|
2582
|
+
top: "-",
|
|
2583
|
+
topRight: "+",
|
|
2584
|
+
right: "|",
|
|
2585
|
+
bottomRight: "+",
|
|
2586
|
+
bottom: "-",
|
|
2587
|
+
bottomLeft: "+",
|
|
2588
|
+
left: "|"
|
|
2589
|
+
},
|
|
2590
|
+
arrow: {
|
|
2591
|
+
topLeft: "↘",
|
|
2592
|
+
top: "↓",
|
|
2593
|
+
topRight: "↙",
|
|
2594
|
+
right: "←",
|
|
2595
|
+
bottomRight: "↖",
|
|
2596
|
+
bottom: "↑",
|
|
2597
|
+
bottomLeft: "↗",
|
|
2598
|
+
left: "→"
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
//#endregion
|
|
2602
|
+
//#region src/render-border.ts
|
|
2603
|
+
const stylePiece = (segment, fg, bg, dim) => {
|
|
2604
|
+
let styled = colorize(segment, fg, "foreground");
|
|
2605
|
+
styled = colorize(styled, bg, "background");
|
|
2606
|
+
if (dim) styled = chalk.dim(styled);
|
|
2607
|
+
return styled;
|
|
2608
|
+
};
|
|
2609
|
+
const renderBorder = (x, y, node, output) => {
|
|
2610
|
+
if (node.style.borderStyle) {
|
|
2611
|
+
const width = node.yogaNode.getComputedWidth();
|
|
2612
|
+
const height = node.yogaNode.getComputedHeight();
|
|
2613
|
+
const box = typeof node.style.borderStyle === "string" ? boxes[node.style.borderStyle] : node.style.borderStyle;
|
|
2614
|
+
const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
|
|
2615
|
+
const bottomBorderColor = node.style.borderBottomColor ?? node.style.borderColor;
|
|
2616
|
+
const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor;
|
|
2617
|
+
const rightBorderColor = node.style.borderRightColor ?? node.style.borderColor;
|
|
2618
|
+
const topBorderBackgroundColor = node.style.borderTopBackgroundColor ?? node.style.borderBackgroundColor;
|
|
2619
|
+
const bottomBorderBackgroundColor = node.style.borderBottomBackgroundColor ?? node.style.borderBackgroundColor;
|
|
2620
|
+
const leftBorderBackgroundColor = node.style.borderLeftBackgroundColor ?? node.style.borderBackgroundColor;
|
|
2621
|
+
const rightBorderBackgroundColor = node.style.borderRightBackgroundColor ?? node.style.borderBackgroundColor;
|
|
2622
|
+
const dimTopBorderColor = node.style.borderTopDimColor ?? node.style.borderDimColor;
|
|
2623
|
+
const dimBottomBorderColor = node.style.borderBottomDimColor ?? node.style.borderDimColor;
|
|
2624
|
+
const dimLeftBorderColor = node.style.borderLeftDimColor ?? node.style.borderDimColor;
|
|
2625
|
+
const dimRightBorderColor = node.style.borderRightDimColor ?? node.style.borderDimColor;
|
|
2626
|
+
const showTopBorder = node.style.borderTop !== false;
|
|
2627
|
+
const showBottomBorder = node.style.borderBottom !== false;
|
|
2628
|
+
const showLeftBorder = node.style.borderLeft !== false;
|
|
2629
|
+
const showRightBorder = node.style.borderRight !== false;
|
|
2630
|
+
const contentWidth = width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0);
|
|
2631
|
+
let topBorder = showTopBorder ? (showLeftBorder ? box.topLeft : "") + box.top.repeat(contentWidth) + (showRightBorder ? box.topRight : "") : void 0;
|
|
2632
|
+
topBorder &&= stylePiece(topBorder, topBorderColor, topBorderBackgroundColor, dimTopBorderColor);
|
|
2633
|
+
let verticalBorderHeight = height;
|
|
2634
|
+
if (showTopBorder) verticalBorderHeight -= 1;
|
|
2635
|
+
if (showBottomBorder) verticalBorderHeight -= 1;
|
|
2636
|
+
let leftBorder = "";
|
|
2637
|
+
if (showLeftBorder) leftBorder = (stylePiece(box.left, leftBorderColor, leftBorderBackgroundColor, dimLeftBorderColor) + "\n").repeat(verticalBorderHeight);
|
|
2638
|
+
let rightBorder = "";
|
|
2639
|
+
if (showRightBorder) rightBorder = (stylePiece(box.right, rightBorderColor, rightBorderBackgroundColor, dimRightBorderColor) + "\n").repeat(verticalBorderHeight);
|
|
2640
|
+
let bottomBorder = showBottomBorder ? (showLeftBorder ? box.bottomLeft : "") + box.bottom.repeat(contentWidth) + (showRightBorder ? box.bottomRight : "") : void 0;
|
|
2641
|
+
bottomBorder &&= stylePiece(bottomBorder, bottomBorderColor, bottomBorderBackgroundColor, dimBottomBorderColor);
|
|
2642
|
+
const offsetY = showTopBorder ? 1 : 0;
|
|
2643
|
+
if (topBorder) output.write(x, y, topBorder, { transformers: [] });
|
|
2644
|
+
if (leftBorder) output.write(x, y + offsetY, leftBorder, { transformers: [] });
|
|
2645
|
+
if (rightBorder) output.write(x + width - 1, y + offsetY, rightBorder, { transformers: [] });
|
|
2646
|
+
if (bottomBorder) output.write(x, y + height - 1, bottomBorder, { transformers: [] });
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
//#endregion
|
|
2650
|
+
//#region src/render-node-to-output.ts
|
|
2651
|
+
const applyPaddingToText = (node, text) => {
|
|
2652
|
+
const yogaNode = node.childNodes[0]?.yogaNode;
|
|
2653
|
+
if (yogaNode) {
|
|
2654
|
+
const offsetX = yogaNode.getComputedLeft();
|
|
2655
|
+
const offsetY = yogaNode.getComputedTop();
|
|
2656
|
+
text = "\n".repeat(offsetY) + indentString(text, offsetX);
|
|
2657
|
+
}
|
|
2658
|
+
return text;
|
|
2659
|
+
};
|
|
2660
|
+
const renderNodeToScreenReaderOutput = (node, options = {}) => {
|
|
2661
|
+
if (options.skipStaticElements && node.internal_static) return "";
|
|
2662
|
+
if (node.yogaNode?.getDisplay() === Yoga.DISPLAY_NONE) return "";
|
|
2663
|
+
let output = "";
|
|
2664
|
+
if (node.nodeName === "ink-text") output = squashTextNodes(node);
|
|
2665
|
+
else if (node.nodeName === "ink-box" || node.nodeName === "ink-root") {
|
|
2666
|
+
const separator = node.style.flexDirection === "row" || node.style.flexDirection === "row-reverse" ? " " : "\n";
|
|
2667
|
+
output = (node.style.flexDirection === "row-reverse" || node.style.flexDirection === "column-reverse" ? [...node.childNodes].reverse() : [...node.childNodes]).map((childNode) => {
|
|
2668
|
+
return renderNodeToScreenReaderOutput(childNode, {
|
|
2669
|
+
parentRole: node.internal_accessibility?.role,
|
|
2670
|
+
skipStaticElements: options.skipStaticElements
|
|
2671
|
+
});
|
|
2672
|
+
}).filter(Boolean).join(separator);
|
|
2673
|
+
}
|
|
2674
|
+
if (node.internal_accessibility) {
|
|
2675
|
+
const { role, state } = node.internal_accessibility;
|
|
2676
|
+
if (state) {
|
|
2677
|
+
const stateDescription = Object.keys(state).filter((key) => state[key]).join(", ");
|
|
2678
|
+
if (stateDescription) output = `(${stateDescription}) ${output}`;
|
|
2679
|
+
}
|
|
2680
|
+
if (role && role !== options.parentRole) output = `${role}: ${output}`;
|
|
2681
|
+
}
|
|
2682
|
+
return output;
|
|
2683
|
+
};
|
|
2684
|
+
const renderNodeToOutput = (node, output, options) => {
|
|
2685
|
+
const { offsetX = 0, offsetY = 0, transformers = [], skipStaticElements } = options;
|
|
2686
|
+
if (skipStaticElements && node.internal_static) return;
|
|
2687
|
+
const { yogaNode } = node;
|
|
2688
|
+
if (yogaNode) {
|
|
2689
|
+
if (yogaNode.getDisplay() === Yoga.DISPLAY_NONE) return;
|
|
2690
|
+
const x = offsetX + yogaNode.getComputedLeft();
|
|
2691
|
+
const y = offsetY + yogaNode.getComputedTop();
|
|
2692
|
+
let newTransformers = transformers;
|
|
2693
|
+
if (typeof node.internal_transform === "function") newTransformers = [node.internal_transform, ...transformers];
|
|
2694
|
+
if (node.nodeName === "ink-text") {
|
|
2695
|
+
let text = squashTextNodes(node);
|
|
2696
|
+
if (text.length > 0) {
|
|
2697
|
+
const currentWidth = widestLine(text);
|
|
2698
|
+
const maxWidth = getMaxWidth(yogaNode);
|
|
2699
|
+
if (currentWidth > maxWidth) {
|
|
2700
|
+
const textWrap = node.style.textWrap ?? "wrap";
|
|
2701
|
+
text = wrapText(text, maxWidth, textWrap);
|
|
2702
|
+
}
|
|
2703
|
+
text = applyPaddingToText(node, text);
|
|
2704
|
+
output.write(x, y, text, { transformers: newTransformers });
|
|
2705
|
+
}
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
let clipped = false;
|
|
2709
|
+
if (node.nodeName === "ink-box") {
|
|
2710
|
+
renderBackground(x, y, node, output);
|
|
2711
|
+
renderBorder(x, y, node, output);
|
|
2712
|
+
const clipHorizontally = node.style.overflowX === "hidden" || node.style.overflow === "hidden";
|
|
2713
|
+
const clipVertically = node.style.overflowY === "hidden" || node.style.overflow === "hidden";
|
|
2714
|
+
if (clipHorizontally || clipVertically) {
|
|
2715
|
+
const x1 = clipHorizontally ? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT) : void 0;
|
|
2716
|
+
const x2 = clipHorizontally ? x + yogaNode.getComputedWidth() - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT) : void 0;
|
|
2717
|
+
const y1 = clipVertically ? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP) : void 0;
|
|
2718
|
+
const y2 = clipVertically ? y + yogaNode.getComputedHeight() - yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM) : void 0;
|
|
2719
|
+
output.clip({
|
|
2720
|
+
x1,
|
|
2721
|
+
x2,
|
|
2722
|
+
y1,
|
|
2723
|
+
y2
|
|
2724
|
+
});
|
|
2725
|
+
clipped = true;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
if (node.nodeName === "ink-root" || node.nodeName === "ink-box") {
|
|
2729
|
+
for (const childNode of node.childNodes) renderNodeToOutput(childNode, output, {
|
|
2730
|
+
offsetX: x,
|
|
2731
|
+
offsetY: y,
|
|
2732
|
+
transformers: newTransformers,
|
|
2733
|
+
skipStaticElements
|
|
2734
|
+
});
|
|
2735
|
+
if (clipped) output.unclip();
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
//#endregion
|
|
2740
|
+
//#region src/renderer.ts
|
|
2741
|
+
const renderer = (node, isScreenReaderEnabled) => {
|
|
2742
|
+
if (node.yogaNode) {
|
|
2743
|
+
if (isScreenReaderEnabled) {
|
|
2744
|
+
const output = renderNodeToScreenReaderOutput(node, { skipStaticElements: true });
|
|
2745
|
+
const outputHeight = output === "" ? 0 : output.split("\n").length;
|
|
2746
|
+
let staticOutput = "";
|
|
2747
|
+
if (node.staticNode) staticOutput = renderNodeToScreenReaderOutput(node.staticNode, { skipStaticElements: false });
|
|
2748
|
+
return {
|
|
2749
|
+
output,
|
|
2750
|
+
outputHeight,
|
|
2751
|
+
staticOutput: staticOutput ? `${staticOutput}\n` : ""
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
const output = new Output({
|
|
2755
|
+
width: node.yogaNode.getComputedWidth(),
|
|
2756
|
+
height: node.yogaNode.getComputedHeight()
|
|
2757
|
+
});
|
|
2758
|
+
renderNodeToOutput(node, output, { skipStaticElements: true });
|
|
2759
|
+
let staticOutput;
|
|
2760
|
+
if (node.staticNode?.yogaNode) {
|
|
2761
|
+
staticOutput = new Output({
|
|
2762
|
+
width: node.staticNode.yogaNode.getComputedWidth(),
|
|
2763
|
+
height: node.staticNode.yogaNode.getComputedHeight()
|
|
2764
|
+
});
|
|
2765
|
+
renderNodeToOutput(node.staticNode, staticOutput, { skipStaticElements: false });
|
|
2766
|
+
}
|
|
2767
|
+
const { output: generatedOutput, height: outputHeight } = output.get();
|
|
2768
|
+
return {
|
|
2769
|
+
output: generatedOutput,
|
|
2770
|
+
outputHeight,
|
|
2771
|
+
staticOutput: staticOutput ? `${staticOutput.get().output}\n` : ""
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
return {
|
|
2775
|
+
output: "",
|
|
2776
|
+
outputHeight: 0,
|
|
2777
|
+
staticOutput: ""
|
|
2778
|
+
};
|
|
2779
|
+
};
|
|
2780
|
+
//#endregion
|
|
2781
|
+
//#region src/throttle.ts
|
|
2782
|
+
/**
|
|
2783
|
+
Invokes `fn` at most once per `wait` milliseconds.
|
|
2784
|
+
|
|
2785
|
+
The first call in a window fires immediately (leading edge). Calls made while
|
|
2786
|
+
the window is open are coalesced into a single trailing invocation with the
|
|
2787
|
+
latest arguments. Matches the lodash semantics Ink relied on: a single call
|
|
2788
|
+
produces only a leading invocation, no trailing one.
|
|
2789
|
+
*/
|
|
2790
|
+
const throttle = (fn, wait = 0) => {
|
|
2791
|
+
let timer;
|
|
2792
|
+
let pendingArgs;
|
|
2793
|
+
const invokePending = () => {
|
|
2794
|
+
const args = pendingArgs;
|
|
2795
|
+
pendingArgs = void 0;
|
|
2796
|
+
fn(...args);
|
|
2797
|
+
};
|
|
2798
|
+
const onTimer = () => {
|
|
2799
|
+
if (pendingArgs) {
|
|
2800
|
+
invokePending();
|
|
2801
|
+
timer = setTimeout(onTimer, wait);
|
|
2802
|
+
} else timer = void 0;
|
|
2803
|
+
};
|
|
2804
|
+
const throttled = (...args) => {
|
|
2805
|
+
if (timer) {
|
|
2806
|
+
pendingArgs = args;
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
fn(...args);
|
|
2810
|
+
timer = setTimeout(onTimer, wait);
|
|
2811
|
+
};
|
|
2812
|
+
throttled.cancel = () => {
|
|
2813
|
+
if (timer) {
|
|
2814
|
+
clearTimeout(timer);
|
|
2815
|
+
timer = void 0;
|
|
2816
|
+
}
|
|
2817
|
+
pendingArgs = void 0;
|
|
2818
|
+
};
|
|
2819
|
+
throttled.flush = () => {
|
|
2820
|
+
if (!pendingArgs) return;
|
|
2821
|
+
if (timer) {
|
|
2822
|
+
clearTimeout(timer);
|
|
2823
|
+
timer = void 0;
|
|
2824
|
+
}
|
|
2825
|
+
invokePending();
|
|
2826
|
+
};
|
|
2827
|
+
return throttled;
|
|
2828
|
+
};
|
|
2829
|
+
//#endregion
|
|
2830
|
+
//#region src/terminal-size.ts
|
|
2831
|
+
const create = (columns, rows) => ({
|
|
2832
|
+
columns: Number.parseInt(String(columns), 10),
|
|
2833
|
+
rows: Number.parseInt(String(rows), 10)
|
|
2834
|
+
});
|
|
2835
|
+
const devTty = () => {
|
|
2836
|
+
try {
|
|
2837
|
+
const { O_EVTONLY: evtOnly } = fs.constants;
|
|
2838
|
+
const flags = process.platform === "darwin" && evtOnly !== void 0 ? evtOnly | fs.constants.O_NONBLOCK : fs.constants.O_NONBLOCK;
|
|
2839
|
+
const { columns, rows } = new tty.WriteStream(fs.openSync("/dev/tty", flags));
|
|
2840
|
+
if (columns && rows) return {
|
|
2841
|
+
columns,
|
|
2842
|
+
rows
|
|
2843
|
+
};
|
|
2844
|
+
} catch {}
|
|
2845
|
+
};
|
|
2846
|
+
const terminalSize = () => {
|
|
2847
|
+
const { env, stdout, stderr } = process;
|
|
2848
|
+
if (stdout?.columns && stdout?.rows) return create(stdout.columns, stdout.rows);
|
|
2849
|
+
if (stderr?.columns && stderr?.rows) return create(stderr.columns, stderr.rows);
|
|
2850
|
+
if (env["COLUMNS"] && env["LINES"]) return create(env["COLUMNS"], env["LINES"]);
|
|
2851
|
+
return devTty() ?? {
|
|
2852
|
+
columns: 80,
|
|
2853
|
+
rows: 24
|
|
2854
|
+
};
|
|
2855
|
+
};
|
|
2856
|
+
//#endregion
|
|
2857
|
+
//#region src/utils.ts
|
|
2858
|
+
const resolveDimension = (value, fallback, defaultValue) => {
|
|
2859
|
+
if (value !== void 0 && value > 0) return value;
|
|
2860
|
+
if (fallback !== void 0 && fallback > 0) return fallback;
|
|
2861
|
+
return defaultValue;
|
|
2862
|
+
};
|
|
2863
|
+
/**
|
|
2864
|
+
Get the effective terminal dimensions from the given stdout stream.
|
|
2865
|
+
|
|
2866
|
+
Falls back to `terminal-size` for columns in piped processes where `stdout.columns` is 0, and uses standard defaults (80×24) when dimensions cannot be determined.
|
|
2867
|
+
*/
|
|
2868
|
+
const getWindowSize = (stdout) => {
|
|
2869
|
+
const columns = stdout.columns ?? 0;
|
|
2870
|
+
const rows = stdout.rows ?? 0;
|
|
2871
|
+
if (columns && rows) return {
|
|
2872
|
+
columns,
|
|
2873
|
+
rows
|
|
2874
|
+
};
|
|
2875
|
+
const fallbackSize = terminalSize();
|
|
2876
|
+
return {
|
|
2877
|
+
columns: resolveDimension(columns, fallbackSize.columns, 80),
|
|
2878
|
+
rows: resolveDimension(rows, fallbackSize.rows, 24)
|
|
2879
|
+
};
|
|
2880
|
+
};
|
|
2881
|
+
//#endregion
|
|
2882
|
+
//#region src/write-synchronized.ts
|
|
2883
|
+
function shouldSynchronize(stream, interactive) {
|
|
2884
|
+
return "isTTY" in stream && stream.isTTY && (interactive ?? !isInCi);
|
|
2885
|
+
}
|
|
2886
|
+
//#endregion
|
|
2887
|
+
//#region src/ink.tsx
|
|
2888
|
+
const noop = () => {};
|
|
2889
|
+
const textEncoder = new TextEncoder();
|
|
2890
|
+
const yieldImmediate = async () => new Promise((resolve) => {
|
|
2891
|
+
setImmediate(resolve);
|
|
2892
|
+
});
|
|
2893
|
+
const kittyQueryEscapeByte = 27;
|
|
2894
|
+
const kittyQueryOpenBracketByte = 91;
|
|
2895
|
+
const kittyQueryQuestionMarkByte = 63;
|
|
2896
|
+
const kittyQueryLetterByte = 117;
|
|
2897
|
+
const zeroByte = 48;
|
|
2898
|
+
const nineByte = 57;
|
|
2899
|
+
const isDigitByte = (byte) => byte >= zeroByte && byte <= nineByte;
|
|
2900
|
+
const matchKittyQueryResponse = (buffer, startIndex) => {
|
|
2901
|
+
if (buffer[startIndex] !== kittyQueryEscapeByte || buffer[startIndex + 1] !== kittyQueryOpenBracketByte || buffer[startIndex + 2] !== kittyQueryQuestionMarkByte) return;
|
|
2902
|
+
let index = startIndex + 3;
|
|
2903
|
+
const digitsStartIndex = index;
|
|
2904
|
+
while (index < buffer.length && isDigitByte(buffer[index])) index++;
|
|
2905
|
+
if (index === digitsStartIndex) return;
|
|
2906
|
+
if (index === buffer.length) return { state: "partial" };
|
|
2907
|
+
if (buffer[index] === kittyQueryLetterByte) return {
|
|
2908
|
+
state: "complete",
|
|
2909
|
+
endIndex: index
|
|
2910
|
+
};
|
|
2911
|
+
};
|
|
2912
|
+
const hasCompleteKittyQueryResponse = (buffer) => {
|
|
2913
|
+
for (let index = 0; index < buffer.length; index++) if (matchKittyQueryResponse(buffer, index)?.state === "complete") return true;
|
|
2914
|
+
return false;
|
|
2915
|
+
};
|
|
2916
|
+
const stripKittyQueryResponsesAndTrailingPartial = (buffer) => {
|
|
2917
|
+
const keptBytes = [];
|
|
2918
|
+
let index = 0;
|
|
2919
|
+
while (index < buffer.length) {
|
|
2920
|
+
const match = matchKittyQueryResponse(buffer, index);
|
|
2921
|
+
if (match?.state === "complete") {
|
|
2922
|
+
index = match.endIndex + 1;
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
if (match?.state === "partial") break;
|
|
2926
|
+
keptBytes.push(buffer[index]);
|
|
2927
|
+
index++;
|
|
2928
|
+
}
|
|
2929
|
+
return keptBytes;
|
|
2930
|
+
};
|
|
2931
|
+
const isWindowsConsole = process.platform === "win32";
|
|
2932
|
+
const shouldClearTerminalForFrame = ({ isTTY, viewportRows, previousOutputHeight, nextOutputHeight, isUnmounting }) => {
|
|
2933
|
+
if (!isTTY) return false;
|
|
2934
|
+
const hadPreviousFrame = previousOutputHeight > 0;
|
|
2935
|
+
const wasFullscreen = previousOutputHeight >= viewportRows;
|
|
2936
|
+
const wasOverflowing = previousOutputHeight > viewportRows;
|
|
2937
|
+
const isOverflowing = nextOutputHeight > viewportRows;
|
|
2938
|
+
const isFullscreen = nextOutputHeight >= viewportRows;
|
|
2939
|
+
const isLeavingFullscreen = wasOverflowing && nextOutputHeight < viewportRows;
|
|
2940
|
+
const shouldClearOnUnmount = isUnmounting && wasFullscreen;
|
|
2941
|
+
if (isWindowsConsole && (wasFullscreen || isFullscreen)) return true;
|
|
2942
|
+
return wasOverflowing || isOverflowing && hadPreviousFrame || isLeavingFullscreen || shouldClearOnUnmount;
|
|
2943
|
+
};
|
|
2944
|
+
const isErrorInput = (value) => {
|
|
2945
|
+
return value instanceof Error || Object.prototype.toString.call(value) === "[object Error]";
|
|
2946
|
+
};
|
|
2947
|
+
const getWritableStreamState = (stdout) => {
|
|
2948
|
+
return { canWriteToStdout: !stdout.destroyed && !stdout.writableEnded && (stdout.writable ?? true) };
|
|
2949
|
+
};
|
|
2950
|
+
const settleThrottle = (throttled, canWriteToStdout) => {
|
|
2951
|
+
if (!throttled || typeof throttled.flush !== "function") return;
|
|
2952
|
+
const throttledValue = throttled;
|
|
2953
|
+
if (canWriteToStdout) throttledValue.flush();
|
|
2954
|
+
else if (typeof throttledValue.cancel === "function") throttledValue.cancel();
|
|
2955
|
+
};
|
|
2956
|
+
const createRenderPassthrough = (stream) => {
|
|
2957
|
+
const passthrough = Object.create(stream);
|
|
2958
|
+
passthrough.write = stream.write.bind(stream);
|
|
2959
|
+
passthrough.on = stream.on.bind(stream);
|
|
2960
|
+
passthrough.off = stream.off.bind(stream);
|
|
2961
|
+
passthrough.once = stream.once.bind(stream);
|
|
2962
|
+
passthrough.addListener = stream.addListener.bind(stream);
|
|
2963
|
+
passthrough.removeListener = stream.removeListener.bind(stream);
|
|
2964
|
+
passthrough.emit = stream.emit.bind(stream);
|
|
2965
|
+
return passthrough;
|
|
2966
|
+
};
|
|
2967
|
+
var Ink = class {
|
|
2968
|
+
/**
|
|
2969
|
+
Whether this instance is using concurrent rendering mode.
|
|
2970
|
+
*/
|
|
2971
|
+
isConcurrent;
|
|
2972
|
+
options;
|
|
2973
|
+
log;
|
|
2974
|
+
cursorPosition;
|
|
2975
|
+
throttledLog;
|
|
2976
|
+
isScreenReaderEnabled;
|
|
2977
|
+
interactive;
|
|
2978
|
+
renderThrottleMs;
|
|
2979
|
+
alternateScreen;
|
|
2980
|
+
isUnmounted;
|
|
2981
|
+
isUnmounting;
|
|
2982
|
+
lastOutput;
|
|
2983
|
+
lastOutputToRender;
|
|
2984
|
+
lastOutputHeight;
|
|
2985
|
+
lastTerminalWidth;
|
|
2986
|
+
lastTerminalHeight;
|
|
2987
|
+
container;
|
|
2988
|
+
rootNode;
|
|
2989
|
+
fullStaticOutput;
|
|
2990
|
+
exitPromise;
|
|
2991
|
+
exitResult;
|
|
2992
|
+
beforeExitHandler;
|
|
2993
|
+
restoreConsole;
|
|
2994
|
+
captureTargets;
|
|
2995
|
+
capturedStdioTails = {
|
|
2996
|
+
stdout: "",
|
|
2997
|
+
stderr: ""
|
|
2998
|
+
};
|
|
2999
|
+
unsubscribeResize;
|
|
3000
|
+
throttledOnRender;
|
|
3001
|
+
hasPendingThrottledRender = false;
|
|
3002
|
+
kittyProtocolEnabled = false;
|
|
3003
|
+
kittyFlags;
|
|
3004
|
+
cancelKittyDetection;
|
|
3005
|
+
nextRenderCommit;
|
|
3006
|
+
isSuspended = false;
|
|
3007
|
+
pauseInput;
|
|
3008
|
+
resumeInput;
|
|
3009
|
+
constructor(options) {
|
|
3010
|
+
autoBind(this);
|
|
3011
|
+
if (options.patchConsole === "stdio") {
|
|
3012
|
+
this.captureTargets = {
|
|
3013
|
+
stdout: options.stdout,
|
|
3014
|
+
stderr: options.stderr
|
|
3015
|
+
};
|
|
3016
|
+
options = {
|
|
3017
|
+
...options,
|
|
3018
|
+
stdout: createRenderPassthrough(options.stdout),
|
|
3019
|
+
stderr: createRenderPassthrough(options.stderr)
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
this.options = options;
|
|
3023
|
+
this.rootNode = createNode("ink-root");
|
|
3024
|
+
this.rootNode.onComputeLayout = this.calculateLayout;
|
|
3025
|
+
this.isScreenReaderEnabled = options.isScreenReaderEnabled ?? process.env["SIGIL_SCREEN_READER"] === "true";
|
|
3026
|
+
this.interactive = this.resolveInteractiveOption(options.interactive);
|
|
3027
|
+
this.alternateScreen = false;
|
|
3028
|
+
const unthrottled = options.debug || this.isScreenReaderEnabled;
|
|
3029
|
+
const maxFps = options.maxFps ?? 30;
|
|
3030
|
+
const renderThrottleMs = maxFps > 0 ? Math.max(1, Math.ceil(1e3 / maxFps)) : 0;
|
|
3031
|
+
this.renderThrottleMs = unthrottled ? 0 : renderThrottleMs;
|
|
3032
|
+
if (unthrottled) {
|
|
3033
|
+
this.rootNode.onRender = this.onRender;
|
|
3034
|
+
this.throttledOnRender = void 0;
|
|
3035
|
+
} else {
|
|
3036
|
+
const throttled = throttle(this.onRender, renderThrottleMs);
|
|
3037
|
+
this.rootNode.onRender = () => {
|
|
3038
|
+
this.hasPendingThrottledRender = true;
|
|
3039
|
+
throttled();
|
|
3040
|
+
};
|
|
3041
|
+
this.throttledOnRender = throttled;
|
|
3042
|
+
}
|
|
3043
|
+
this.rootNode.onImmediateRender = this.onRender;
|
|
3044
|
+
this.rootNode.onStaticChange = this.handleStaticChange;
|
|
3045
|
+
this.log = logUpdate.create(options.stdout, { incremental: options.incrementalRendering });
|
|
3046
|
+
this.cursorPosition = void 0;
|
|
3047
|
+
this.throttledLog = unthrottled ? this.log : throttle((output) => {
|
|
3048
|
+
const shouldWrite = this.log.willRender(output);
|
|
3049
|
+
const sync = this.shouldSync();
|
|
3050
|
+
if (sync && shouldWrite) this.options.stdout.write(bsu);
|
|
3051
|
+
this.log(output);
|
|
3052
|
+
if (sync && shouldWrite) this.options.stdout.write(esu);
|
|
3053
|
+
});
|
|
3054
|
+
this.isUnmounted = false;
|
|
3055
|
+
this.isUnmounting = false;
|
|
3056
|
+
this.isConcurrent = options.concurrent ?? false;
|
|
3057
|
+
this.lastOutput = "";
|
|
3058
|
+
this.lastOutputToRender = "";
|
|
3059
|
+
this.lastOutputHeight = 0;
|
|
3060
|
+
this.lastTerminalWidth = getWindowSize(this.options.stdout).columns;
|
|
3061
|
+
this.lastTerminalHeight = getWindowSize(this.options.stdout).rows;
|
|
3062
|
+
this.fullStaticOutput = "";
|
|
3063
|
+
const rootTag = options.concurrent ? ConcurrentRoot : LegacyRoot;
|
|
3064
|
+
this.container = reconciler.createContainer(this.rootNode, rootTag, null, false, null, "id", () => {}, () => {}, () => {}, () => {});
|
|
3065
|
+
this.unsubscribeExit = signalExit(this.unmount.bind(this), { alwaysLast: false });
|
|
3066
|
+
this.setAlternateScreen(Boolean(options.alternateScreen));
|
|
3067
|
+
if (process.env["SIGIL_DEV"] === "true") reconciler.injectIntoDevTools();
|
|
3068
|
+
if (options.patchConsole) this.patchConsole();
|
|
3069
|
+
if (this.interactive) {
|
|
3070
|
+
options.stdout.on("resize", this.resized);
|
|
3071
|
+
this.unsubscribeResize = () => {
|
|
3072
|
+
options.stdout.off("resize", this.resized);
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
this.initKittyKeyboard();
|
|
3076
|
+
this.exitPromise = new Promise((resolve, reject) => {
|
|
3077
|
+
this.resolveExitPromise = resolve;
|
|
3078
|
+
this.rejectExitPromise = reject;
|
|
3079
|
+
});
|
|
3080
|
+
this.exitPromise.catch(noop);
|
|
3081
|
+
}
|
|
3082
|
+
resized = () => {
|
|
3083
|
+
const currentWidth = getWindowSize(this.options.stdout).columns;
|
|
3084
|
+
const currentHeight = getWindowSize(this.options.stdout).rows;
|
|
3085
|
+
if (currentWidth < this.lastTerminalWidth || currentHeight !== this.lastTerminalHeight) {
|
|
3086
|
+
this.log.clear();
|
|
3087
|
+
this.lastOutput = "";
|
|
3088
|
+
this.lastOutputToRender = "";
|
|
3089
|
+
this.lastOutputHeight = 0;
|
|
3090
|
+
}
|
|
3091
|
+
this.calculateLayout();
|
|
3092
|
+
emitLayoutListeners(this.rootNode);
|
|
3093
|
+
this.onRender();
|
|
3094
|
+
this.lastTerminalWidth = currentWidth;
|
|
3095
|
+
this.lastTerminalHeight = currentHeight;
|
|
3096
|
+
};
|
|
3097
|
+
resolveExitPromise = () => {};
|
|
3098
|
+
rejectExitPromise = () => {};
|
|
3099
|
+
unsubscribeExit = () => {};
|
|
3100
|
+
handleAppExit = (errorOrResult) => {
|
|
3101
|
+
if (this.isUnmounted || this.isUnmounting) return;
|
|
3102
|
+
if (isErrorInput(errorOrResult)) {
|
|
3103
|
+
this.unmount(errorOrResult);
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
this.exitResult = errorOrResult;
|
|
3107
|
+
this.unmount();
|
|
3108
|
+
};
|
|
3109
|
+
setCursorPosition = (position) => {
|
|
3110
|
+
this.cursorPosition = position;
|
|
3111
|
+
this.log.setCursorPosition(position);
|
|
3112
|
+
};
|
|
3113
|
+
restoreLastOutput = () => {
|
|
3114
|
+
if (!this.interactive) return;
|
|
3115
|
+
this.log.setCursorPosition(this.cursorPosition);
|
|
3116
|
+
this.log(this.lastOutputToRender || this.lastOutput + "\n");
|
|
3117
|
+
};
|
|
3118
|
+
calculateLayout = () => {
|
|
3119
|
+
const terminalWidth = getWindowSize(this.options.stdout).columns;
|
|
3120
|
+
this.rootNode.yogaNode.setWidth(terminalWidth);
|
|
3121
|
+
this.rootNode.yogaNode.calculateLayout(void 0, void 0, Yoga.DIRECTION_LTR);
|
|
3122
|
+
};
|
|
3123
|
+
handleStaticChange = () => {
|
|
3124
|
+
this.fullStaticOutput = "";
|
|
3125
|
+
};
|
|
3126
|
+
onRender = () => {
|
|
3127
|
+
this.hasPendingThrottledRender = false;
|
|
3128
|
+
if (this.isUnmounted) return;
|
|
3129
|
+
if (this.isSuspended) {
|
|
3130
|
+
if (this.nextRenderCommit) {
|
|
3131
|
+
this.nextRenderCommit.resolve();
|
|
3132
|
+
this.nextRenderCommit = void 0;
|
|
3133
|
+
}
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
if (this.nextRenderCommit) {
|
|
3137
|
+
this.nextRenderCommit.resolve();
|
|
3138
|
+
this.nextRenderCommit = void 0;
|
|
3139
|
+
}
|
|
3140
|
+
const startTime = performance.now();
|
|
3141
|
+
const { output, outputHeight, staticOutput } = renderer(this.rootNode, this.isScreenReaderEnabled);
|
|
3142
|
+
this.options.onRender?.({ renderTime: performance.now() - startTime });
|
|
3143
|
+
const hasStaticOutput = staticOutput && staticOutput !== "\n";
|
|
3144
|
+
if (this.options.debug) {
|
|
3145
|
+
if (hasStaticOutput) this.fullStaticOutput += staticOutput;
|
|
3146
|
+
this.lastOutput = output;
|
|
3147
|
+
this.lastOutputToRender = output;
|
|
3148
|
+
this.lastOutputHeight = outputHeight;
|
|
3149
|
+
this.options.stdout.write(this.fullStaticOutput + output);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
if (!this.interactive) {
|
|
3153
|
+
if (hasStaticOutput) this.options.stdout.write(staticOutput);
|
|
3154
|
+
this.lastOutput = output;
|
|
3155
|
+
this.lastOutputToRender = output + "\n";
|
|
3156
|
+
this.lastOutputHeight = outputHeight;
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
if (this.isScreenReaderEnabled) {
|
|
3160
|
+
const sync = this.shouldSync();
|
|
3161
|
+
if (sync) this.options.stdout.write(bsu);
|
|
3162
|
+
if (hasStaticOutput) {
|
|
3163
|
+
const erase = this.lastOutputHeight > 0 ? ansiEscapes.eraseLines(this.lastOutputHeight) : "";
|
|
3164
|
+
this.options.stdout.write(erase + staticOutput);
|
|
3165
|
+
this.lastOutputHeight = 0;
|
|
3166
|
+
}
|
|
3167
|
+
if (output === this.lastOutput && !hasStaticOutput) {
|
|
3168
|
+
if (sync) this.options.stdout.write(esu);
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
const terminalWidth = getWindowSize(this.options.stdout).columns;
|
|
3172
|
+
const wrappedOutput = wrapAnsi(output, terminalWidth, {
|
|
3173
|
+
trim: false,
|
|
3174
|
+
hard: true
|
|
3175
|
+
});
|
|
3176
|
+
if (hasStaticOutput) this.options.stdout.write(wrappedOutput);
|
|
3177
|
+
else {
|
|
3178
|
+
const erase = this.lastOutputHeight > 0 ? ansiEscapes.eraseLines(this.lastOutputHeight) : "";
|
|
3179
|
+
this.options.stdout.write(erase + wrappedOutput);
|
|
3180
|
+
}
|
|
3181
|
+
this.lastOutput = output;
|
|
3182
|
+
this.lastOutputToRender = wrappedOutput;
|
|
3183
|
+
this.lastOutputHeight = wrappedOutput === "" ? 0 : wrappedOutput.split("\n").length;
|
|
3184
|
+
if (sync) this.options.stdout.write(esu);
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
if (hasStaticOutput) this.fullStaticOutput += staticOutput;
|
|
3188
|
+
this.renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : "");
|
|
3189
|
+
};
|
|
3190
|
+
render(node) {
|
|
3191
|
+
const tree = /* @__PURE__ */ jsx(accessibilityContext.Provider, {
|
|
3192
|
+
value: { isScreenReaderEnabled: this.isScreenReaderEnabled },
|
|
3193
|
+
children: /* @__PURE__ */ jsx(App, {
|
|
3194
|
+
stdin: this.options.stdin,
|
|
3195
|
+
stdout: this.options.stdout,
|
|
3196
|
+
stderr: this.options.stderr,
|
|
3197
|
+
exitOnCtrlC: this.options.exitOnCtrlC,
|
|
3198
|
+
interactive: this.interactive,
|
|
3199
|
+
renderThrottleMs: this.renderThrottleMs,
|
|
3200
|
+
writeToStdout: this.writeToStdout.bind(this),
|
|
3201
|
+
writeToStderr: this.writeToStderr.bind(this),
|
|
3202
|
+
setCursorPosition: this.setCursorPosition.bind(this),
|
|
3203
|
+
onExit: this.handleAppExit.bind(this),
|
|
3204
|
+
onWaitUntilRenderFlush: this.waitUntilRenderFlush.bind(this),
|
|
3205
|
+
onSuspendTerminal: this.suspendTerminal.bind(this),
|
|
3206
|
+
onRegisterInputControl: this.registerInputControl.bind(this),
|
|
3207
|
+
children: node
|
|
3208
|
+
})
|
|
3209
|
+
});
|
|
3210
|
+
if (this.options.concurrent) reconciler.updateContainer(tree, this.container, null, noop);
|
|
3211
|
+
else {
|
|
3212
|
+
reconciler.updateContainerSync(tree, this.container, null, noop);
|
|
3213
|
+
reconciler.flushSyncWork();
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
writeToStdout(data) {
|
|
3217
|
+
if (this.isUnmounted) return;
|
|
3218
|
+
if (this.isSuspended) return;
|
|
3219
|
+
if (this.options.debug) {
|
|
3220
|
+
this.options.stdout.write(data + this.fullStaticOutput + this.lastOutput);
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
3223
|
+
if (!this.interactive) {
|
|
3224
|
+
this.options.stdout.write(data);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
const sync = this.shouldSync();
|
|
3228
|
+
if (sync) this.options.stdout.write(bsu);
|
|
3229
|
+
this.log.clear();
|
|
3230
|
+
this.options.stdout.write(data);
|
|
3231
|
+
this.restoreLastOutput();
|
|
3232
|
+
if (sync) this.options.stdout.write(esu);
|
|
3233
|
+
}
|
|
3234
|
+
writeToStderr(data) {
|
|
3235
|
+
if (this.isUnmounted) return;
|
|
3236
|
+
if (this.isSuspended) return;
|
|
3237
|
+
if (this.options.debug) {
|
|
3238
|
+
this.options.stderr.write(data);
|
|
3239
|
+
this.options.stdout.write(this.fullStaticOutput + this.lastOutput);
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
if (!this.interactive) {
|
|
3243
|
+
this.options.stderr.write(data);
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
const sync = this.shouldSync();
|
|
3247
|
+
if (sync) this.options.stdout.write(bsu);
|
|
3248
|
+
this.log.clear();
|
|
3249
|
+
this.options.stderr.write(data);
|
|
3250
|
+
this.restoreLastOutput();
|
|
3251
|
+
if (sync) this.options.stdout.write(esu);
|
|
3252
|
+
}
|
|
3253
|
+
unmount(error) {
|
|
3254
|
+
if (this.isUnmounted || this.isUnmounting) return;
|
|
3255
|
+
this.isUnmounting = true;
|
|
3256
|
+
if (this.beforeExitHandler) {
|
|
3257
|
+
process.off("beforeExit", this.beforeExitHandler);
|
|
3258
|
+
this.beforeExitHandler = void 0;
|
|
3259
|
+
}
|
|
3260
|
+
const { stdout } = this.options;
|
|
3261
|
+
const { canWriteToStdout } = getWritableStreamState(stdout);
|
|
3262
|
+
if (canWriteToStdout) this.flushCapturedStdio();
|
|
3263
|
+
settleThrottle(this.throttledOnRender, canWriteToStdout);
|
|
3264
|
+
if (canWriteToStdout) {
|
|
3265
|
+
if (!this.throttledOnRender || !this.hasPendingThrottledRender && this.fullStaticOutput === "") {
|
|
3266
|
+
this.calculateLayout();
|
|
3267
|
+
this.onRender();
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
this.isUnmounted = true;
|
|
3271
|
+
this.unsubscribeExit();
|
|
3272
|
+
settleThrottle(this.throttledLog, canWriteToStdout);
|
|
3273
|
+
if (typeof this.restoreConsole === "function") this.restoreConsole();
|
|
3274
|
+
const finishUnmount = () => {
|
|
3275
|
+
if (typeof this.unsubscribeResize === "function") this.unsubscribeResize();
|
|
3276
|
+
if (this.cancelKittyDetection) this.cancelKittyDetection();
|
|
3277
|
+
if (canWriteToStdout) {
|
|
3278
|
+
if (this.kittyProtocolEnabled) this.writeBestEffort(this.options.stdout, ansiEscapes.popKittyKeyboard);
|
|
3279
|
+
if (this.alternateScreen) {
|
|
3280
|
+
this.writeBestEffort(this.options.stdout, ansiEscapes.exitAlternativeScreen);
|
|
3281
|
+
this.writeBestEffort(this.options.stdout, showCursorEscape);
|
|
3282
|
+
this.alternateScreen = false;
|
|
3283
|
+
}
|
|
3284
|
+
if (!this.interactive) this.options.stdout.write(this.options.debug ? "\n" : this.lastOutput + "\n");
|
|
3285
|
+
else if (!this.options.debug) this.log.done();
|
|
3286
|
+
}
|
|
3287
|
+
this.kittyProtocolEnabled = false;
|
|
3288
|
+
instances.delete(this.captureTargets?.stdout ?? this.options.stdout);
|
|
3289
|
+
const { exitResult } = this;
|
|
3290
|
+
const resolveOrReject = () => {
|
|
3291
|
+
if (isErrorInput(error)) this.rejectExitPromise(error);
|
|
3292
|
+
else this.resolveExitPromise(exitResult);
|
|
3293
|
+
};
|
|
3294
|
+
if (error !== void 0 && !isErrorInput(error)) resolveOrReject();
|
|
3295
|
+
else if (canWriteToStdout) this.options.stdout.write("", resolveOrReject);
|
|
3296
|
+
else setImmediate(resolveOrReject);
|
|
3297
|
+
};
|
|
3298
|
+
const concurrentReconciler = reconciler;
|
|
3299
|
+
if (this.options.concurrent) {
|
|
3300
|
+
reconciler.updateContainerSync(null, this.container, null, noop);
|
|
3301
|
+
reconciler.flushSyncWork();
|
|
3302
|
+
concurrentReconciler.flushPassiveEffects?.();
|
|
3303
|
+
finishUnmount();
|
|
3304
|
+
} else {
|
|
3305
|
+
reconciler.updateContainerSync(null, this.container, null, noop);
|
|
3306
|
+
reconciler.flushSyncWork();
|
|
3307
|
+
finishUnmount();
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
async waitUntilExit() {
|
|
3311
|
+
if (!this.beforeExitHandler) {
|
|
3312
|
+
this.beforeExitHandler = () => {
|
|
3313
|
+
this.unmount();
|
|
3314
|
+
};
|
|
3315
|
+
process.once("beforeExit", this.beforeExitHandler);
|
|
3316
|
+
}
|
|
3317
|
+
return this.exitPromise;
|
|
3318
|
+
}
|
|
3319
|
+
async waitUntilRenderFlush() {
|
|
3320
|
+
if (this.isUnmounted || this.isUnmounting) {
|
|
3321
|
+
await this.awaitExit();
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
await yieldImmediate();
|
|
3325
|
+
if (this.isUnmounted || this.isUnmounting) {
|
|
3326
|
+
await this.awaitExit();
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
3329
|
+
if (this.isConcurrent && this.hasPendingConcurrentWork()) {
|
|
3330
|
+
await Promise.race([this.awaitNextRender(), this.awaitExit()]);
|
|
3331
|
+
if (this.isUnmounted || this.isUnmounting) {
|
|
3332
|
+
this.nextRenderCommit = void 0;
|
|
3333
|
+
await this.awaitExit();
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
reconciler.flushSyncWork();
|
|
3338
|
+
const { stdout } = this.options;
|
|
3339
|
+
const { canWriteToStdout } = getWritableStreamState(stdout);
|
|
3340
|
+
settleThrottle(this.throttledOnRender, canWriteToStdout);
|
|
3341
|
+
settleThrottle(this.throttledLog, canWriteToStdout);
|
|
3342
|
+
if (canWriteToStdout) {
|
|
3343
|
+
await new Promise((resolve) => {
|
|
3344
|
+
this.options.stdout.write("", () => {
|
|
3345
|
+
resolve();
|
|
3346
|
+
});
|
|
3347
|
+
});
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
await yieldImmediate();
|
|
3351
|
+
}
|
|
3352
|
+
clear() {
|
|
3353
|
+
if (this.interactive && !this.options.debug) {
|
|
3354
|
+
this.log.clear();
|
|
3355
|
+
this.log.sync(this.lastOutputToRender || this.lastOutput + "\n");
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
patchConsole() {
|
|
3359
|
+
if (this.options.debug) return;
|
|
3360
|
+
const restoreConsoleMethods = patchConsole((stream, data) => {
|
|
3361
|
+
if (this.options.onCapturedOutput?.(stream, data, "console") === true) return;
|
|
3362
|
+
if (stream === "stdout") this.writeToStdout(data);
|
|
3363
|
+
if (stream === "stderr") {
|
|
3364
|
+
if (!data.startsWith("The above error occurred")) this.writeToStderr(data);
|
|
3365
|
+
}
|
|
3366
|
+
});
|
|
3367
|
+
const restoreDirectStdio = this.patchDirectStdio();
|
|
3368
|
+
this.restoreConsole = () => {
|
|
3369
|
+
restoreConsoleMethods();
|
|
3370
|
+
restoreDirectStdio?.();
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
patchDirectStdio() {
|
|
3374
|
+
const targets = this.captureTargets;
|
|
3375
|
+
if (!targets) return;
|
|
3376
|
+
const patch = (name, stream) => {
|
|
3377
|
+
const originalWrite = stream.write;
|
|
3378
|
+
const patchedWrite = (chunk, encodingOrCallback, callback) => {
|
|
3379
|
+
const data = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString() : String(chunk);
|
|
3380
|
+
this.handleCapturedStdio(name, data);
|
|
3381
|
+
(typeof encodingOrCallback === "function" ? encodingOrCallback : typeof callback === "function" ? callback : void 0)?.();
|
|
3382
|
+
return true;
|
|
3383
|
+
};
|
|
3384
|
+
stream.write = patchedWrite;
|
|
3385
|
+
return () => {
|
|
3386
|
+
stream.write = originalWrite;
|
|
3387
|
+
};
|
|
3388
|
+
};
|
|
3389
|
+
const restoreStdout = patch("stdout", targets.stdout);
|
|
3390
|
+
const restoreStderr = patch("stderr", targets.stderr);
|
|
3391
|
+
return () => {
|
|
3392
|
+
restoreStdout();
|
|
3393
|
+
restoreStderr();
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
handleCapturedStdio(stream, data) {
|
|
3397
|
+
if (this.options.onCapturedOutput?.(stream, data, "stdio") === true) return;
|
|
3398
|
+
const parts = (this.capturedStdioTails[stream] + data).split(/\r?\n/);
|
|
3399
|
+
this.capturedStdioTails[stream] = parts.pop() ?? "";
|
|
3400
|
+
if (parts.length === 0) return;
|
|
3401
|
+
const payload = parts.join("\n") + "\n";
|
|
3402
|
+
if (stream === "stdout") this.writeToStdout(payload);
|
|
3403
|
+
else this.writeToStderr(payload);
|
|
3404
|
+
}
|
|
3405
|
+
flushCapturedStdio() {
|
|
3406
|
+
for (const stream of ["stdout", "stderr"]) {
|
|
3407
|
+
const tail = this.capturedStdioTails[stream];
|
|
3408
|
+
if (tail === "") continue;
|
|
3409
|
+
this.capturedStdioTails[stream] = "";
|
|
3410
|
+
if (stream === "stdout") this.writeToStdout(tail + "\n");
|
|
3411
|
+
else this.writeToStderr(tail + "\n");
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
registerInputControl(pauseInput, resumeInput) {
|
|
3415
|
+
this.pauseInput = pauseInput;
|
|
3416
|
+
this.resumeInput = resumeInput;
|
|
3417
|
+
}
|
|
3418
|
+
async suspendTerminal(callback) {
|
|
3419
|
+
this.beginSuspend();
|
|
3420
|
+
if (callback) {
|
|
3421
|
+
try {
|
|
3422
|
+
await callback();
|
|
3423
|
+
} finally {
|
|
3424
|
+
await this.endSuspend();
|
|
3425
|
+
}
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
const resume = async () => {
|
|
3429
|
+
await this.endSuspend();
|
|
3430
|
+
};
|
|
3431
|
+
return {
|
|
3432
|
+
resume,
|
|
3433
|
+
[Symbol.asyncDispose]: resume
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
setAlternateScreen(enabled) {
|
|
3437
|
+
this.alternateScreen = this.resolveAlternateScreenOption(enabled, this.interactive);
|
|
3438
|
+
if (this.alternateScreen) {
|
|
3439
|
+
this.writeBestEffort(this.options.stdout, ansiEscapes.enterAlternativeScreen);
|
|
3440
|
+
this.writeBestEffort(this.options.stdout, hideCursorEscape);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
resolveInteractiveOption(interactive) {
|
|
3444
|
+
return interactive ?? (!isInCi && Boolean(this.options.stdout.isTTY));
|
|
3445
|
+
}
|
|
3446
|
+
resolveAlternateScreenOption(alternateScreen, interactive) {
|
|
3447
|
+
return Boolean(alternateScreen) && interactive && Boolean(this.options.stdout.isTTY);
|
|
3448
|
+
}
|
|
3449
|
+
shouldSync() {
|
|
3450
|
+
return shouldSynchronize(this.options.stdout, this.interactive);
|
|
3451
|
+
}
|
|
3452
|
+
writeBestEffort(stream, data) {
|
|
3453
|
+
try {
|
|
3454
|
+
stream.write(data);
|
|
3455
|
+
} catch {}
|
|
3456
|
+
}
|
|
3457
|
+
async awaitExit() {
|
|
3458
|
+
try {
|
|
3459
|
+
await this.exitPromise;
|
|
3460
|
+
} catch {}
|
|
3461
|
+
}
|
|
3462
|
+
hasPendingConcurrentWork() {
|
|
3463
|
+
const concurrentContainer = this.container;
|
|
3464
|
+
return (concurrentContainer.pendingLanes ?? 0) !== 0 && concurrentContainer.callbackNode !== void 0 && concurrentContainer.callbackNode !== null;
|
|
3465
|
+
}
|
|
3466
|
+
async awaitNextRender() {
|
|
3467
|
+
if (!this.nextRenderCommit) {
|
|
3468
|
+
let resolveRender;
|
|
3469
|
+
const promise = new Promise((resolve) => {
|
|
3470
|
+
resolveRender = resolve;
|
|
3471
|
+
});
|
|
3472
|
+
this.nextRenderCommit = {
|
|
3473
|
+
promise,
|
|
3474
|
+
resolve: resolveRender
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
return this.nextRenderCommit.promise;
|
|
3478
|
+
}
|
|
3479
|
+
renderInteractiveFrame(output, outputHeight, staticOutput) {
|
|
3480
|
+
const hasStaticOutput = staticOutput !== "";
|
|
3481
|
+
const isTTY = Boolean(this.options.stdout.isTTY);
|
|
3482
|
+
const viewportRows = isTTY ? getWindowSize(this.options.stdout).rows : 24;
|
|
3483
|
+
if (isTTY && outputHeight > viewportRows) {
|
|
3484
|
+
const lines = output.split("\n");
|
|
3485
|
+
output = lines.slice(lines.length - viewportRows).join("\n");
|
|
3486
|
+
outputHeight = viewportRows;
|
|
3487
|
+
}
|
|
3488
|
+
const outputToRender = isTTY && outputHeight >= viewportRows ? output : output + "\n";
|
|
3489
|
+
if (shouldClearTerminalForFrame({
|
|
3490
|
+
isTTY,
|
|
3491
|
+
viewportRows,
|
|
3492
|
+
previousOutputHeight: this.lastOutputHeight,
|
|
3493
|
+
nextOutputHeight: outputHeight,
|
|
3494
|
+
isUnmounting: this.isUnmounting
|
|
3495
|
+
})) {
|
|
3496
|
+
const sync = this.shouldSync();
|
|
3497
|
+
if (sync) this.options.stdout.write(bsu);
|
|
3498
|
+
this.options.stdout.write(ansiEscapes.clearTerminal + this.fullStaticOutput + outputToRender);
|
|
3499
|
+
this.lastOutput = output;
|
|
3500
|
+
this.lastOutputToRender = outputToRender;
|
|
3501
|
+
this.lastOutputHeight = outputHeight;
|
|
3502
|
+
this.log.sync(outputToRender);
|
|
3503
|
+
if (sync) this.options.stdout.write(esu);
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
if (hasStaticOutput) {
|
|
3507
|
+
const sync = this.shouldSync();
|
|
3508
|
+
if (sync) this.options.stdout.write(bsu);
|
|
3509
|
+
this.log.clear();
|
|
3510
|
+
this.options.stdout.write(staticOutput);
|
|
3511
|
+
this.log(outputToRender);
|
|
3512
|
+
if (sync) this.options.stdout.write(esu);
|
|
3513
|
+
} else if (output !== this.lastOutput || this.log.isCursorDirty()) this.throttledLog(outputToRender);
|
|
3514
|
+
this.lastOutput = output;
|
|
3515
|
+
this.lastOutputToRender = outputToRender;
|
|
3516
|
+
this.lastOutputHeight = outputHeight;
|
|
3517
|
+
}
|
|
3518
|
+
initKittyKeyboard() {
|
|
3519
|
+
if (!this.options.kittyKeyboard) return;
|
|
3520
|
+
const opts = this.options.kittyKeyboard;
|
|
3521
|
+
const mode = opts.mode ?? "auto";
|
|
3522
|
+
if (mode === "disabled") return;
|
|
3523
|
+
const flags = opts.flags ?? ["disambiguateEscapeCodes"];
|
|
3524
|
+
if (mode === "enabled") {
|
|
3525
|
+
if (isTty(this.options.stdin) && this.options.stdout.isTTY) this.enableKittyProtocol(flags);
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
if (!this.interactive || !isTty(this.options.stdin) || !this.options.stdout.isTTY) return;
|
|
3529
|
+
this.confirmKittySupport(flags);
|
|
3530
|
+
}
|
|
3531
|
+
confirmKittySupport(flags) {
|
|
3532
|
+
const { stdin, stdout } = this.options;
|
|
3533
|
+
let responseBuffer = [];
|
|
3534
|
+
const cleanup = () => {
|
|
3535
|
+
this.cancelKittyDetection = void 0;
|
|
3536
|
+
clearTimeout(timer);
|
|
3537
|
+
stdin.removeListener("data", onData);
|
|
3538
|
+
const remaining = stripKittyQueryResponsesAndTrailingPartial(responseBuffer);
|
|
3539
|
+
responseBuffer = [];
|
|
3540
|
+
if (remaining.length > 0) stdin.unshift(Uint8Array.from(remaining));
|
|
3541
|
+
};
|
|
3542
|
+
const onData = (data) => {
|
|
3543
|
+
const chunk = typeof data === "string" ? textEncoder.encode(data) : data;
|
|
3544
|
+
for (const byte of chunk) responseBuffer.push(byte);
|
|
3545
|
+
if (hasCompleteKittyQueryResponse(responseBuffer)) {
|
|
3546
|
+
cleanup();
|
|
3547
|
+
if (!this.isUnmounted) this.enableKittyProtocol(flags);
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3550
|
+
stdin.on("data", onData);
|
|
3551
|
+
const timer = setTimeout(cleanup, 200);
|
|
3552
|
+
this.cancelKittyDetection = cleanup;
|
|
3553
|
+
stdout.write(ansiEscapes.kittyQuery);
|
|
3554
|
+
}
|
|
3555
|
+
enableKittyProtocol(flags) {
|
|
3556
|
+
this.options.stdout.write(ansiEscapes.pushKittyKeyboard(resolveFlags(flags)));
|
|
3557
|
+
this.kittyProtocolEnabled = true;
|
|
3558
|
+
this.kittyFlags = flags;
|
|
3559
|
+
}
|
|
3560
|
+
beginSuspend() {
|
|
3561
|
+
if (this.isSuspended) throw new Error("The terminal is already suspended. Resume the current suspension before suspending again.");
|
|
3562
|
+
this.isSuspended = true;
|
|
3563
|
+
if (!this.interactive || this.isUnmounted || this.isUnmounting) return;
|
|
3564
|
+
try {
|
|
3565
|
+
const { stdout } = this.options;
|
|
3566
|
+
const { canWriteToStdout } = getWritableStreamState(stdout);
|
|
3567
|
+
settleThrottle(this.throttledOnRender, canWriteToStdout);
|
|
3568
|
+
settleThrottle(this.throttledLog, canWriteToStdout);
|
|
3569
|
+
if (canWriteToStdout) this.flushCapturedStdio();
|
|
3570
|
+
if (canWriteToStdout) {
|
|
3571
|
+
this.log.clear();
|
|
3572
|
+
this.log.done();
|
|
3573
|
+
if (this.kittyProtocolEnabled) this.writeBestEffort(this.options.stdout, ansiEscapes.popKittyKeyboard);
|
|
3574
|
+
if (this.alternateScreen) this.writeBestEffort(this.options.stdout, ansiEscapes.exitAlternativeScreen);
|
|
3575
|
+
}
|
|
3576
|
+
this.pauseInput?.();
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
this.isSuspended = false;
|
|
3579
|
+
try {
|
|
3580
|
+
this.resumeInput?.();
|
|
3581
|
+
} catch {}
|
|
3582
|
+
throw error;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
async endSuspend() {
|
|
3586
|
+
if (!this.isSuspended) return;
|
|
3587
|
+
this.isSuspended = false;
|
|
3588
|
+
this.resumeInput?.();
|
|
3589
|
+
if (!this.interactive || this.isUnmounted || this.isUnmounting) return;
|
|
3590
|
+
const { stdout } = this.options;
|
|
3591
|
+
const { canWriteToStdout } = getWritableStreamState(stdout);
|
|
3592
|
+
if (canWriteToStdout) {
|
|
3593
|
+
if (this.alternateScreen) this.writeBestEffort(this.options.stdout, ansiEscapes.enterAlternativeScreen);
|
|
3594
|
+
if (this.kittyProtocolEnabled && this.kittyFlags) this.writeBestEffort(this.options.stdout, ansiEscapes.pushKittyKeyboard(resolveFlags(this.kittyFlags)));
|
|
3595
|
+
}
|
|
3596
|
+
this.lastOutput = "";
|
|
3597
|
+
this.lastOutputToRender = "";
|
|
3598
|
+
this.lastOutputHeight = 0;
|
|
3599
|
+
this.log.reset();
|
|
3600
|
+
try {
|
|
3601
|
+
this.calculateLayout();
|
|
3602
|
+
this.onRender();
|
|
3603
|
+
await this.waitUntilRenderFlush();
|
|
3604
|
+
} catch {}
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
//#endregion
|
|
3608
|
+
//#region src/render.ts
|
|
3609
|
+
/**
|
|
3610
|
+
Mount a component and render the output.
|
|
3611
|
+
*/
|
|
3612
|
+
const render = (node, options) => {
|
|
3613
|
+
const inkOptions = {
|
|
3614
|
+
stdout: process.stdout,
|
|
3615
|
+
stdin: process.stdin,
|
|
3616
|
+
stderr: process.stderr,
|
|
3617
|
+
debug: false,
|
|
3618
|
+
exitOnCtrlC: true,
|
|
3619
|
+
patchConsole: true,
|
|
3620
|
+
maxFps: 30,
|
|
3621
|
+
incrementalRendering: false,
|
|
3622
|
+
concurrent: false,
|
|
3623
|
+
alternateScreen: false,
|
|
3624
|
+
...getOptions(options)
|
|
3625
|
+
};
|
|
3626
|
+
const instance = getInstance(inkOptions.stdout, () => new Ink(inkOptions));
|
|
3627
|
+
instance.render(node);
|
|
3628
|
+
return {
|
|
3629
|
+
rerender: instance.render.bind(instance),
|
|
3630
|
+
unmount() {
|
|
3631
|
+
instance.unmount();
|
|
3632
|
+
},
|
|
3633
|
+
waitUntilExit: instance.waitUntilExit.bind(instance),
|
|
3634
|
+
waitUntilRenderFlush: instance.waitUntilRenderFlush.bind(instance),
|
|
3635
|
+
cleanup() {
|
|
3636
|
+
instance.unmount();
|
|
3637
|
+
},
|
|
3638
|
+
clear: instance.clear.bind(instance)
|
|
3639
|
+
};
|
|
3640
|
+
};
|
|
3641
|
+
const getOptions = (stdout = {}) => {
|
|
3642
|
+
if (stdout instanceof Stream) return {
|
|
3643
|
+
stdout,
|
|
3644
|
+
stdin: process.stdin
|
|
3645
|
+
};
|
|
3646
|
+
return stdout;
|
|
3647
|
+
};
|
|
3648
|
+
const getInstance = (stdout, createInstance) => {
|
|
3649
|
+
const instance = instances.get(stdout);
|
|
3650
|
+
if (instance === void 0) {
|
|
3651
|
+
const newInstance = createInstance();
|
|
3652
|
+
instances.set(stdout, newInstance);
|
|
3653
|
+
return newInstance;
|
|
3654
|
+
}
|
|
3655
|
+
process.stderr.write("Warning: render() was called again for the same stdout before the previous Ink instance was unmounted. Reusing stdout across multiple render() calls is unsupported. Call unmount() first.\n");
|
|
3656
|
+
return instance;
|
|
3657
|
+
};
|
|
3658
|
+
//#endregion
|
|
3659
|
+
//#region src/render-to-string.ts
|
|
3660
|
+
/**
|
|
3661
|
+
Render a React element to a string synchronously. Unlike `render()`, this function does not write to stdout, does not set up any terminal event listeners, and returns the rendered output as a string.
|
|
3662
|
+
|
|
3663
|
+
Useful for generating documentation, writing output to files, testing, or any scenario where you need the rendered output as a string without starting a persistent terminal application.
|
|
3664
|
+
|
|
3665
|
+
**Notes:**
|
|
3666
|
+
|
|
3667
|
+
- Terminal-specific hooks (`useInput`, `useStdin`, `useStdout`, `useStderr`, `useApp`, `useFocus`, `useFocusManager`) return default no-op values since there is no terminal session. They will not throw, but they will not function as in a live terminal.
|
|
3668
|
+
- `useEffect` callbacks will execute during rendering (due to synchronous rendering mode), but state updates they trigger will not affect the returned output, which reflects the initial render.
|
|
3669
|
+
- `useLayoutEffect` callbacks fire synchronously during commit, so state updates they trigger **will** be reflected in the output.
|
|
3670
|
+
- The `<Static>` component is supported — its output is prepended to the dynamic output.
|
|
3671
|
+
- If a component throws during rendering, the error is propagated to the caller after cleanup.
|
|
3672
|
+
|
|
3673
|
+
@example
|
|
3674
|
+
```
|
|
3675
|
+
import {renderToString, Text, Box} from 'ink';
|
|
3676
|
+
|
|
3677
|
+
const output = renderToString(
|
|
3678
|
+
<Box padding={1}>
|
|
3679
|
+
<Text color="green">Hello World</Text>
|
|
3680
|
+
</Box>,
|
|
3681
|
+
{columns: 40}
|
|
3682
|
+
);
|
|
3683
|
+
|
|
3684
|
+
console.log(output);
|
|
3685
|
+
```
|
|
3686
|
+
*/
|
|
3687
|
+
const renderToString = (node, options) => {
|
|
3688
|
+
const columns = options?.columns ?? 80;
|
|
3689
|
+
const rootNode = createNode("ink-root");
|
|
3690
|
+
let capturedStaticOutput = "";
|
|
3691
|
+
rootNode.onComputeLayout = () => {
|
|
3692
|
+
rootNode.yogaNode.setWidth(columns);
|
|
3693
|
+
rootNode.yogaNode.calculateLayout(void 0, void 0, Yoga.DIRECTION_LTR);
|
|
3694
|
+
};
|
|
3695
|
+
rootNode.onImmediateRender = () => {
|
|
3696
|
+
const { staticOutput } = renderer(rootNode, false);
|
|
3697
|
+
if (staticOutput && staticOutput !== "\n") capturedStaticOutput += staticOutput;
|
|
3698
|
+
};
|
|
3699
|
+
let uncaughtError;
|
|
3700
|
+
const container = reconciler.createContainer(rootNode, LegacyRoot, null, false, null, "render-to-string", (error) => {
|
|
3701
|
+
uncaughtError ??= error;
|
|
3702
|
+
}, () => {}, () => {}, () => {});
|
|
3703
|
+
reconciler.updateContainerSync(node, container, null, () => {});
|
|
3704
|
+
reconciler.flushSyncWork();
|
|
3705
|
+
const { output } = renderer(rootNode, false);
|
|
3706
|
+
reconciler.updateContainerSync(null, container, null, () => {});
|
|
3707
|
+
reconciler.flushSyncWork();
|
|
3708
|
+
if (uncaughtError !== void 0) throw uncaughtError instanceof Error ? uncaughtError : new Error(String(uncaughtError));
|
|
3709
|
+
const normalizedStaticOutput = capturedStaticOutput.endsWith("\n") ? capturedStaticOutput.slice(0, -1) : capturedStaticOutput;
|
|
3710
|
+
if (normalizedStaticOutput && output) return normalizedStaticOutput + "\n" + output;
|
|
3711
|
+
return normalizedStaticOutput || output;
|
|
3712
|
+
};
|
|
3713
|
+
//#endregion
|
|
3714
|
+
//#region src/components/Static.tsx
|
|
3715
|
+
/**
|
|
3716
|
+
`<Static>` component permanently renders its output above everything else. It's useful for displaying activity like completed tasks or logs—things that don't change after they're rendered (hence the name "Static").
|
|
3717
|
+
|
|
3718
|
+
It's preferred to use `<Static>` for use cases like these when you can't know or control the number of items that need to be rendered.
|
|
3719
|
+
|
|
3720
|
+
For example, [Tap](https://github.com/tapjs/node-tap) uses `<Static>` to display a list of completed tests. [Gatsby](https://github.com/gatsbyjs/gatsby) uses it to display a list of generated pages while still displaying a live progress bar.
|
|
3721
|
+
*/
|
|
3722
|
+
function Static(props) {
|
|
3723
|
+
const { items, children: render, style: customStyle } = props;
|
|
3724
|
+
const [index, setIndex] = useState(0);
|
|
3725
|
+
const itemsToRender = useMemo(() => {
|
|
3726
|
+
return items.slice(index);
|
|
3727
|
+
}, [items, index]);
|
|
3728
|
+
useLayoutEffect(() => {
|
|
3729
|
+
setIndex(items.length);
|
|
3730
|
+
}, [items.length]);
|
|
3731
|
+
const children = itemsToRender.map((item, itemIndex) => {
|
|
3732
|
+
return render(item, index + itemIndex);
|
|
3733
|
+
});
|
|
3734
|
+
const style = useMemo(() => ({
|
|
3735
|
+
position: "absolute",
|
|
3736
|
+
flexDirection: "column",
|
|
3737
|
+
...customStyle
|
|
3738
|
+
}), [customStyle]);
|
|
3739
|
+
return /* @__PURE__ */ jsx("ink-box", {
|
|
3740
|
+
internal_static: true,
|
|
3741
|
+
style,
|
|
3742
|
+
children
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
//#endregion
|
|
3746
|
+
//#region src/components/Transform.tsx
|
|
3747
|
+
/**
|
|
3748
|
+
Transform a string representation of React components before they're written to output. For example, you might want to apply a gradient to text, add a clickable link, or create some text effects. These use cases can't accept React nodes as input; they expect a string. That's what the <Transform> component does: it gives you an output string of its child components and lets you transform it in any way.
|
|
3749
|
+
*/
|
|
3750
|
+
function Transform({ children, transform, accessibilityLabel }) {
|
|
3751
|
+
const { isScreenReaderEnabled } = useContext(accessibilityContext);
|
|
3752
|
+
if (children === void 0 || children === null) return null;
|
|
3753
|
+
return /* @__PURE__ */ jsx("ink-text", {
|
|
3754
|
+
style: {
|
|
3755
|
+
flexGrow: 0,
|
|
3756
|
+
flexShrink: 1,
|
|
3757
|
+
flexDirection: "row"
|
|
3758
|
+
},
|
|
3759
|
+
internal_transform: transform,
|
|
3760
|
+
children: isScreenReaderEnabled && accessibilityLabel ? accessibilityLabel : children
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
//#endregion
|
|
3764
|
+
//#region src/components/Newline.tsx
|
|
3765
|
+
/**
|
|
3766
|
+
Adds one or more newline (`\n`) characters. Must be used within `<Text>` components.
|
|
3767
|
+
*/
|
|
3768
|
+
function Newline({ count = 1 }) {
|
|
3769
|
+
return /* @__PURE__ */ jsx("ink-text", { children: "\n".repeat(count) });
|
|
3770
|
+
}
|
|
3771
|
+
//#endregion
|
|
3772
|
+
//#region src/components/Spacer.tsx
|
|
3773
|
+
/**
|
|
3774
|
+
A flexible space that expands along the major axis of its containing layout.
|
|
3775
|
+
|
|
3776
|
+
It's useful as a shortcut for filling all the available space between elements.
|
|
3777
|
+
*/
|
|
3778
|
+
function Spacer() {
|
|
3779
|
+
return /* @__PURE__ */ jsx(Box, { flexGrow: 1 });
|
|
3780
|
+
}
|
|
3781
|
+
//#endregion
|
|
3782
|
+
//#region src/parse-keypress.ts
|
|
3783
|
+
const textDecoder = new TextDecoder();
|
|
3784
|
+
const metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
|
|
3785
|
+
const fnKeyRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
|
|
3786
|
+
const keyName = {
|
|
3787
|
+
OP: "f1",
|
|
3788
|
+
OQ: "f2",
|
|
3789
|
+
OR: "f3",
|
|
3790
|
+
OS: "f4",
|
|
3791
|
+
"[P": "f1",
|
|
3792
|
+
"[Q": "f2",
|
|
3793
|
+
"[R": "f3",
|
|
3794
|
+
"[S": "f4",
|
|
3795
|
+
"[11~": "f1",
|
|
3796
|
+
"[12~": "f2",
|
|
3797
|
+
"[13~": "f3",
|
|
3798
|
+
"[14~": "f4",
|
|
3799
|
+
"[[A": "f1",
|
|
3800
|
+
"[[B": "f2",
|
|
3801
|
+
"[[C": "f3",
|
|
3802
|
+
"[[D": "f4",
|
|
3803
|
+
"[[E": "f5",
|
|
3804
|
+
"[15~": "f5",
|
|
3805
|
+
"[17~": "f6",
|
|
3806
|
+
"[18~": "f7",
|
|
3807
|
+
"[19~": "f8",
|
|
3808
|
+
"[20~": "f9",
|
|
3809
|
+
"[21~": "f10",
|
|
3810
|
+
"[23~": "f11",
|
|
3811
|
+
"[24~": "f12",
|
|
3812
|
+
"[A": "up",
|
|
3813
|
+
"[B": "down",
|
|
3814
|
+
"[C": "right",
|
|
3815
|
+
"[D": "left",
|
|
3816
|
+
"[E": "clear",
|
|
3817
|
+
"[F": "end",
|
|
3818
|
+
"[H": "home",
|
|
3819
|
+
OA: "up",
|
|
3820
|
+
OB: "down",
|
|
3821
|
+
OC: "right",
|
|
3822
|
+
OD: "left",
|
|
3823
|
+
OE: "clear",
|
|
3824
|
+
OF: "end",
|
|
3825
|
+
OH: "home",
|
|
3826
|
+
"[1~": "home",
|
|
3827
|
+
"[2~": "insert",
|
|
3828
|
+
"[3~": "delete",
|
|
3829
|
+
"[4~": "end",
|
|
3830
|
+
"[5~": "pageup",
|
|
3831
|
+
"[6~": "pagedown",
|
|
3832
|
+
"[[5~": "pageup",
|
|
3833
|
+
"[[6~": "pagedown",
|
|
3834
|
+
"[7~": "home",
|
|
3835
|
+
"[8~": "end",
|
|
3836
|
+
"[a": "up",
|
|
3837
|
+
"[b": "down",
|
|
3838
|
+
"[c": "right",
|
|
3839
|
+
"[d": "left",
|
|
3840
|
+
"[e": "clear",
|
|
3841
|
+
"[2$": "insert",
|
|
3842
|
+
"[3$": "delete",
|
|
3843
|
+
"[5$": "pageup",
|
|
3844
|
+
"[6$": "pagedown",
|
|
3845
|
+
"[7$": "home",
|
|
3846
|
+
"[8$": "end",
|
|
3847
|
+
Oa: "up",
|
|
3848
|
+
Ob: "down",
|
|
3849
|
+
Oc: "right",
|
|
3850
|
+
Od: "left",
|
|
3851
|
+
Oe: "clear",
|
|
3852
|
+
"[2^": "insert",
|
|
3853
|
+
"[3^": "delete",
|
|
3854
|
+
"[5^": "pageup",
|
|
3855
|
+
"[6^": "pagedown",
|
|
3856
|
+
"[7^": "home",
|
|
3857
|
+
"[8^": "end",
|
|
3858
|
+
"[Z": "tab"
|
|
3859
|
+
};
|
|
3860
|
+
const nonAlphanumericKeys = [...Object.values(keyName), "backspace"];
|
|
3861
|
+
const isShiftKey = (code) => {
|
|
3862
|
+
return [
|
|
3863
|
+
"[a",
|
|
3864
|
+
"[b",
|
|
3865
|
+
"[c",
|
|
3866
|
+
"[d",
|
|
3867
|
+
"[e",
|
|
3868
|
+
"[2$",
|
|
3869
|
+
"[3$",
|
|
3870
|
+
"[5$",
|
|
3871
|
+
"[6$",
|
|
3872
|
+
"[7$",
|
|
3873
|
+
"[8$",
|
|
3874
|
+
"[Z"
|
|
3875
|
+
].includes(code);
|
|
3876
|
+
};
|
|
3877
|
+
const isCtrlKey = (code) => {
|
|
3878
|
+
return [
|
|
3879
|
+
"Oa",
|
|
3880
|
+
"Ob",
|
|
3881
|
+
"Oc",
|
|
3882
|
+
"Od",
|
|
3883
|
+
"Oe",
|
|
3884
|
+
"[2^",
|
|
3885
|
+
"[3^",
|
|
3886
|
+
"[5^",
|
|
3887
|
+
"[6^",
|
|
3888
|
+
"[7^",
|
|
3889
|
+
"[8^"
|
|
3890
|
+
].includes(code);
|
|
3891
|
+
};
|
|
3892
|
+
const kittyKeyRe = /^\x1b\[(\d+)(?:;(\d+)(?::(\d+))?(?:;([\d:]+))?)?u$/;
|
|
3893
|
+
const kittySpecialKeyRe = /^\x1b\[(\d+);(\d+):(\d+)([A-Za-z~])$/;
|
|
3894
|
+
const kittySpecialLetterKeys = {
|
|
3895
|
+
A: "up",
|
|
3896
|
+
B: "down",
|
|
3897
|
+
C: "right",
|
|
3898
|
+
D: "left",
|
|
3899
|
+
E: "clear",
|
|
3900
|
+
F: "end",
|
|
3901
|
+
H: "home",
|
|
3902
|
+
P: "f1",
|
|
3903
|
+
Q: "f2",
|
|
3904
|
+
R: "f3",
|
|
3905
|
+
S: "f4"
|
|
3906
|
+
};
|
|
3907
|
+
const kittySpecialNumberKeys = {
|
|
3908
|
+
2: "insert",
|
|
3909
|
+
3: "delete",
|
|
3910
|
+
5: "pageup",
|
|
3911
|
+
6: "pagedown",
|
|
3912
|
+
7: "home",
|
|
3913
|
+
8: "end",
|
|
3914
|
+
11: "f1",
|
|
3915
|
+
12: "f2",
|
|
3916
|
+
13: "f3",
|
|
3917
|
+
14: "f4",
|
|
3918
|
+
15: "f5",
|
|
3919
|
+
17: "f6",
|
|
3920
|
+
18: "f7",
|
|
3921
|
+
19: "f8",
|
|
3922
|
+
20: "f9",
|
|
3923
|
+
21: "f10",
|
|
3924
|
+
23: "f11",
|
|
3925
|
+
24: "f12"
|
|
3926
|
+
};
|
|
3927
|
+
const kittyCodepointNames = {
|
|
3928
|
+
27: "escape",
|
|
3929
|
+
9: "tab",
|
|
3930
|
+
127: "backspace",
|
|
3931
|
+
8: "backspace",
|
|
3932
|
+
57358: "capslock",
|
|
3933
|
+
57359: "scrolllock",
|
|
3934
|
+
57360: "numlock",
|
|
3935
|
+
57361: "printscreen",
|
|
3936
|
+
57362: "pause",
|
|
3937
|
+
57363: "menu",
|
|
3938
|
+
57376: "f13",
|
|
3939
|
+
57377: "f14",
|
|
3940
|
+
57378: "f15",
|
|
3941
|
+
57379: "f16",
|
|
3942
|
+
57380: "f17",
|
|
3943
|
+
57381: "f18",
|
|
3944
|
+
57382: "f19",
|
|
3945
|
+
57383: "f20",
|
|
3946
|
+
57384: "f21",
|
|
3947
|
+
57385: "f22",
|
|
3948
|
+
57386: "f23",
|
|
3949
|
+
57387: "f24",
|
|
3950
|
+
57388: "f25",
|
|
3951
|
+
57389: "f26",
|
|
3952
|
+
57390: "f27",
|
|
3953
|
+
57391: "f28",
|
|
3954
|
+
57392: "f29",
|
|
3955
|
+
57393: "f30",
|
|
3956
|
+
57394: "f31",
|
|
3957
|
+
57395: "f32",
|
|
3958
|
+
57396: "f33",
|
|
3959
|
+
57397: "f34",
|
|
3960
|
+
57398: "f35",
|
|
3961
|
+
57399: "kp0",
|
|
3962
|
+
57400: "kp1",
|
|
3963
|
+
57401: "kp2",
|
|
3964
|
+
57402: "kp3",
|
|
3965
|
+
57403: "kp4",
|
|
3966
|
+
57404: "kp5",
|
|
3967
|
+
57405: "kp6",
|
|
3968
|
+
57406: "kp7",
|
|
3969
|
+
57407: "kp8",
|
|
3970
|
+
57408: "kp9",
|
|
3971
|
+
57409: "kpdecimal",
|
|
3972
|
+
57410: "kpdivide",
|
|
3973
|
+
57411: "kpmultiply",
|
|
3974
|
+
57412: "kpsubtract",
|
|
3975
|
+
57413: "kpadd",
|
|
3976
|
+
57414: "kpenter",
|
|
3977
|
+
57415: "kpequal",
|
|
3978
|
+
57416: "kpseparator",
|
|
3979
|
+
57417: "kpleft",
|
|
3980
|
+
57418: "kpright",
|
|
3981
|
+
57419: "kpup",
|
|
3982
|
+
57420: "kpdown",
|
|
3983
|
+
57421: "kppageup",
|
|
3984
|
+
57422: "kppagedown",
|
|
3985
|
+
57423: "kphome",
|
|
3986
|
+
57424: "kpend",
|
|
3987
|
+
57425: "kpinsert",
|
|
3988
|
+
57426: "kpdelete",
|
|
3989
|
+
57427: "kpbegin",
|
|
3990
|
+
57428: "mediaplay",
|
|
3991
|
+
57429: "mediapause",
|
|
3992
|
+
57430: "mediaplaypause",
|
|
3993
|
+
57431: "mediareverse",
|
|
3994
|
+
57432: "mediastop",
|
|
3995
|
+
57433: "mediafastforward",
|
|
3996
|
+
57434: "mediarewind",
|
|
3997
|
+
57435: "mediatracknext",
|
|
3998
|
+
57436: "mediatrackprevious",
|
|
3999
|
+
57437: "mediarecord",
|
|
4000
|
+
57438: "lowervolume",
|
|
4001
|
+
57439: "raisevolume",
|
|
4002
|
+
57440: "mutevolume",
|
|
4003
|
+
57441: "leftshift",
|
|
4004
|
+
57442: "leftcontrol",
|
|
4005
|
+
57443: "leftalt",
|
|
4006
|
+
57444: "leftsuper",
|
|
4007
|
+
57445: "lefthyper",
|
|
4008
|
+
57446: "leftmeta",
|
|
4009
|
+
57447: "rightshift",
|
|
4010
|
+
57448: "rightcontrol",
|
|
4011
|
+
57449: "rightalt",
|
|
4012
|
+
57450: "rightsuper",
|
|
4013
|
+
57451: "righthyper",
|
|
4014
|
+
57452: "rightmeta",
|
|
4015
|
+
57453: "isoLevel3Shift",
|
|
4016
|
+
57454: "isoLevel5Shift"
|
|
4017
|
+
};
|
|
4018
|
+
const isValidCodepoint = (cp) => cp >= 0 && cp <= 1114111 && !(cp >= 55296 && cp <= 57343);
|
|
4019
|
+
const safeFromCodePoint = (cp) => isValidCodepoint(cp) ? String.fromCodePoint(cp) : "?";
|
|
4020
|
+
function resolveEventType(value) {
|
|
4021
|
+
if (value === 3) return "release";
|
|
4022
|
+
if (value === 2) return "repeat";
|
|
4023
|
+
return "press";
|
|
4024
|
+
}
|
|
4025
|
+
function parseKittyModifiers(modifiers) {
|
|
4026
|
+
return {
|
|
4027
|
+
ctrl: !!(modifiers & kittyModifiers.ctrl),
|
|
4028
|
+
shift: !!(modifiers & kittyModifiers.shift),
|
|
4029
|
+
meta: !!(modifiers & (kittyModifiers.meta | kittyModifiers.alt)),
|
|
4030
|
+
super: !!(modifiers & kittyModifiers.super),
|
|
4031
|
+
hyper: !!(modifiers & kittyModifiers.hyper),
|
|
4032
|
+
capsLock: !!(modifiers & kittyModifiers.capsLock),
|
|
4033
|
+
numLock: !!(modifiers & kittyModifiers.numLock)
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
const parseKittyKeypress = (s) => {
|
|
4037
|
+
const match = kittyKeyRe.exec(s);
|
|
4038
|
+
if (!match) return null;
|
|
4039
|
+
const codepoint = parseInt(match[1], 10);
|
|
4040
|
+
const modifiers = match[2] ? Math.max(0, parseInt(match[2], 10) - 1) : 0;
|
|
4041
|
+
const eventType = match[3] ? parseInt(match[3], 10) : 1;
|
|
4042
|
+
const textField = match[4];
|
|
4043
|
+
if (!isValidCodepoint(codepoint)) return null;
|
|
4044
|
+
let text;
|
|
4045
|
+
if (textField) text = textField.split(":").map((cp) => safeFromCodePoint(parseInt(cp, 10))).join("");
|
|
4046
|
+
let name;
|
|
4047
|
+
let isPrintable;
|
|
4048
|
+
if (codepoint === 32) {
|
|
4049
|
+
name = "space";
|
|
4050
|
+
isPrintable = true;
|
|
4051
|
+
} else if (codepoint === 13) {
|
|
4052
|
+
name = "return";
|
|
4053
|
+
isPrintable = true;
|
|
4054
|
+
} else if (kittyCodepointNames[codepoint]) {
|
|
4055
|
+
name = kittyCodepointNames[codepoint];
|
|
4056
|
+
isPrintable = false;
|
|
4057
|
+
} else if (codepoint >= 1 && codepoint <= 26) {
|
|
4058
|
+
name = String.fromCodePoint(codepoint + 96);
|
|
4059
|
+
isPrintable = false;
|
|
4060
|
+
} else {
|
|
4061
|
+
name = safeFromCodePoint(codepoint).toLowerCase();
|
|
4062
|
+
isPrintable = true;
|
|
4063
|
+
}
|
|
4064
|
+
if (isPrintable && !text) text = safeFromCodePoint(codepoint);
|
|
4065
|
+
return {
|
|
4066
|
+
name,
|
|
4067
|
+
...parseKittyModifiers(modifiers),
|
|
4068
|
+
eventType: resolveEventType(eventType),
|
|
4069
|
+
sequence: s,
|
|
4070
|
+
raw: s,
|
|
4071
|
+
isKittyProtocol: true,
|
|
4072
|
+
isPrintable,
|
|
4073
|
+
text
|
|
4074
|
+
};
|
|
4075
|
+
};
|
|
4076
|
+
const parseKittySpecialKey = (s) => {
|
|
4077
|
+
const match = kittySpecialKeyRe.exec(s);
|
|
4078
|
+
if (!match) return null;
|
|
4079
|
+
const number = parseInt(match[1], 10);
|
|
4080
|
+
const modifiers = Math.max(0, parseInt(match[2], 10) - 1);
|
|
4081
|
+
const eventType = parseInt(match[3], 10);
|
|
4082
|
+
const terminator = match[4];
|
|
4083
|
+
const name = terminator === "~" ? kittySpecialNumberKeys[number] : kittySpecialLetterKeys[terminator];
|
|
4084
|
+
if (!name) return null;
|
|
4085
|
+
return {
|
|
4086
|
+
name,
|
|
4087
|
+
...parseKittyModifiers(modifiers),
|
|
4088
|
+
eventType: resolveEventType(eventType),
|
|
4089
|
+
sequence: s,
|
|
4090
|
+
raw: s,
|
|
4091
|
+
isKittyProtocol: true,
|
|
4092
|
+
isPrintable: false
|
|
4093
|
+
};
|
|
4094
|
+
};
|
|
4095
|
+
const parseKeypress = (s = "") => {
|
|
4096
|
+
let parts;
|
|
4097
|
+
if (s instanceof Uint8Array) {
|
|
4098
|
+
if (s[0] > 127 && s[1] === void 0) {
|
|
4099
|
+
s[0] -= 128;
|
|
4100
|
+
s = "\x1B" + textDecoder.decode(s);
|
|
4101
|
+
} else s = textDecoder.decode(s);
|
|
4102
|
+
} else if (s !== void 0 && typeof s !== "string") s = String(s);
|
|
4103
|
+
else if (!s) s = "";
|
|
4104
|
+
const kittyResult = parseKittyKeypress(s);
|
|
4105
|
+
if (kittyResult) return kittyResult;
|
|
4106
|
+
const kittySpecialResult = parseKittySpecialKey(s);
|
|
4107
|
+
if (kittySpecialResult) return kittySpecialResult;
|
|
4108
|
+
if (kittyKeyRe.test(s)) return {
|
|
4109
|
+
name: "",
|
|
4110
|
+
ctrl: false,
|
|
4111
|
+
meta: false,
|
|
4112
|
+
shift: false,
|
|
4113
|
+
sequence: s,
|
|
4114
|
+
raw: s,
|
|
4115
|
+
isKittyProtocol: true,
|
|
4116
|
+
isPrintable: false
|
|
4117
|
+
};
|
|
4118
|
+
const key = {
|
|
4119
|
+
name: "",
|
|
4120
|
+
ctrl: false,
|
|
4121
|
+
meta: false,
|
|
4122
|
+
shift: false,
|
|
4123
|
+
sequence: s,
|
|
4124
|
+
raw: s
|
|
4125
|
+
};
|
|
4126
|
+
key.sequence = key.sequence || s || key.name;
|
|
4127
|
+
if (s === "\r" || s === "\x1B\r") {
|
|
4128
|
+
key.raw = void 0;
|
|
4129
|
+
key.name = "return";
|
|
4130
|
+
key.meta = s.length === 2;
|
|
4131
|
+
} else if (s === "\n") key.name = "enter";
|
|
4132
|
+
else if (s === " ") key.name = "tab";
|
|
4133
|
+
else if (s === "\b" || s === "\x1B\b") {
|
|
4134
|
+
key.name = "backspace";
|
|
4135
|
+
key.meta = s.charAt(0) === "\x1B";
|
|
4136
|
+
} else if (s === "" || s === "\x1B") {
|
|
4137
|
+
key.name = "backspace";
|
|
4138
|
+
key.meta = s.charAt(0) === "\x1B";
|
|
4139
|
+
} else if (s === "\x1B" || s === "\x1B\x1B") {
|
|
4140
|
+
key.name = "escape";
|
|
4141
|
+
key.meta = s.length === 2;
|
|
4142
|
+
} else if (s === " " || s === "\x1B ") {
|
|
4143
|
+
key.name = "space";
|
|
4144
|
+
key.meta = s.length === 2;
|
|
4145
|
+
} else if (s.length === 1 && s <= "") {
|
|
4146
|
+
key.name = String.fromCharCode(s.charCodeAt(0) + "a".charCodeAt(0) - 1);
|
|
4147
|
+
key.ctrl = true;
|
|
4148
|
+
} else if (s.length === 1 && s >= "0" && s <= "9") key.name = "number";
|
|
4149
|
+
else if (s.length === 1 && s >= "a" && s <= "z") key.name = s;
|
|
4150
|
+
else if (s.length === 1 && s >= "A" && s <= "Z") {
|
|
4151
|
+
key.name = s.toLowerCase();
|
|
4152
|
+
key.shift = true;
|
|
4153
|
+
} else if (parts = metaKeyCodeRe.exec(s)) {
|
|
4154
|
+
key.name = parts[1].toLowerCase();
|
|
4155
|
+
key.meta = true;
|
|
4156
|
+
key.shift = /^[A-Z]$/.test(parts[1]);
|
|
4157
|
+
} else if (parts = fnKeyRe.exec(s)) {
|
|
4158
|
+
const segs = [...s];
|
|
4159
|
+
if (segs[0] === "\x1B" && segs[1] === "\x1B") key.meta = true;
|
|
4160
|
+
const code = [
|
|
4161
|
+
parts[1],
|
|
4162
|
+
parts[2],
|
|
4163
|
+
parts[4],
|
|
4164
|
+
parts[6]
|
|
4165
|
+
].filter(Boolean).join("");
|
|
4166
|
+
const modifier = (parts[3] || parts[5] || 1) - 1;
|
|
4167
|
+
key.ctrl = !!(modifier & 4);
|
|
4168
|
+
key.meta = key.meta || !!(modifier & 10);
|
|
4169
|
+
key.shift = !!(modifier & 1);
|
|
4170
|
+
key.code = code;
|
|
4171
|
+
key.name = keyName[code] ?? "";
|
|
4172
|
+
key.shift = isShiftKey(code) || key.shift;
|
|
4173
|
+
key.ctrl = isCtrlKey(code) || key.ctrl;
|
|
4174
|
+
}
|
|
4175
|
+
return key;
|
|
4176
|
+
};
|
|
4177
|
+
//#endregion
|
|
4178
|
+
//#region src/hooks/use-stdin.ts
|
|
4179
|
+
/**
|
|
4180
|
+
A React hook that returns the stdin stream and stdin-related utilities.
|
|
4181
|
+
*/
|
|
4182
|
+
const useStdin = () => useContext(StdinContext);
|
|
4183
|
+
const useStdinContext = () => useContext(StdinContext);
|
|
4184
|
+
//#endregion
|
|
4185
|
+
//#region src/hooks/use-input.ts
|
|
4186
|
+
/**
|
|
4187
|
+
A React hook that returns `void` and handles user input.
|
|
4188
|
+
It's a more convenient alternative to using `StdinContext` and listening for `data` events. The callback you pass to `useInput` is called for each character when the user enters any input. However, if the user pastes text and it's more than one character, the callback will be called only once, and the whole string will be passed as `input`.
|
|
4189
|
+
|
|
4190
|
+
```
|
|
4191
|
+
import {useInput} from 'ink';
|
|
4192
|
+
|
|
4193
|
+
const UserInput = () => {
|
|
4194
|
+
useInput((input, key) => {
|
|
4195
|
+
if (input === 'q') {
|
|
4196
|
+
// Exit program
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
if (key.leftArrow) {
|
|
4200
|
+
// Left arrow key pressed
|
|
4201
|
+
}
|
|
4202
|
+
});
|
|
4203
|
+
|
|
4204
|
+
return …
|
|
4205
|
+
};
|
|
4206
|
+
```
|
|
4207
|
+
*/
|
|
4208
|
+
const useInput = (inputHandler, options = {}) => {
|
|
4209
|
+
const { setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdinContext();
|
|
4210
|
+
useEffect(() => {
|
|
4211
|
+
if (options.isActive === false) return;
|
|
4212
|
+
setRawMode(true);
|
|
4213
|
+
return () => {
|
|
4214
|
+
setRawMode(false);
|
|
4215
|
+
};
|
|
4216
|
+
}, [options.isActive, setRawMode]);
|
|
4217
|
+
const handleData = useEffectEvent((data) => {
|
|
4218
|
+
const keypress = parseKeypress(data);
|
|
4219
|
+
const key = {
|
|
4220
|
+
upArrow: keypress.name === "up",
|
|
4221
|
+
downArrow: keypress.name === "down",
|
|
4222
|
+
leftArrow: keypress.name === "left",
|
|
4223
|
+
rightArrow: keypress.name === "right",
|
|
4224
|
+
pageDown: keypress.name === "pagedown",
|
|
4225
|
+
pageUp: keypress.name === "pageup",
|
|
4226
|
+
home: keypress.name === "home",
|
|
4227
|
+
end: keypress.name === "end",
|
|
4228
|
+
return: keypress.name === "return",
|
|
4229
|
+
escape: keypress.name === "escape",
|
|
4230
|
+
ctrl: keypress.ctrl,
|
|
4231
|
+
shift: keypress.shift,
|
|
4232
|
+
tab: keypress.name === "tab",
|
|
4233
|
+
backspace: keypress.name === "backspace",
|
|
4234
|
+
delete: keypress.name === "delete",
|
|
4235
|
+
meta: keypress.meta,
|
|
4236
|
+
super: keypress.super ?? false,
|
|
4237
|
+
hyper: keypress.hyper ?? false,
|
|
4238
|
+
capsLock: keypress.capsLock ?? false,
|
|
4239
|
+
numLock: keypress.numLock ?? false,
|
|
4240
|
+
eventType: keypress.eventType
|
|
4241
|
+
};
|
|
4242
|
+
let input;
|
|
4243
|
+
if (keypress.isKittyProtocol) {
|
|
4244
|
+
if (keypress.isPrintable) input = keypress.text ?? keypress.name;
|
|
4245
|
+
else if (keypress.ctrl && keypress.name.length === 1) input = keypress.name;
|
|
4246
|
+
else input = "";
|
|
4247
|
+
} else if (keypress.ctrl) input = keypress.name ?? "";
|
|
4248
|
+
else input = keypress.sequence;
|
|
4249
|
+
if (!keypress.isKittyProtocol && nonAlphanumericKeys.includes(keypress.name)) input = "";
|
|
4250
|
+
if (input.startsWith("\x1B")) input = input.slice(1);
|
|
4251
|
+
if (input.length === 1 && /[A-Z]/.test(input)) key.shift = true;
|
|
4252
|
+
if (input === "c" && key.ctrl && internal_exitOnCtrlC) return;
|
|
4253
|
+
reconciler.discreteUpdates(() => {
|
|
4254
|
+
inputHandler(input, key);
|
|
4255
|
+
});
|
|
4256
|
+
});
|
|
4257
|
+
useEffect(() => {
|
|
4258
|
+
if (options.isActive === false) return;
|
|
4259
|
+
internal_eventEmitter.on("input", handleData);
|
|
4260
|
+
return () => {
|
|
4261
|
+
internal_eventEmitter.removeListener("input", handleData);
|
|
4262
|
+
};
|
|
4263
|
+
}, [options.isActive, internal_eventEmitter]);
|
|
4264
|
+
};
|
|
4265
|
+
//#endregion
|
|
4266
|
+
//#region src/hooks/use-paste.ts
|
|
4267
|
+
/**
|
|
4268
|
+
A React hook that calls `handler` whenever the user pastes text in the terminal. Bracketed paste mode (`\x1b[?2004h`) is automatically enabled while the hook is active, so pasted text arrives as a single string rather than being misinterpreted as individual key presses.
|
|
4269
|
+
|
|
4270
|
+
`usePaste` and `useInput` can be used together in the same component. They operate on separate event channels, so paste content is never forwarded to `useInput` handlers when `usePaste` is active.
|
|
4271
|
+
|
|
4272
|
+
```
|
|
4273
|
+
import {useInput, usePaste} from 'ink';
|
|
4274
|
+
|
|
4275
|
+
const MyInput = () => {
|
|
4276
|
+
useInput((input, key) => {
|
|
4277
|
+
// Only receives typed characters and key events, not pasted text.
|
|
4278
|
+
if (key.return) {
|
|
4279
|
+
// Submit
|
|
4280
|
+
}
|
|
4281
|
+
});
|
|
4282
|
+
|
|
4283
|
+
usePaste((text) => {
|
|
4284
|
+
// Receives the full pasted string, including newlines.
|
|
4285
|
+
console.log('Pasted:', text);
|
|
4286
|
+
});
|
|
4287
|
+
|
|
4288
|
+
return …
|
|
4289
|
+
};
|
|
4290
|
+
```
|
|
4291
|
+
*/
|
|
4292
|
+
const usePaste = (handler, options = {}) => {
|
|
4293
|
+
const { setRawMode, setBracketedPasteMode, internal_eventEmitter } = useStdinContext();
|
|
4294
|
+
useEffect(() => {
|
|
4295
|
+
if (options.isActive === false) return;
|
|
4296
|
+
setRawMode(true);
|
|
4297
|
+
setBracketedPasteMode(true);
|
|
4298
|
+
return () => {
|
|
4299
|
+
setRawMode(false);
|
|
4300
|
+
setBracketedPasteMode(false);
|
|
4301
|
+
};
|
|
4302
|
+
}, [
|
|
4303
|
+
options.isActive,
|
|
4304
|
+
setRawMode,
|
|
4305
|
+
setBracketedPasteMode
|
|
4306
|
+
]);
|
|
4307
|
+
const handlePaste = useEffectEvent((text) => {
|
|
4308
|
+
reconciler.discreteUpdates(() => {
|
|
4309
|
+
handler(text);
|
|
4310
|
+
});
|
|
4311
|
+
});
|
|
4312
|
+
useEffect(() => {
|
|
4313
|
+
if (options.isActive === false) return;
|
|
4314
|
+
internal_eventEmitter.on("paste", handlePaste);
|
|
4315
|
+
return () => {
|
|
4316
|
+
internal_eventEmitter.removeListener("paste", handlePaste);
|
|
4317
|
+
};
|
|
4318
|
+
}, [options.isActive, internal_eventEmitter]);
|
|
4319
|
+
};
|
|
4320
|
+
//#endregion
|
|
4321
|
+
//#region src/hooks/use-app.ts
|
|
4322
|
+
/**
|
|
4323
|
+
A React hook that returns app lifecycle methods like `exit()` and `waitUntilRenderFlush()`.
|
|
4324
|
+
*/
|
|
4325
|
+
const useApp = () => useContext(AppContext);
|
|
4326
|
+
//#endregion
|
|
4327
|
+
//#region src/hooks/use-stdout.ts
|
|
4328
|
+
/**
|
|
4329
|
+
A React hook that returns the stdout stream where Ink renders your app.
|
|
4330
|
+
*/
|
|
4331
|
+
const useStdout = () => useContext(StdoutContext);
|
|
4332
|
+
//#endregion
|
|
4333
|
+
//#region src/hooks/use-stderr.ts
|
|
4334
|
+
/**
|
|
4335
|
+
A React hook that returns the stderr stream.
|
|
4336
|
+
*/
|
|
4337
|
+
const useStderr = () => useContext(StderrContext);
|
|
4338
|
+
//#endregion
|
|
4339
|
+
//#region src/hooks/use-focus.ts
|
|
4340
|
+
/**
|
|
4341
|
+
A React hook that returns focus state and focus controls for the current component.
|
|
4342
|
+
A component that uses the `useFocus` hook becomes "focusable" to Ink, so when the user presses <kbd>Tab</kbd>, Ink will switch focus to this component. If there are multiple components that execute the `useFocus` hook, focus will be given to them in the order in which these components are rendered.
|
|
4343
|
+
*/
|
|
4344
|
+
const useFocus = ({ isActive = true, autoFocus = false, id: customId } = {}) => {
|
|
4345
|
+
const { isRawModeSupported, setRawMode } = useStdin();
|
|
4346
|
+
const { activeId, add, remove, activate, deactivate, focus } = useContext(FocusContext);
|
|
4347
|
+
const id = useMemo(() => {
|
|
4348
|
+
return customId ?? Math.random().toString().slice(2, 7);
|
|
4349
|
+
}, [customId]);
|
|
4350
|
+
useEffect(() => {
|
|
4351
|
+
add(id, { autoFocus });
|
|
4352
|
+
return () => {
|
|
4353
|
+
remove(id);
|
|
4354
|
+
};
|
|
4355
|
+
}, [
|
|
4356
|
+
id,
|
|
4357
|
+
autoFocus,
|
|
4358
|
+
add,
|
|
4359
|
+
remove
|
|
4360
|
+
]);
|
|
4361
|
+
useEffect(() => {
|
|
4362
|
+
if (isActive) activate(id);
|
|
4363
|
+
else deactivate(id);
|
|
4364
|
+
}, [
|
|
4365
|
+
isActive,
|
|
4366
|
+
id,
|
|
4367
|
+
activate,
|
|
4368
|
+
deactivate
|
|
4369
|
+
]);
|
|
4370
|
+
useEffect(() => {
|
|
4371
|
+
if (!isRawModeSupported || !isActive) return;
|
|
4372
|
+
setRawMode(true);
|
|
4373
|
+
return () => {
|
|
4374
|
+
setRawMode(false);
|
|
4375
|
+
};
|
|
4376
|
+
}, [
|
|
4377
|
+
isActive,
|
|
4378
|
+
isRawModeSupported,
|
|
4379
|
+
setRawMode
|
|
4380
|
+
]);
|
|
4381
|
+
return {
|
|
4382
|
+
isFocused: Boolean(id) && activeId === id,
|
|
4383
|
+
focus
|
|
4384
|
+
};
|
|
4385
|
+
};
|
|
4386
|
+
//#endregion
|
|
4387
|
+
//#region src/hooks/use-focus-manager.ts
|
|
4388
|
+
/**
|
|
4389
|
+
A React hook that returns methods to enable or disable focus management for all components or manually switch focus to the next or previous components.
|
|
4390
|
+
*/
|
|
4391
|
+
const useFocusManager = () => {
|
|
4392
|
+
const focusContext = useContext(FocusContext);
|
|
4393
|
+
return {
|
|
4394
|
+
enableFocus: focusContext.enableFocus,
|
|
4395
|
+
disableFocus: focusContext.disableFocus,
|
|
4396
|
+
focusNext: focusContext.focusNext,
|
|
4397
|
+
focusPrevious: focusContext.focusPrevious,
|
|
4398
|
+
focus: focusContext.focus,
|
|
4399
|
+
activeId: focusContext.activeId
|
|
4400
|
+
};
|
|
4401
|
+
};
|
|
4402
|
+
//#endregion
|
|
4403
|
+
//#region src/hooks/use-is-screen-reader-enabled.ts
|
|
4404
|
+
/**
|
|
4405
|
+
A React hook that returns whether a screen reader is enabled.
|
|
4406
|
+
This is useful when you want to render different output for screen readers.
|
|
4407
|
+
*/
|
|
4408
|
+
const useIsScreenReaderEnabled = () => {
|
|
4409
|
+
const { isScreenReaderEnabled } = useContext(accessibilityContext);
|
|
4410
|
+
return isScreenReaderEnabled;
|
|
4411
|
+
};
|
|
4412
|
+
//#endregion
|
|
4413
|
+
//#region src/hooks/use-cursor.ts
|
|
4414
|
+
/**
|
|
4415
|
+
A React hook that returns methods to control the terminal cursor position.
|
|
4416
|
+
|
|
4417
|
+
Setting a cursor position makes the cursor visible at the specified coordinates (relative to the Ink output origin). This is useful for IME (Input Method Editor) support, where the composing character is displayed at the cursor location.
|
|
4418
|
+
|
|
4419
|
+
Pass `undefined` to hide the cursor.
|
|
4420
|
+
*/
|
|
4421
|
+
const useCursor = () => {
|
|
4422
|
+
const context = useContext(CursorContext);
|
|
4423
|
+
const positionRef = useRef(void 0);
|
|
4424
|
+
const setCursorPosition = useCallback((position) => {
|
|
4425
|
+
positionRef.current = position;
|
|
4426
|
+
}, []);
|
|
4427
|
+
useInsertionEffect(() => {
|
|
4428
|
+
context.setCursorPosition(positionRef.current);
|
|
4429
|
+
return () => {
|
|
4430
|
+
context.setCursorPosition(void 0);
|
|
4431
|
+
};
|
|
4432
|
+
});
|
|
4433
|
+
return { setCursorPosition };
|
|
4434
|
+
};
|
|
4435
|
+
//#endregion
|
|
4436
|
+
//#region src/hooks/use-animation.ts
|
|
4437
|
+
const defaultAnimationInterval = 100;
|
|
4438
|
+
const maximumTimerInterval = 2147483647;
|
|
4439
|
+
const zeroAnimState = {
|
|
4440
|
+
frame: 0,
|
|
4441
|
+
time: 0,
|
|
4442
|
+
delta: 0
|
|
4443
|
+
};
|
|
4444
|
+
/**
|
|
4445
|
+
A React hook that drives animations. Returns a frame counter, elapsed time, frame delta, and a reset function. All animations share a single timer internally, so multiple animated components consolidate into one render cycle.
|
|
4446
|
+
|
|
4447
|
+
@example
|
|
4448
|
+
```
|
|
4449
|
+
import {Text, useAnimation} from 'ink';
|
|
4450
|
+
|
|
4451
|
+
const Spinner = () => {
|
|
4452
|
+
const {frame} = useAnimation({interval: 80});
|
|
4453
|
+
const characters = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
4454
|
+
|
|
4455
|
+
return <Text>{characters[frame % characters.length]}</Text>;
|
|
4456
|
+
};
|
|
4457
|
+
```
|
|
4458
|
+
*/
|
|
4459
|
+
function useAnimation(options) {
|
|
4460
|
+
const { interval = defaultAnimationInterval, isActive = true } = options ?? {};
|
|
4461
|
+
const safeInterval = normalizeAnimationInterval(interval);
|
|
4462
|
+
const { subscribe, renderThrottleMs } = useContext(animationContext);
|
|
4463
|
+
const [resetKey, setResetKey] = useState(0);
|
|
4464
|
+
const [animState, setAnimState] = useState(zeroAnimState);
|
|
4465
|
+
const nextRenderTimeRef = useRef(0);
|
|
4466
|
+
const lastRenderTimeRef = useRef(0);
|
|
4467
|
+
const previousOptionsRef = useRef({
|
|
4468
|
+
isActive,
|
|
4469
|
+
safeInterval,
|
|
4470
|
+
resetKey
|
|
4471
|
+
});
|
|
4472
|
+
const previousOptions = previousOptionsRef.current;
|
|
4473
|
+
const shouldReset = isActive && (safeInterval !== previousOptions.safeInterval || !previousOptions.isActive || resetKey !== previousOptions.resetKey);
|
|
4474
|
+
const reset = useCallback(() => {
|
|
4475
|
+
setResetKey((k) => k + 1);
|
|
4476
|
+
}, []);
|
|
4477
|
+
useLayoutEffect(() => {
|
|
4478
|
+
if (!isActive) return;
|
|
4479
|
+
setAnimState(zeroAnimState);
|
|
4480
|
+
let startTime = 0;
|
|
4481
|
+
const { startTime: subscriberStartTime, unsubscribe } = subscribe((currentTime) => {
|
|
4482
|
+
if (renderThrottleMs > 0 && currentTime < nextRenderTimeRef.current) return;
|
|
4483
|
+
const elapsed = currentTime - startTime;
|
|
4484
|
+
const nextDelta = currentTime - lastRenderTimeRef.current;
|
|
4485
|
+
lastRenderTimeRef.current = currentTime;
|
|
4486
|
+
nextRenderTimeRef.current = currentTime + renderThrottleMs;
|
|
4487
|
+
setAnimState({
|
|
4488
|
+
frame: Math.floor(elapsed / safeInterval),
|
|
4489
|
+
time: elapsed,
|
|
4490
|
+
delta: nextDelta
|
|
4491
|
+
});
|
|
4492
|
+
}, safeInterval);
|
|
4493
|
+
startTime = subscriberStartTime;
|
|
4494
|
+
lastRenderTimeRef.current = subscriberStartTime;
|
|
4495
|
+
nextRenderTimeRef.current = startTime + renderThrottleMs;
|
|
4496
|
+
return unsubscribe;
|
|
4497
|
+
}, [
|
|
4498
|
+
safeInterval,
|
|
4499
|
+
isActive,
|
|
4500
|
+
subscribe,
|
|
4501
|
+
renderThrottleMs,
|
|
4502
|
+
resetKey
|
|
4503
|
+
]);
|
|
4504
|
+
useLayoutEffect(() => {
|
|
4505
|
+
previousOptionsRef.current = {
|
|
4506
|
+
isActive,
|
|
4507
|
+
safeInterval,
|
|
4508
|
+
resetKey
|
|
4509
|
+
};
|
|
4510
|
+
}, [
|
|
4511
|
+
isActive,
|
|
4512
|
+
safeInterval,
|
|
4513
|
+
resetKey
|
|
4514
|
+
]);
|
|
4515
|
+
if (shouldReset) return {
|
|
4516
|
+
...zeroAnimState,
|
|
4517
|
+
reset
|
|
4518
|
+
};
|
|
4519
|
+
return {
|
|
4520
|
+
...animState,
|
|
4521
|
+
reset
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4524
|
+
function normalizeAnimationInterval(interval) {
|
|
4525
|
+
if (!Number.isFinite(interval)) return defaultAnimationInterval;
|
|
4526
|
+
return Math.min(maximumTimerInterval, Math.max(1, interval));
|
|
4527
|
+
}
|
|
4528
|
+
//#endregion
|
|
4529
|
+
//#region src/hooks/use-window-size.ts
|
|
4530
|
+
/**
|
|
4531
|
+
A React hook that returns the current terminal window dimensions and re-renders the component whenever the terminal is resized.
|
|
4532
|
+
*/
|
|
4533
|
+
const useWindowSize = () => {
|
|
4534
|
+
const { stdout } = useStdout();
|
|
4535
|
+
const [size, setSize] = useState(() => getWindowSize(stdout));
|
|
4536
|
+
useEffect(() => {
|
|
4537
|
+
const onResize = () => {
|
|
4538
|
+
setSize(getWindowSize(stdout));
|
|
4539
|
+
};
|
|
4540
|
+
stdout.on("resize", onResize);
|
|
4541
|
+
return () => {
|
|
4542
|
+
stdout.off("resize", onResize);
|
|
4543
|
+
};
|
|
4544
|
+
}, [stdout]);
|
|
4545
|
+
return size;
|
|
4546
|
+
};
|
|
4547
|
+
//#endregion
|
|
4548
|
+
//#region src/hooks/use-box-metrics.ts
|
|
4549
|
+
const emptyMetrics = {
|
|
4550
|
+
width: 0,
|
|
4551
|
+
height: 0,
|
|
4552
|
+
left: 0,
|
|
4553
|
+
top: 0
|
|
4554
|
+
};
|
|
4555
|
+
const findRootNode = (node) => {
|
|
4556
|
+
if (!node) return;
|
|
4557
|
+
if (!node.parentNode) return node.nodeName === "ink-root" ? node : void 0;
|
|
4558
|
+
return findRootNode(node.parentNode);
|
|
4559
|
+
};
|
|
4560
|
+
/**
|
|
4561
|
+
A React hook that returns the current layout metrics for a tracked box element.
|
|
4562
|
+
It updates when layout changes (for example terminal resize, sibling/content changes, or position changes).
|
|
4563
|
+
|
|
4564
|
+
The hook returns `{width: 0, height: 0, left: 0, top: 0}` until the first layout pass completes. It also returns zeros when the tracked ref is detached.
|
|
4565
|
+
|
|
4566
|
+
Use `hasMeasured` to detect when the currently tracked element has been measured.
|
|
4567
|
+
|
|
4568
|
+
@example
|
|
4569
|
+
```tsx
|
|
4570
|
+
import {useRef} from 'react';
|
|
4571
|
+
import {Box, Text, useBoxMetrics} from 'ink';
|
|
4572
|
+
|
|
4573
|
+
const Example = () => {
|
|
4574
|
+
const ref = useRef(null);
|
|
4575
|
+
const {width, height, left, top, hasMeasured} = useBoxMetrics(ref);
|
|
4576
|
+
return (
|
|
4577
|
+
<Box ref={ref}>
|
|
4578
|
+
<Text>
|
|
4579
|
+
{hasMeasured ? `${width}x${height} at ${left},${top}` : 'Measuring...'}
|
|
4580
|
+
</Text>
|
|
4581
|
+
</Box>
|
|
4582
|
+
);
|
|
4583
|
+
};
|
|
4584
|
+
```
|
|
4585
|
+
*/
|
|
4586
|
+
const useBoxMetrics = (ref) => {
|
|
4587
|
+
const [metrics, setMetrics] = useState(emptyMetrics);
|
|
4588
|
+
const [hasMeasured, setHasMeasured] = useState(false);
|
|
4589
|
+
const updateMetrics = useCallback(() => {
|
|
4590
|
+
const layout = ref.current?.yogaNode?.getComputedLayout() ?? emptyMetrics;
|
|
4591
|
+
setMetrics((previousMetrics) => {
|
|
4592
|
+
return previousMetrics.width !== layout.width || previousMetrics.height !== layout.height || previousMetrics.left !== layout.left || previousMetrics.top !== layout.top ? layout : previousMetrics;
|
|
4593
|
+
});
|
|
4594
|
+
setHasMeasured(Boolean(ref.current));
|
|
4595
|
+
}, [ref]);
|
|
4596
|
+
useEffect(updateMetrics);
|
|
4597
|
+
useEffect(() => {
|
|
4598
|
+
const rootNode = findRootNode(ref.current);
|
|
4599
|
+
if (!rootNode) return;
|
|
4600
|
+
return addLayoutListener(rootNode, updateMetrics);
|
|
4601
|
+
});
|
|
4602
|
+
return useMemo(() => ({
|
|
4603
|
+
...metrics,
|
|
4604
|
+
hasMeasured
|
|
4605
|
+
}), [metrics, hasMeasured]);
|
|
4606
|
+
};
|
|
4607
|
+
//#endregion
|
|
4608
|
+
//#region src/measure-element.ts
|
|
4609
|
+
/**
|
|
4610
|
+
Measure the layout metrics of a particular `<Box>` element.
|
|
4611
|
+
Returns an object with `x`, `y`, `width`, and `height` properties.
|
|
4612
|
+
|
|
4613
|
+
`x` and `y` are the element's position within the live layout region, computed by walking up the layout tree and accumulating each ancestor's offset. These are layout-tree coordinates, not terminal viewport coordinates. To compare them with mouse events, convert the event coordinates using the live region's viewport position. This is necessary even in alternate-screen mode when output, such as `<Static>` content, appears above the live region.
|
|
4614
|
+
|
|
4615
|
+
Note: `measureElement()` returns `{x: 0, y: 0, width: 0, height: 0}` when called during render (before layout is calculated). Call it from post-render code, such as `useEffect`, `useLayoutEffect`, input handlers, or timer callbacks. When content changes, pass the relevant dependency to your effect so it re-measures after each update.
|
|
4616
|
+
*/
|
|
4617
|
+
const measureElement = (node) => {
|
|
4618
|
+
const { yogaNode } = node;
|
|
4619
|
+
if (!yogaNode) return {
|
|
4620
|
+
x: 0,
|
|
4621
|
+
y: 0,
|
|
4622
|
+
width: 0,
|
|
4623
|
+
height: 0
|
|
4624
|
+
};
|
|
4625
|
+
let x = yogaNode.getComputedLeft();
|
|
4626
|
+
let y = yogaNode.getComputedTop();
|
|
4627
|
+
let current = node.parentNode;
|
|
4628
|
+
while (current) {
|
|
4629
|
+
if (current.yogaNode) {
|
|
4630
|
+
x += current.yogaNode.getComputedLeft();
|
|
4631
|
+
y += current.yogaNode.getComputedTop();
|
|
4632
|
+
}
|
|
4633
|
+
current = current.parentNode;
|
|
4634
|
+
}
|
|
4635
|
+
return {
|
|
4636
|
+
x,
|
|
4637
|
+
y,
|
|
4638
|
+
width: yogaNode.getComputedWidth(),
|
|
4639
|
+
height: yogaNode.getComputedHeight()
|
|
4640
|
+
};
|
|
4641
|
+
};
|
|
4642
|
+
//#endregion
|
|
4643
|
+
export { Box, Newline, Spacer, Static, Text, Transform, kittyFlags, kittyModifiers, measureElement, render, renderToString, useAnimation, useApp, useBoxMetrics, useCursor, useFocus, useFocusManager, useInput, useIsScreenReaderEnabled, usePaste, useStderr, useStdin, useStdout, useWindowSize };
|