@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.3

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