@alchemy.run/sigil 0.0.0-alpha.4 → 0.0.0-alpha.6

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 (138) hide show
  1. package/README.md +21 -9
  2. package/THIRD_PARTY_NOTICES.md +39 -1
  3. package/dist/Text-BobFKi74.d.ts +452 -0
  4. package/dist/ansi.d.ts +122 -131
  5. package/dist/ansi.js +86 -6
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.js +3 -0
  8. package/dist/cell-_ZVhbfl0.js +44 -0
  9. package/dist/color-CkbalRqK.js +2 -0
  10. package/dist/color-policy-BMzMwV7Q.d.ts +22 -0
  11. package/dist/color-policy-SVj1pYTA.js +560 -0
  12. package/dist/color-profile-DHhQHY55.js +36 -0
  13. package/dist/color-profile-u0Nhe9Nv.d.ts +97 -0
  14. package/dist/color.d.ts +21 -0
  15. package/dist/color.js +3 -0
  16. package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
  17. package/dist/detect-Bh4yGP6w.d.ts +186 -0
  18. package/dist/detect-BuTXtY6e.js +373 -0
  19. package/dist/env-YVw64yZS.js +9 -0
  20. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  21. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  22. package/dist/index-DZ88EXJv.d.ts +21 -0
  23. package/dist/index.d.ts +143 -498
  24. package/dist/index.js +1026 -2244
  25. package/dist/osc-CCH7xDoS.js +71 -0
  26. package/dist/osc-Cn0fw77g.d.ts +23 -0
  27. package/dist/paint-C19minOS.d.ts +81 -0
  28. package/dist/query-vaIeGOkH.d.ts +152 -0
  29. package/dist/router.d.ts +392 -0
  30. package/dist/router.js +709 -0
  31. package/dist/sample-Cqw1bjUL.js +445 -0
  32. package/dist/screen-BOSLQ8fF.d.ts +49 -0
  33. package/dist/screen-CiPytswf.js +342 -0
  34. package/dist/screen.d.ts +5 -0
  35. package/dist/screen.js +5 -0
  36. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  37. package/dist/serialize-BTkAZgw1.js +79 -0
  38. package/dist/session-aZr9O8h3.js +665 -0
  39. package/dist/sgr-BhwaWAJB.js +246 -0
  40. package/dist/store-CgrG9K4y.d.ts +72 -0
  41. package/dist/string-width-CijQwpIk.js +69 -0
  42. package/dist/strip-BvU4toXG.js +6 -0
  43. package/dist/terminal.d.ts +118 -0
  44. package/dist/terminal.js +2 -0
  45. package/dist/tokenize-AjqbvtiT.js +1242 -0
  46. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  47. package/dist/truncate-D31fhU6i.js +562 -0
  48. package/dist/use-focus-BzqAJi0n.js +1337 -0
  49. package/package.json +41 -9
  50. package/src/ansi/chalk.ts +5 -3
  51. package/src/ansi/escapes.ts +14 -0
  52. package/src/ansi/graphemes.ts +8 -0
  53. package/src/ansi/hyperlink.ts +44 -0
  54. package/src/ansi/index.ts +3 -1
  55. package/src/ansi/osc.ts +77 -0
  56. package/src/ansi/tokenize.ts +3 -4
  57. package/src/capabilities/color-policy.ts +34 -0
  58. package/src/capabilities/detect.ts +594 -0
  59. package/src/capabilities/index.ts +37 -0
  60. package/src/capabilities/query.ts +657 -0
  61. package/src/capabilities/store.ts +379 -0
  62. package/src/color/index.ts +3 -0
  63. package/src/color/paint.ts +169 -0
  64. package/src/color/palette.ts +48 -0
  65. package/src/color/sample.ts +323 -0
  66. package/src/color.ts +1 -0
  67. package/src/components/AnsiText.tsx +42 -0
  68. package/src/components/App.tsx +98 -10
  69. package/src/components/BackgroundContext.ts +2 -3
  70. package/src/components/Box.tsx +0 -8
  71. package/src/components/CursorContext.ts +1 -1
  72. package/src/components/Hyperlink.tsx +56 -0
  73. package/src/components/TerminalOscContext.ts +25 -0
  74. package/src/components/Text.tsx +22 -45
  75. package/src/components/Transform.tsx +1 -1
  76. package/src/dom.ts +11 -2
  77. package/src/global.d.ts +3 -0
  78. package/src/hooks/use-capabilities.ts +73 -0
  79. package/src/hooks/use-cursor.ts +2 -2
  80. package/src/hooks/use-terminal-osc.ts +59 -0
  81. package/src/index.ts +45 -1
  82. package/src/ink.tsx +213 -153
  83. package/src/{render-node-to-output.ts → paint-tree.ts} +75 -48
  84. package/src/reconciler.ts +24 -3
  85. package/src/render-background.ts +36 -15
  86. package/src/render-border.ts +94 -61
  87. package/src/render-frame.ts +83 -0
  88. package/src/render-to-string.ts +21 -6
  89. package/src/render.ts +15 -6
  90. package/src/router/components.tsx +343 -0
  91. package/src/router/context.ts +41 -0
  92. package/src/router/history.ts +194 -0
  93. package/src/router/hooks.tsx +391 -0
  94. package/src/router/index.ts +34 -0
  95. package/src/router/matcher.ts +571 -0
  96. package/src/screen/ansi.ts +184 -0
  97. package/src/screen/canvas.ts +160 -0
  98. package/src/screen/cell.ts +138 -0
  99. package/src/screen/color-profile.ts +47 -0
  100. package/src/screen/geometry.ts +9 -0
  101. package/src/screen/index.ts +6 -0
  102. package/src/screen/screen.ts +305 -0
  103. package/src/screen/serialize.ts +129 -0
  104. package/src/screen.ts +1 -0
  105. package/src/semantic-text-style.ts +118 -0
  106. package/src/squash-text-nodes.ts +2 -5
  107. package/src/structured-text.ts +325 -0
  108. package/src/styles.ts +19 -14
  109. package/src/terminal/index.ts +2 -0
  110. package/src/terminal/inline-presenter.ts +120 -0
  111. package/src/terminal/input.ts +86 -0
  112. package/src/terminal/render-scheduler.ts +37 -0
  113. package/src/terminal/screen-presenter.ts +188 -0
  114. package/src/terminal/session.ts +407 -0
  115. package/src/terminal.ts +1 -0
  116. package/src/testing/browser.ts +588 -0
  117. package/src/testing/emulators.ts +205 -0
  118. package/src/testing/explorer-app/index.html +12 -0
  119. package/src/testing/explorer-app/main.ts +381 -0
  120. package/src/testing/explorer-app/style.css +194 -0
  121. package/src/testing/explorer-app/tsconfig.json +15 -0
  122. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  123. package/src/testing/index.ts +26 -0
  124. package/src/testing/keys.ts +56 -0
  125. package/src/testing/live.ts +85 -0
  126. package/src/testing/matchers.ts +70 -0
  127. package/src/testing/public.ts +94 -0
  128. package/src/testing/terminal.ts +349 -0
  129. package/src/testing/vitest.ts +157 -0
  130. package/src/transform-adapter.ts +14 -0
  131. package/src/wrap-text.ts +4 -0
  132. package/dist/sgr-CMfEpjSk.d.ts +0 -91
  133. package/dist/truncate-Cr6xVFMa.js +0 -2330
  134. package/src/ansi/supports-color.ts +0 -207
  135. package/src/colorize.ts +0 -60
  136. package/src/log-update.ts +0 -370
  137. package/src/output.ts +0 -308
  138. package/src/renderer.ts +0 -73
package/dist/index.js CHANGED
@@ -1,21 +1,33 @@
1
- import { Bt as kittyQuery, C as isScreenReader, D as wrapAnsi, E as isWindows, Ht as pasteEnd, O as stringWidth, Rt as esu, S as isMacos, St as bsu, T as isTty, Ut as pasteStart, _ as chalk, b as signalExit, d as styledCharsFromTokens, f as styledCharsToString, g as tokenizeAnsi, gt as CSI, h as hasAnsiControlCharacters, k as widestLine, n as sliceAnsi, p as tokenize, t as cliTruncate, w as isSigilDev, x as isInCi, xt as ansiEscapes, y as cliCursor } from "./truncate-Cr6xVFMa.js";
1
+ import { C as bsu, S as ansiEscapes, V as esu, W as link, _ as CSI } from "./sgr-BhwaWAJB.js";
2
+ import { h as tokenizeAnsi, p as graphemes } from "./tokenize-AjqbvtiT.js";
3
+ import { t as stringWidth } from "./string-width-CijQwpIk.js";
4
+ import { n as sliceAnsi, r as wrapAnsi } from "./truncate-D31fhU6i.js";
5
+ import { _ as squashTextNodes, a as reconciler, c as kittyModifiers, d as FocusContext, f as Text, g as transformAnsiLine, h as emitLayoutListeners, i as useStdinContext, l as resolveFlags, m as createNode, n as useInput, o as detectKittySupport, p as addLayoutListener, r as useStdin, s as kittyFlags, t as useFocus, u as StdinContext, v as accessibilityContext } from "./use-focus-BzqAJi0n.js";
6
+ import { a as isTty, i as isSigilDev, o as isWindows, r as isScreenReader, t as isInCi } from "./env-YVw64yZS.js";
7
+ import { a as detectTerminal, c as signalExit, i as detectHyperlinkSupport, n as detectCapabilities, o as detectUnicodeSupport, r as detectColorLevel, s as terminalSize, t as createSupportsColor } from "./detect-BuTXtY6e.js";
8
+ import { u as cliCursor } from "./osc-CCH7xDoS.js";
9
+ import { a as registerTerminalIntegration, c as ensureTerminalQuery, d as queryTerminal, f as refreshTerminalQuery, i as getCapabilities, l as getTerminalQuery, o as getRawModeStream, r as capabilities, s as applyTerminalQuery, u as getTerminalQueryPromise } from "./color-policy-SVj1pYTA.js";
10
+ import { a as buildCursorSuffix, c as hideCursorEscape, i as buildCursorOnlySequence, o as buildReturnToBottomPrefix, s as cursorPositionChanged, t as TerminalSession } from "./session-aZr9O8h3.js";
2
11
  import { t as Yoga } from "./yoga-5jKhYCJC.js";
12
+ import { r as createCell, t as cellAttributes } from "./cell-_ZVhbfl0.js";
13
+ import { i as semanticTextStyleToCellStyle, n as mergeSemanticTextStyles, t as emptySemanticTextStyle } from "./semantic-text-style-DIMzC7xt.js";
14
+ import { a as samplePaint } from "./sample-Cqw1bjUL.js";
15
+ import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-CiPytswf.js";
16
+ import { t as colorProfileFromLevel } from "./color-profile-DHhQHY55.js";
17
+ import { n as serializeScreen, t as serializeLine } from "./serialize-BTkAZgw1.js";
18
+ import { t as stripAnsi } from "./strip-BvU4toXG.js";
19
+ import "./color-CkbalRqK.js";
3
20
  import { PassThrough, Stream } from "node:stream";
4
21
  import { setImmediate as setImmediate$1 } from "node:timers/promises";
5
22
  import { isNativeError } from "node:util/types";
6
- import { PureComponent, createContext, useCallback, useContext, useEffect, useEffectEvent, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
- import createReconciler from "react-reconciler";
8
- import { ConcurrentRoot, DefaultEventPriority, LegacyRoot, NoEventPriority } from "react-reconciler/constants.js";
23
+ import { PureComponent, createContext, useCallback, useContext, useEffect, useEffectEvent, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
24
+ import "react-reconciler";
25
+ import { ConcurrentRoot, LegacyRoot } from "react-reconciler/constants.js";
9
26
  import { EventEmitter } from "node:events";
10
- import { constants, existsSync, openSync, readFileSync } from "node:fs";
27
+ import { existsSync, readFileSync } from "node:fs";
11
28
  import { cwd } from "node:process";
12
- import { WriteStream } from "node:tty";
13
29
  import { jsx, jsxs } from "react/jsx-runtime";
14
30
  import { Console } from "node:console";
15
- import * as Scheduler from "scheduler";
16
- //#region src/components/AccessibilityContext.ts
17
- const accessibilityContext = createContext({ isScreenReaderEnabled: false });
18
- //#endregion
19
31
  //#region src/components/AnimationContext.ts
20
32
  const animationContext = createContext({
21
33
  renderThrottleMs: 0,
@@ -49,276 +61,6 @@ const AppContext = createContext({
49
61
  });
50
62
  AppContext.displayName = "InternalAppContext";
51
63
  //#endregion
52
- //#region src/cursor-position.ts
53
- const showCursorEscape = ansiEscapes.cursorShow;
54
- const hideCursorEscape = ansiEscapes.cursorHide;
55
- /**
56
- Compare two cursor positions. Returns true if they differ.
57
- */
58
- const cursorPositionChanged = (a, b) => a?.x !== b?.x || a?.y !== b?.y;
59
- /**
60
- Build escape sequence to move cursor from the bottom of the output to the target position and show it.
61
-
62
- `bottomLine` is the row the renderer left the cursor on, counted from the top of the output.
63
- That is always `lines.length - 1` for `lines = str.split('\n')`, whether or not the output ends
64
- with a newline:
65
-
66
- - With a trailing newline, `split` yields one extra empty element and the renderer stops just
67
- past the last visible line — which is `lines.length - 1`.
68
- - Without one, there is no extra element and the renderer deliberately stops on the last visible
69
- line instead of moving past it — which is also `lines.length - 1`.
70
-
71
- This is the same row basis `buildReturnToBottom` measures from, so the two stay in step.
72
- */
73
- const buildCursorSuffix = (bottomLine, cursorPosition) => {
74
- if (!cursorPosition) return "";
75
- const moveUp = bottomLine - cursorPosition.y;
76
- return (moveUp > 0 ? ansiEscapes.cursorUp(moveUp) : "") + ansiEscapes.cursorTo(cursorPosition.x) + showCursorEscape;
77
- };
78
- /**
79
- Build escape sequence to move cursor from previousCursorPosition back to the bottom of output.
80
- This must be done before eraseLines or any operation that assumes cursor is at the bottom.
81
- */
82
- const buildReturnToBottom = (previousLineCount, previousCursorPosition) => {
83
- if (!previousCursorPosition) return "";
84
- const down = previousLineCount - 1 - previousCursorPosition.y;
85
- return (down > 0 ? ansiEscapes.cursorDown(down) : "") + ansiEscapes.cursorTo(0);
86
- };
87
- /**
88
- Build the escape sequence for cursor-only updates (output unchanged, cursor moved).
89
- Hides cursor if it was previously shown, returns to bottom, then repositions.
90
-
91
- `buildReturnToBottom` has just placed the cursor on row `previousLineCount - 1`, so the
92
- suffix measures from there rather than recomputing the row from the output.
93
- */
94
- const buildCursorOnlySequence = (input) => {
95
- const hidePrefix = input.cursorWasShown ? hideCursorEscape : "";
96
- const returnToBottom = buildReturnToBottom(input.previousLineCount, input.previousCursorPosition);
97
- const cursorSuffix = buildCursorSuffix(input.previousLineCount - 1, input.cursorPosition);
98
- return hidePrefix + returnToBottom + cursorSuffix;
99
- };
100
- /**
101
- Build the prefix that hides cursor and returns to bottom before erasing or rewriting.
102
- Returns empty string if cursor was not shown.
103
- */
104
- const buildReturnToBottomPrefix = (cursorWasShown, previousLineCount, previousCursorPosition) => {
105
- if (!cursorWasShown) return "";
106
- return hideCursorEscape + buildReturnToBottom(previousLineCount, previousCursorPosition);
107
- };
108
- //#endregion
109
- //#region src/log-update.ts
110
- const visibleLineCount = (lines, str) => str.endsWith("\n") ? lines.length - 1 : lines.length;
111
- const createStandard = (stream, { showCursor = false } = {}) => {
112
- let previousLineCount = 0;
113
- let previousOutput = "";
114
- let hasHiddenCursor = false;
115
- let cursorPosition;
116
- let cursorDirty = false;
117
- let previousCursorPosition;
118
- let cursorWasShown = false;
119
- const getActiveCursor = () => cursorDirty ? cursorPosition : void 0;
120
- const hasChanges = (str, activeCursor) => {
121
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
122
- return str !== previousOutput || cursorChanged;
123
- };
124
- const render = (str) => {
125
- if (!showCursor && !hasHiddenCursor) {
126
- cliCursor.hide(stream);
127
- hasHiddenCursor = true;
128
- }
129
- const activeCursor = getActiveCursor();
130
- cursorDirty = false;
131
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
132
- if (!hasChanges(str, activeCursor)) return false;
133
- const lines = str.split("\n");
134
- const cursorSuffix = buildCursorSuffix(lines.length - 1, activeCursor);
135
- if (str === previousOutput && cursorChanged) stream.write(buildCursorOnlySequence({
136
- cursorWasShown,
137
- previousLineCount,
138
- previousCursorPosition,
139
- cursorPosition: activeCursor
140
- }));
141
- else {
142
- previousOutput = str;
143
- const returnPrefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
144
- stream.write(returnPrefix + ansiEscapes.eraseLines(previousLineCount) + str + cursorSuffix);
145
- previousLineCount = lines.length;
146
- }
147
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
148
- cursorWasShown = activeCursor !== void 0;
149
- return true;
150
- };
151
- render.clear = () => {
152
- const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
153
- stream.write(prefix + ansiEscapes.eraseLines(previousLineCount));
154
- previousOutput = "";
155
- previousLineCount = 0;
156
- previousCursorPosition = void 0;
157
- cursorWasShown = false;
158
- };
159
- render.done = () => {
160
- previousOutput = "";
161
- previousLineCount = 0;
162
- previousCursorPosition = void 0;
163
- cursorWasShown = false;
164
- if (!showCursor) {
165
- cliCursor.show(stream);
166
- hasHiddenCursor = false;
167
- }
168
- };
169
- render.reset = () => {
170
- previousOutput = "";
171
- previousLineCount = 0;
172
- previousCursorPosition = void 0;
173
- cursorWasShown = false;
174
- };
175
- render.sync = (str) => {
176
- const activeCursor = cursorDirty ? cursorPosition : void 0;
177
- cursorDirty = false;
178
- const lines = str.split("\n");
179
- previousOutput = str;
180
- previousLineCount = lines.length;
181
- if (!activeCursor && cursorWasShown) stream.write(hideCursorEscape);
182
- if (activeCursor) stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
183
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
184
- cursorWasShown = activeCursor !== void 0;
185
- };
186
- render.setCursorPosition = (position) => {
187
- cursorPosition = position;
188
- cursorDirty = true;
189
- };
190
- render.isCursorDirty = () => cursorDirty;
191
- render.willRender = (str) => hasChanges(str, getActiveCursor());
192
- return render;
193
- };
194
- const createIncremental = (stream, { showCursor = false } = {}) => {
195
- let previousLines = [];
196
- let previousOutput = "";
197
- let hasHiddenCursor = false;
198
- let cursorPosition;
199
- let cursorDirty = false;
200
- let previousCursorPosition;
201
- let cursorWasShown = false;
202
- const getActiveCursor = () => cursorDirty ? cursorPosition : void 0;
203
- const hasChanges = (str, activeCursor) => {
204
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
205
- return str !== previousOutput || cursorChanged;
206
- };
207
- const render = (str) => {
208
- if (!showCursor && !hasHiddenCursor) {
209
- cliCursor.hide(stream);
210
- hasHiddenCursor = true;
211
- }
212
- const activeCursor = getActiveCursor();
213
- cursorDirty = false;
214
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
215
- if (!hasChanges(str, activeCursor)) return false;
216
- const nextLines = str.split("\n");
217
- const visibleCount = visibleLineCount(nextLines, str);
218
- const previousVisible = visibleLineCount(previousLines, previousOutput);
219
- if (str === previousOutput && cursorChanged) {
220
- stream.write(buildCursorOnlySequence({
221
- cursorWasShown,
222
- previousLineCount: previousLines.length,
223
- previousCursorPosition,
224
- cursorPosition: activeCursor
225
- }));
226
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
227
- cursorWasShown = activeCursor !== void 0;
228
- return true;
229
- }
230
- const returnPrefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
231
- if (str === "\n" || previousOutput.length === 0) {
232
- const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
233
- stream.write(returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix);
234
- cursorWasShown = activeCursor !== void 0;
235
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
236
- previousOutput = str;
237
- previousLines = nextLines;
238
- return true;
239
- }
240
- if (visibleCount > previousVisible) {
241
- const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
242
- stream.write(returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix);
243
- cursorWasShown = activeCursor !== void 0;
244
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
245
- previousOutput = str;
246
- previousLines = nextLines;
247
- return true;
248
- }
249
- const hasTrailingNewline = str.endsWith("\n");
250
- const buffer = [];
251
- buffer.push(returnPrefix);
252
- if (visibleCount < previousVisible) {
253
- const extraSlot = previousOutput.endsWith("\n") ? 1 : 0;
254
- buffer.push(ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot), ansiEscapes.cursorUp(visibleCount));
255
- } else buffer.push(ansiEscapes.cursorUp(previousLines.length - 1));
256
- for (let i = 0; i < visibleCount; i++) {
257
- const isLastLine = i === visibleCount - 1;
258
- if (nextLines[i] === previousLines[i]) {
259
- if (!isLastLine || hasTrailingNewline) buffer.push(ansiEscapes.cursorNextLine);
260
- continue;
261
- }
262
- buffer.push(ansiEscapes.cursorTo(0) + nextLines[i] + ansiEscapes.eraseEndLine + (isLastLine && !hasTrailingNewline ? "" : "\n"));
263
- }
264
- const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
265
- buffer.push(cursorSuffix);
266
- stream.write(buffer.join(""));
267
- cursorWasShown = activeCursor !== void 0;
268
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
269
- previousOutput = str;
270
- previousLines = nextLines;
271
- return true;
272
- };
273
- render.clear = () => {
274
- const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
275
- stream.write(prefix + ansiEscapes.eraseLines(previousLines.length));
276
- previousOutput = "";
277
- previousLines = [];
278
- previousCursorPosition = void 0;
279
- cursorWasShown = false;
280
- };
281
- render.done = () => {
282
- previousOutput = "";
283
- previousLines = [];
284
- previousCursorPosition = void 0;
285
- cursorWasShown = false;
286
- if (!showCursor) {
287
- cliCursor.show(stream);
288
- hasHiddenCursor = false;
289
- }
290
- };
291
- render.reset = () => {
292
- previousOutput = "";
293
- previousLines = [];
294
- previousCursorPosition = void 0;
295
- cursorWasShown = false;
296
- };
297
- render.sync = (str) => {
298
- const activeCursor = cursorDirty ? cursorPosition : void 0;
299
- cursorDirty = false;
300
- const lines = str.split("\n");
301
- previousOutput = str;
302
- previousLines = lines;
303
- if (!activeCursor && cursorWasShown) stream.write(hideCursorEscape);
304
- if (activeCursor) stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
305
- previousCursorPosition = activeCursor ? { ...activeCursor } : void 0;
306
- cursorWasShown = activeCursor !== void 0;
307
- };
308
- render.setCursorPosition = (position) => {
309
- cursorPosition = position;
310
- cursorDirty = true;
311
- };
312
- render.isCursorDirty = () => cursorDirty;
313
- render.willRender = (str) => hasChanges(str, getActiveCursor());
314
- return render;
315
- };
316
- const create$1 = (stream, { showCursor = false, incremental = false } = {}) => {
317
- if (incremental) return createIncremental(stream, { showCursor });
318
- return createStandard(stream, { showCursor });
319
- };
320
- const logUpdate = { create: create$1 };
321
- //#endregion
322
64
  //#region src/components/CursorContext.ts
323
65
  const CursorContext = createContext({ setCursorPosition() {} });
324
66
  CursorContext.displayName = "InternalCursorContext";
@@ -340,131 +82,6 @@ const codeExcerpt = (source, line, options = {}) => {
340
82
  }));
341
83
  };
342
84
  //#endregion
343
- //#region src/components/BackgroundContext.ts
344
- const backgroundContext = createContext(void 0);
345
- //#endregion
346
- //#region src/quick-lru.ts
347
- var QuickLru = class {
348
- #size = 0;
349
- #cache = /* @__PURE__ */ new Map();
350
- #oldCache = /* @__PURE__ */ new Map();
351
- #maxSize;
352
- constructor({ maxSize }) {
353
- if (!(maxSize && maxSize > 0)) throw new TypeError("`maxSize` must be a number greater than 0");
354
- this.#maxSize = maxSize;
355
- }
356
- get size() {
357
- let oldCacheSize = 0;
358
- for (const key of this.#oldCache.keys()) if (!this.#cache.has(key)) oldCacheSize++;
359
- return Math.min(this.#size + oldCacheSize, this.#maxSize);
360
- }
361
- get(key) {
362
- if (this.#cache.has(key)) return this.#cache.get(key);
363
- if (this.#oldCache.has(key)) {
364
- const value = this.#oldCache.get(key);
365
- this.#oldCache.delete(key);
366
- this.#set(key, value);
367
- return value;
368
- }
369
- }
370
- set(key, value) {
371
- if (this.#cache.has(key)) this.#cache.set(key, value);
372
- else this.#set(key, value);
373
- return this;
374
- }
375
- has(key) {
376
- return this.#cache.has(key) || this.#oldCache.has(key);
377
- }
378
- delete(key) {
379
- const deleted = this.#cache.delete(key);
380
- if (deleted) this.#size--;
381
- return this.#oldCache.delete(key) || deleted;
382
- }
383
- clear() {
384
- this.#cache.clear();
385
- this.#oldCache.clear();
386
- this.#size = 0;
387
- }
388
- #set(key, value) {
389
- this.#cache.set(key, value);
390
- this.#size++;
391
- if (this.#size >= this.#maxSize) {
392
- this.#size = 0;
393
- this.#oldCache = this.#cache;
394
- this.#cache = /* @__PURE__ */ new Map();
395
- }
396
- }
397
- };
398
- //#endregion
399
- //#region src/measure-text.ts
400
- const cache = new QuickLru({ maxSize: 4096 });
401
- const measureText = (text) => {
402
- if (text.length === 0) return {
403
- width: 0,
404
- height: 0
405
- };
406
- const cachedDimensions = cache.get(text);
407
- if (cachedDimensions) return cachedDimensions;
408
- const dimensions = {
409
- width: widestLine(text),
410
- height: text.split("\n").length
411
- };
412
- cache.set(text, dimensions);
413
- return dimensions;
414
- };
415
- //#endregion
416
- //#region src/get-max-width.ts
417
- const getMaxWidth = (yogaNode) => {
418
- return yogaNode.getComputedWidth() - yogaNode.getComputedPadding(Yoga.EDGE_LEFT) - yogaNode.getComputedPadding(Yoga.EDGE_RIGHT) - yogaNode.getComputedBorder(Yoga.EDGE_LEFT) - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT);
419
- };
420
- //#endregion
421
- //#region src/colorize.ts
422
- const rgbRegex = /^rgb\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/;
423
- const ansiRegex = /^ansi256\(\s?(\d+)\s?\)$/;
424
- const isNamedColor = (color) => {
425
- return color in chalk;
426
- };
427
- const colorize = (str, color, type) => {
428
- if (!color) return str;
429
- if (isNamedColor(color)) {
430
- if (type === "foreground") return chalk[color](str);
431
- const methodName = `bg${color[0].toUpperCase() + color.slice(1)}`;
432
- return chalk[methodName](str);
433
- }
434
- if (color.startsWith("#")) return type === "foreground" ? chalk.hex(color)(str) : chalk.bgHex(color)(str);
435
- if (color.startsWith("ansi256")) {
436
- const matches = ansiRegex.exec(color);
437
- if (!matches) return str;
438
- const value = Number(matches[1]);
439
- return type === "foreground" ? chalk.ansi256(value)(str) : chalk.bgAnsi256(value)(str);
440
- }
441
- if (color.startsWith("rgb")) {
442
- const matches = rgbRegex.exec(color);
443
- if (!matches) return str;
444
- const firstValue = Number(matches[1]);
445
- const secondValue = Number(matches[2]);
446
- const thirdValue = Number(matches[3]);
447
- return type === "foreground" ? chalk.rgb(firstValue, secondValue, thirdValue)(str) : chalk.bgRgb(firstValue, secondValue, thirdValue)(str);
448
- }
449
- return str;
450
- };
451
- //#endregion
452
- //#region src/render-background.ts
453
- const renderBackground = (x, y, node, output) => {
454
- if (!node.style.backgroundColor) return;
455
- const width = node.yogaNode.getComputedWidth();
456
- const height = node.yogaNode.getComputedHeight();
457
- const leftBorderWidth = node.style.borderStyle && node.style.borderLeft !== false ? 1 : 0;
458
- const rightBorderWidth = node.style.borderStyle && node.style.borderRight !== false ? 1 : 0;
459
- const topBorderHeight = node.style.borderStyle && node.style.borderTop !== false ? 1 : 0;
460
- const bottomBorderHeight = node.style.borderStyle && node.style.borderBottom !== false ? 1 : 0;
461
- const contentWidth = width - leftBorderWidth - rightBorderWidth;
462
- const contentHeight = height - topBorderHeight - bottomBorderHeight;
463
- if (!(contentWidth > 0 && contentHeight > 0)) return;
464
- const backgroundLine = colorize(" ".repeat(contentWidth), node.style.backgroundColor, "background");
465
- for (let row = 0; row < contentHeight; row++) output.write(x + leftBorderWidth, y + topBorderHeight + row, backgroundLine, { transformers: [] });
466
- };
467
- //#endregion
468
85
  //#region src/glyphs.ts
469
86
  const BOXES = {
470
87
  single: {
@@ -549,465 +166,6 @@ const BOXES = {
549
166
  }
550
167
  };
551
168
  //#endregion
552
- //#region src/render-border.ts
553
- const stylePiece = (segment, fg, bg, dim) => {
554
- let styled = colorize(segment, fg, "foreground");
555
- styled = colorize(styled, bg, "background");
556
- if (dim) styled = chalk.dim(styled);
557
- return styled;
558
- };
559
- const renderBorder = (x, y, node, output) => {
560
- if (node.style.borderStyle) {
561
- const width = node.yogaNode.getComputedWidth();
562
- const height = node.yogaNode.getComputedHeight();
563
- const box = typeof node.style.borderStyle === "string" ? BOXES[node.style.borderStyle] : node.style.borderStyle;
564
- const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
565
- const bottomBorderColor = node.style.borderBottomColor ?? node.style.borderColor;
566
- const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor;
567
- const rightBorderColor = node.style.borderRightColor ?? node.style.borderColor;
568
- const topBorderBackgroundColor = node.style.borderTopBackgroundColor ?? node.style.borderBackgroundColor;
569
- const bottomBorderBackgroundColor = node.style.borderBottomBackgroundColor ?? node.style.borderBackgroundColor;
570
- const leftBorderBackgroundColor = node.style.borderLeftBackgroundColor ?? node.style.borderBackgroundColor;
571
- const rightBorderBackgroundColor = node.style.borderRightBackgroundColor ?? node.style.borderBackgroundColor;
572
- const dimTopBorderColor = node.style.borderTopDimColor ?? node.style.borderDimColor;
573
- const dimBottomBorderColor = node.style.borderBottomDimColor ?? node.style.borderDimColor;
574
- const dimLeftBorderColor = node.style.borderLeftDimColor ?? node.style.borderDimColor;
575
- const dimRightBorderColor = node.style.borderRightDimColor ?? node.style.borderDimColor;
576
- const showTopBorder = node.style.borderTop !== false;
577
- const showBottomBorder = node.style.borderBottom !== false;
578
- const showLeftBorder = node.style.borderLeft !== false;
579
- const showRightBorder = node.style.borderRight !== false;
580
- const contentWidth = width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0);
581
- let topBorder = showTopBorder ? (showLeftBorder ? box.topLeft : "") + box.top.repeat(contentWidth) + (showRightBorder ? box.topRight : "") : void 0;
582
- topBorder &&= stylePiece(topBorder, topBorderColor, topBorderBackgroundColor, dimTopBorderColor);
583
- let verticalBorderHeight = height;
584
- if (showTopBorder) verticalBorderHeight -= 1;
585
- if (showBottomBorder) verticalBorderHeight -= 1;
586
- let leftBorder = "";
587
- if (showLeftBorder) leftBorder = (stylePiece(box.left, leftBorderColor, leftBorderBackgroundColor, dimLeftBorderColor) + "\n").repeat(verticalBorderHeight);
588
- let rightBorder = "";
589
- if (showRightBorder) rightBorder = (stylePiece(box.right, rightBorderColor, rightBorderBackgroundColor, dimRightBorderColor) + "\n").repeat(verticalBorderHeight);
590
- let bottomBorder = showBottomBorder ? (showLeftBorder ? box.bottomLeft : "") + box.bottom.repeat(contentWidth) + (showRightBorder ? box.bottomRight : "") : void 0;
591
- bottomBorder &&= stylePiece(bottomBorder, bottomBorderColor, bottomBorderBackgroundColor, dimBottomBorderColor);
592
- const offsetY = showTopBorder ? 1 : 0;
593
- if (topBorder) output.write(x, y, topBorder, { transformers: [] });
594
- if (leftBorder) output.write(x, y + offsetY, leftBorder, { transformers: [] });
595
- if (rightBorder) output.write(x + width - 1, y + offsetY, rightBorder, { transformers: [] });
596
- if (bottomBorder) output.write(x, y + height - 1, bottomBorder, { transformers: [] });
597
- }
598
- };
599
- //#endregion
600
- //#region src/sanitize-ansi.ts
601
- const sgrParametersRegex = /^[\d:;]*$/;
602
- const sanitizeAnsi = (text) => {
603
- if (!hasAnsiControlCharacters(text)) return text;
604
- let output = "";
605
- for (const token of tokenizeAnsi(text)) {
606
- if (token.type === "text" || token.type === "osc") {
607
- output += token.value;
608
- continue;
609
- }
610
- if (token.type === "csi" && token.finalCharacter === "m" && token.intermediateString === "" && sgrParametersRegex.test(token.parameterString)) output += token.value;
611
- }
612
- return output;
613
- };
614
- //#endregion
615
- //#region src/squash-text-nodes.ts
616
- const squashTextNodes = (node) => {
617
- let text = "";
618
- for (let index = 0; index < node.childNodes.length; index++) {
619
- const childNode = node.childNodes[index];
620
- if (childNode === void 0) continue;
621
- let nodeText = "";
622
- if (childNode.nodeName === "#text") nodeText = childNode.nodeValue;
623
- else {
624
- if (childNode.nodeName === "ink-text" || childNode.nodeName === "ink-virtual-text") nodeText = squashTextNodes(childNode);
625
- if (nodeText.length > 0 && typeof childNode.internal_transform === "function") nodeText = childNode.internal_transform(nodeText, index);
626
- }
627
- text += nodeText;
628
- }
629
- return sanitizeAnsi(text);
630
- };
631
- //#endregion
632
- //#region src/styles.ts
633
- const positionEdges = [
634
- ["top", Yoga.EDGE_TOP],
635
- ["right", Yoga.EDGE_RIGHT],
636
- ["bottom", Yoga.EDGE_BOTTOM],
637
- ["left", Yoga.EDGE_LEFT]
638
- ];
639
- const applyPositionStyles = (node, style) => {
640
- if ("position" in style) {
641
- let positionType = Yoga.POSITION_TYPE_RELATIVE;
642
- if (style.position === "absolute") positionType = Yoga.POSITION_TYPE_ABSOLUTE;
643
- else if (style.position === "static") positionType = Yoga.POSITION_TYPE_STATIC;
644
- node.setPositionType(positionType);
645
- }
646
- for (const [property, edge] of positionEdges) {
647
- if (!(property in style)) continue;
648
- const value = style[property];
649
- if (typeof value === "string") {
650
- node.setPositionPercent(edge, Number.parseFloat(value));
651
- continue;
652
- }
653
- node.setPosition(edge, value);
654
- }
655
- };
656
- const applyMarginStyles = (node, style) => {
657
- if ("margin" in style) node.setMargin(Yoga.EDGE_ALL, style.margin ?? 0);
658
- if ("marginX" in style) node.setMargin(Yoga.EDGE_HORIZONTAL, style.marginX ?? 0);
659
- if ("marginY" in style) node.setMargin(Yoga.EDGE_VERTICAL, style.marginY ?? 0);
660
- if ("marginLeft" in style) node.setMargin(Yoga.EDGE_START, style.marginLeft ?? 0);
661
- if ("marginRight" in style) node.setMargin(Yoga.EDGE_END, style.marginRight ?? 0);
662
- if ("marginTop" in style) node.setMargin(Yoga.EDGE_TOP, style.marginTop ?? 0);
663
- if ("marginBottom" in style) node.setMargin(Yoga.EDGE_BOTTOM, style.marginBottom ?? 0);
664
- };
665
- const applyPaddingStyles = (node, style) => {
666
- if ("padding" in style) node.setPadding(Yoga.EDGE_ALL, style.padding ?? 0);
667
- if ("paddingX" in style) node.setPadding(Yoga.EDGE_HORIZONTAL, style.paddingX ?? 0);
668
- if ("paddingY" in style) node.setPadding(Yoga.EDGE_VERTICAL, style.paddingY ?? 0);
669
- if ("paddingLeft" in style) node.setPadding(Yoga.EDGE_LEFT, style.paddingLeft ?? 0);
670
- if ("paddingRight" in style) node.setPadding(Yoga.EDGE_RIGHT, style.paddingRight ?? 0);
671
- if ("paddingTop" in style) node.setPadding(Yoga.EDGE_TOP, style.paddingTop ?? 0);
672
- if ("paddingBottom" in style) node.setPadding(Yoga.EDGE_BOTTOM, style.paddingBottom ?? 0);
673
- };
674
- const applyFlexStyles = (node, style) => {
675
- if ("flexGrow" in style) node.setFlexGrow(style.flexGrow ?? 0);
676
- if ("flexShrink" in style) node.setFlexShrink(typeof style.flexShrink === "number" ? style.flexShrink : 1);
677
- if ("flexWrap" in style) {
678
- if (style.flexWrap === "nowrap") node.setFlexWrap(Yoga.WRAP_NO_WRAP);
679
- if (style.flexWrap === "wrap") node.setFlexWrap(Yoga.WRAP_WRAP);
680
- if (style.flexWrap === "wrap-reverse") node.setFlexWrap(Yoga.WRAP_WRAP_REVERSE);
681
- }
682
- if ("flexDirection" in style) {
683
- if (style.flexDirection === "row") node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
684
- if (style.flexDirection === "row-reverse") node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW_REVERSE);
685
- if (style.flexDirection === "column") node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
686
- if (style.flexDirection === "column-reverse") node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
687
- }
688
- if ("flexBasis" in style) {
689
- if (typeof style.flexBasis === "number") node.setFlexBasis(style.flexBasis);
690
- else if (typeof style.flexBasis === "string") node.setFlexBasisPercent(Number.parseInt(style.flexBasis, 10));
691
- else node.setFlexBasisAuto();
692
- }
693
- if ("alignItems" in style) {
694
- if (style.alignItems === "stretch" || !style.alignItems) node.setAlignItems(Yoga.ALIGN_STRETCH);
695
- if (style.alignItems === "flex-start") node.setAlignItems(Yoga.ALIGN_FLEX_START);
696
- if (style.alignItems === "center") node.setAlignItems(Yoga.ALIGN_CENTER);
697
- if (style.alignItems === "flex-end") node.setAlignItems(Yoga.ALIGN_FLEX_END);
698
- if (style.alignItems === "baseline") node.setAlignItems(Yoga.ALIGN_BASELINE);
699
- }
700
- if ("alignSelf" in style) {
701
- if (style.alignSelf === "auto" || !style.alignSelf) node.setAlignSelf(Yoga.ALIGN_AUTO);
702
- if (style.alignSelf === "flex-start") node.setAlignSelf(Yoga.ALIGN_FLEX_START);
703
- if (style.alignSelf === "center") node.setAlignSelf(Yoga.ALIGN_CENTER);
704
- if (style.alignSelf === "flex-end") node.setAlignSelf(Yoga.ALIGN_FLEX_END);
705
- if (style.alignSelf === "stretch") node.setAlignSelf(Yoga.ALIGN_STRETCH);
706
- if (style.alignSelf === "baseline") node.setAlignSelf(Yoga.ALIGN_BASELINE);
707
- }
708
- if ("alignContent" in style) {
709
- if (style.alignContent === "flex-start" || !style.alignContent) node.setAlignContent(Yoga.ALIGN_FLEX_START);
710
- if (style.alignContent === "center") node.setAlignContent(Yoga.ALIGN_CENTER);
711
- if (style.alignContent === "flex-end") node.setAlignContent(Yoga.ALIGN_FLEX_END);
712
- if (style.alignContent === "space-between") node.setAlignContent(Yoga.ALIGN_SPACE_BETWEEN);
713
- if (style.alignContent === "space-around") node.setAlignContent(Yoga.ALIGN_SPACE_AROUND);
714
- if (style.alignContent === "space-evenly") node.setAlignContent(Yoga.ALIGN_SPACE_EVENLY);
715
- if (style.alignContent === "stretch") node.setAlignContent(Yoga.ALIGN_STRETCH);
716
- }
717
- if ("justifyContent" in style) {
718
- if (style.justifyContent === "flex-start" || !style.justifyContent) node.setJustifyContent(Yoga.JUSTIFY_FLEX_START);
719
- if (style.justifyContent === "center") node.setJustifyContent(Yoga.JUSTIFY_CENTER);
720
- if (style.justifyContent === "flex-end") node.setJustifyContent(Yoga.JUSTIFY_FLEX_END);
721
- if (style.justifyContent === "space-between") node.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN);
722
- if (style.justifyContent === "space-around") node.setJustifyContent(Yoga.JUSTIFY_SPACE_AROUND);
723
- if (style.justifyContent === "space-evenly") node.setJustifyContent(Yoga.JUSTIFY_SPACE_EVENLY);
724
- }
725
- };
726
- const applyDimensionStyles = (node, style) => {
727
- if ("width" in style) {
728
- if (typeof style.width === "number") node.setWidth(style.width);
729
- else if (typeof style.width === "string") node.setWidthPercent(Number.parseInt(style.width, 10));
730
- else node.setWidthAuto();
731
- }
732
- if ("height" in style) {
733
- if (typeof style.height === "number") node.setHeight(style.height);
734
- else if (typeof style.height === "string") node.setHeightPercent(Number.parseInt(style.height, 10));
735
- else node.setHeightAuto();
736
- }
737
- if ("minWidth" in style) {
738
- if (typeof style.minWidth === "string") node.setMinWidthPercent(Number.parseInt(style.minWidth, 10));
739
- else node.setMinWidth(style.minWidth ?? 0);
740
- }
741
- if ("minHeight" in style) {
742
- if (typeof style.minHeight === "string") node.setMinHeightPercent(Number.parseInt(style.minHeight, 10));
743
- else node.setMinHeight(style.minHeight ?? 0);
744
- }
745
- if ("maxWidth" in style) {
746
- if (typeof style.maxWidth === "string") node.setMaxWidthPercent(Number.parseInt(style.maxWidth, 10));
747
- else node.setMaxWidth(style.maxWidth);
748
- }
749
- if ("maxHeight" in style) {
750
- if (typeof style.maxHeight === "string") node.setMaxHeightPercent(Number.parseInt(style.maxHeight, 10));
751
- else node.setMaxHeight(style.maxHeight);
752
- }
753
- if ("aspectRatio" in style) node.setAspectRatio(style.aspectRatio);
754
- };
755
- const applyDisplayStyles = (node, style) => {
756
- if ("display" in style) node.setDisplay(style.display === "flex" ? Yoga.DISPLAY_FLEX : Yoga.DISPLAY_NONE);
757
- };
758
- const applyBorderStyles = (node, style, currentStyle) => {
759
- if (!("borderStyle" in style || "borderTop" in style || "borderBottom" in style || "borderLeft" in style || "borderRight" in style)) return;
760
- const borderWidth = currentStyle.borderStyle ? 1 : 0;
761
- node.setBorder(Yoga.EDGE_TOP, currentStyle.borderTop === false ? 0 : borderWidth);
762
- node.setBorder(Yoga.EDGE_BOTTOM, currentStyle.borderBottom === false ? 0 : borderWidth);
763
- node.setBorder(Yoga.EDGE_LEFT, currentStyle.borderLeft === false ? 0 : borderWidth);
764
- node.setBorder(Yoga.EDGE_RIGHT, currentStyle.borderRight === false ? 0 : borderWidth);
765
- };
766
- const applyGapStyles = (node, style) => {
767
- if ("gap" in style) node.setGap(Yoga.GUTTER_ALL, style.gap ?? 0);
768
- if ("columnGap" in style) node.setGap(Yoga.GUTTER_COLUMN, style.columnGap ?? 0);
769
- if ("rowGap" in style) node.setGap(Yoga.GUTTER_ROW, style.rowGap ?? 0);
770
- };
771
- const styles = (node, style = {}, currentStyle = style) => {
772
- applyPositionStyles(node, style);
773
- applyMarginStyles(node, style);
774
- applyPaddingStyles(node, style);
775
- applyFlexStyles(node, style);
776
- applyDimensionStyles(node, style);
777
- applyDisplayStyles(node, style);
778
- applyBorderStyles(node, style, currentStyle);
779
- applyGapStyles(node, style);
780
- };
781
- //#endregion
782
- //#region src/wrap-text.ts
783
- const wrapTextCache = new QuickLru({ maxSize: 4096 });
784
- const wrapText = (text, maxWidth, wrapType) => {
785
- const cacheKey = text + String(maxWidth) + String(wrapType);
786
- const cachedText = wrapTextCache.get(cacheKey);
787
- if (cachedText !== void 0) return cachedText;
788
- let wrappedText = text;
789
- if (wrapType === "wrap") wrappedText = wrapAnsi(text, maxWidth, {
790
- trim: false,
791
- hard: true
792
- });
793
- if (wrapType === "hard") wrappedText = wrapAnsi(text, maxWidth, {
794
- trim: false,
795
- hard: true,
796
- wordWrap: false
797
- });
798
- if (wrapType.startsWith("truncate")) {
799
- let position = "end";
800
- if (wrapType === "truncate-middle") position = "middle";
801
- if (wrapType === "truncate-start") position = "start";
802
- wrappedText = cliTruncate(text, maxWidth, { position });
803
- }
804
- wrapTextCache.set(cacheKey, wrappedText);
805
- return wrappedText;
806
- };
807
- //#endregion
808
- //#region src/render-node-to-output.ts
809
- const applyPaddingToText = (node, text) => {
810
- const yogaNode = node.childNodes[0]?.yogaNode;
811
- if (yogaNode) {
812
- const offsetX = yogaNode.getComputedLeft();
813
- const offsetY = yogaNode.getComputedTop();
814
- text = "\n".repeat(offsetY) + (offsetX > 0 ? text.replace(/^(?!\s*$)/gm, " ".repeat(offsetX)) : text);
815
- }
816
- return text;
817
- };
818
- const renderNodeToScreenReaderOutput = (node, options = {}) => {
819
- if (options.skipStaticElements && node.internal_static) return "";
820
- if (node.yogaNode?.getDisplay() === Yoga.DISPLAY_NONE) return "";
821
- let output = "";
822
- if (node.nodeName === "ink-text") output = squashTextNodes(node);
823
- else if (node.nodeName === "ink-box" || node.nodeName === "ink-root") {
824
- const separator = node.style.flexDirection === "row" || node.style.flexDirection === "row-reverse" ? " " : "\n";
825
- output = (node.style.flexDirection === "row-reverse" || node.style.flexDirection === "column-reverse" ? [...node.childNodes].reverse() : [...node.childNodes]).map((childNode) => {
826
- return renderNodeToScreenReaderOutput(childNode, {
827
- parentRole: node.internal_accessibility?.role,
828
- skipStaticElements: options.skipStaticElements
829
- });
830
- }).filter(Boolean).join(separator);
831
- }
832
- if (node.internal_accessibility) {
833
- const { role, state } = node.internal_accessibility;
834
- if (state) {
835
- const stateDescription = Object.keys(state).filter((key) => state[key]).join(", ");
836
- if (stateDescription) output = `(${stateDescription}) ${output}`;
837
- }
838
- if (role && role !== options.parentRole) output = `${role}: ${output}`;
839
- }
840
- return output;
841
- };
842
- const renderNodeToOutput = (node, output, options) => {
843
- const { offsetX = 0, offsetY = 0, transformers = [], skipStaticElements } = options;
844
- if (skipStaticElements && node.internal_static) return;
845
- const { yogaNode } = node;
846
- if (yogaNode) {
847
- if (yogaNode.getDisplay() === Yoga.DISPLAY_NONE) return;
848
- const x = offsetX + yogaNode.getComputedLeft();
849
- const y = offsetY + yogaNode.getComputedTop();
850
- let newTransformers = transformers;
851
- if (typeof node.internal_transform === "function") newTransformers = [node.internal_transform, ...transformers];
852
- if (node.nodeName === "ink-text") {
853
- let text = squashTextNodes(node);
854
- if (text.length > 0) {
855
- const currentWidth = widestLine(text);
856
- const maxWidth = getMaxWidth(yogaNode);
857
- if (currentWidth > maxWidth) {
858
- const textWrap = node.style.textWrap ?? "wrap";
859
- text = wrapText(text, maxWidth, textWrap);
860
- }
861
- text = applyPaddingToText(node, text);
862
- output.write(x, y, text, { transformers: newTransformers });
863
- }
864
- return;
865
- }
866
- let clipped = false;
867
- if (node.nodeName === "ink-box") {
868
- renderBackground(x, y, node, output);
869
- renderBorder(x, y, node, output);
870
- const clipHorizontally = node.style.overflowX === "hidden" || node.style.overflow === "hidden";
871
- const clipVertically = node.style.overflowY === "hidden" || node.style.overflow === "hidden";
872
- if (clipHorizontally || clipVertically) {
873
- const x1 = clipHorizontally ? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT) : void 0;
874
- const x2 = clipHorizontally ? x + yogaNode.getComputedWidth() - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT) : void 0;
875
- const y1 = clipVertically ? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP) : void 0;
876
- const y2 = clipVertically ? y + yogaNode.getComputedHeight() - yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM) : void 0;
877
- output.clip({
878
- x1,
879
- x2,
880
- y1,
881
- y2
882
- });
883
- clipped = true;
884
- }
885
- }
886
- if (node.nodeName === "ink-root" || node.nodeName === "ink-box") {
887
- for (const childNode of node.childNodes) renderNodeToOutput(childNode, output, {
888
- offsetX: x,
889
- offsetY: y,
890
- transformers: newTransformers,
891
- skipStaticElements
892
- });
893
- if (clipped) output.unclip();
894
- }
895
- }
896
- };
897
- //#endregion
898
- //#region src/dom.ts
899
- const createNode = (nodeName) => {
900
- const node = {
901
- nodeName,
902
- style: {},
903
- attributes: {},
904
- childNodes: [],
905
- parentNode: void 0,
906
- yogaNode: nodeName === "ink-virtual-text" ? void 0 : Yoga.Node.create(),
907
- internal_accessibility: {}
908
- };
909
- if (nodeName === "ink-text") node.yogaNode?.setMeasureFunc((width) => measureTextNode(node, width));
910
- return node;
911
- };
912
- const appendChildNode = (node, childNode) => {
913
- if (childNode.parentNode) removeChildNode(childNode.parentNode, childNode);
914
- childNode.parentNode = node;
915
- node.childNodes.push(childNode);
916
- if (childNode.yogaNode) node.yogaNode?.insertChild(childNode.yogaNode, node.yogaNode.getChildCount());
917
- if (node.nodeName === "ink-text" || node.nodeName === "ink-virtual-text") markNodeAsDirty(node);
918
- };
919
- const insertBeforeNode = (node, newChildNode, beforeChildNode) => {
920
- if (newChildNode.parentNode) removeChildNode(newChildNode.parentNode, newChildNode);
921
- newChildNode.parentNode = node;
922
- const index = node.childNodes.indexOf(beforeChildNode);
923
- if (index >= 0) {
924
- node.childNodes.splice(index, 0, newChildNode);
925
- if (newChildNode.yogaNode) node.yogaNode?.insertChild(newChildNode.yogaNode, index);
926
- } else {
927
- node.childNodes.push(newChildNode);
928
- if (newChildNode.yogaNode) node.yogaNode?.insertChild(newChildNode.yogaNode, node.yogaNode.getChildCount());
929
- }
930
- if (node.nodeName === "ink-text" || node.nodeName === "ink-virtual-text") markNodeAsDirty(node);
931
- };
932
- const removeChildNode = (node, removeNode) => {
933
- if (removeNode.yogaNode) removeNode.parentNode?.yogaNode?.removeChild(removeNode.yogaNode);
934
- removeNode.parentNode = void 0;
935
- const index = node.childNodes.indexOf(removeNode);
936
- if (index >= 0) node.childNodes.splice(index, 1);
937
- if (node.nodeName === "ink-text" || node.nodeName === "ink-virtual-text") markNodeAsDirty(node);
938
- };
939
- const nullifyYogaNodes = (node) => {
940
- node.yogaNode = void 0;
941
- if (node.nodeName !== "#text") for (const childNode of node.childNodes) nullifyYogaNodes(childNode);
942
- };
943
- /**
944
- Detach a removed subtree from the layout engine: drop the measure callback
945
- and null the `yogaNode` reference on every DOM node within it.
946
-
947
- Nulling the references makes every `?.yogaNode` guard in the codebase
948
- effective for removed nodes, turns lingering access into a safe no-op (see
949
- QwenLM/qwen-code#6820), and lets the garbage collector reclaim the Yoga
950
- tree along with its closures.
951
- */
952
- const detachYogaSubtree = (removeNode) => {
953
- removeNode.yogaNode?.unsetMeasureFunc();
954
- nullifyYogaNodes(removeNode);
955
- };
956
- const setAttribute = (node, key, value) => {
957
- if (key === "internal_accessibility") {
958
- node.internal_accessibility = value;
959
- return;
960
- }
961
- node.attributes[key] = value;
962
- };
963
- const setStyle = (node, style) => {
964
- node.style = style ?? {};
965
- };
966
- const createTextNode = (text) => {
967
- const node = {
968
- nodeName: "#text",
969
- nodeValue: text,
970
- yogaNode: void 0,
971
- parentNode: void 0,
972
- style: {}
973
- };
974
- setTextNodeValue(node, text);
975
- return node;
976
- };
977
- const measureTextNode = function(node, width) {
978
- const text = node.nodeName === "#text" ? node.nodeValue : squashTextNodes(node);
979
- const dimensions = measureText(text);
980
- if (dimensions.width <= width) return dimensions;
981
- if (dimensions.width >= 1 && width > 0 && width < 1) return dimensions;
982
- const textWrap = node.style?.textWrap ?? "wrap";
983
- const wrappedText = wrapText(text, width, textWrap);
984
- return measureText(wrappedText);
985
- };
986
- const findClosestYogaNode = (node) => {
987
- if (!node?.parentNode) return;
988
- return node.yogaNode ?? findClosestYogaNode(node.parentNode);
989
- };
990
- const markNodeAsDirty = (node) => {
991
- findClosestYogaNode(node)?.markDirty();
992
- };
993
- const setTextNodeValue = (node, text) => {
994
- if (typeof text !== "string") text = String(text);
995
- node.nodeValue = text;
996
- markNodeAsDirty(node);
997
- };
998
- const addLayoutListener = (rootNode, listener) => {
999
- if (rootNode.nodeName !== "ink-root") return () => {};
1000
- rootNode.internal_layoutListeners ??= /* @__PURE__ */ new Set();
1001
- rootNode.internal_layoutListeners.add(listener);
1002
- return () => {
1003
- rootNode.internal_layoutListeners?.delete(listener);
1004
- };
1005
- };
1006
- const emitLayoutListeners = (rootNode) => {
1007
- if (rootNode.nodeName !== "ink-root" || !rootNode.internal_layoutListeners) return;
1008
- for (const listener of rootNode.internal_layoutListeners) listener();
1009
- };
1010
- //#endregion
1011
169
  //#region src/components/Box.tsx
1012
170
  /** @jsxImportSource react */
1013
171
  /**
@@ -1017,7 +175,7 @@ function Box({ children, ref, backgroundColor, "aria-label": ariaLabel, "aria-hi
1017
175
  const { isScreenReaderEnabled } = useContext(accessibilityContext);
1018
176
  const label = ariaLabel ? /* @__PURE__ */ jsx("ink-text", { children: ariaLabel }) : void 0;
1019
177
  if (isScreenReaderEnabled && ariaHidden) return null;
1020
- const boxElement = /* @__PURE__ */ jsx("ink-box", {
178
+ return /* @__PURE__ */ jsx("ink-box", {
1021
179
  ref,
1022
180
  style: {
1023
181
  flexWrap: "nowrap",
@@ -1035,46 +193,6 @@ function Box({ children, ref, backgroundColor, "aria-label": ariaLabel, "aria-hi
1035
193
  },
1036
194
  children: isScreenReaderEnabled && label ? label : children
1037
195
  });
1038
- if (backgroundColor) return /* @__PURE__ */ jsx(backgroundContext.Provider, {
1039
- value: backgroundColor,
1040
- children: boxElement
1041
- });
1042
- return boxElement;
1043
- }
1044
- //#endregion
1045
- //#region src/components/Text.tsx
1046
- /** @jsxImportSource react */
1047
- /**
1048
- This component can display text and change its style to make it bold, underlined, italic, or strikethrough.
1049
- */
1050
- 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 }) {
1051
- const { isScreenReaderEnabled } = useContext(accessibilityContext);
1052
- const inheritedBackgroundColor = useContext(backgroundContext);
1053
- const childrenOrAriaLabel = isScreenReaderEnabled && ariaLabel ? ariaLabel : children;
1054
- if (childrenOrAriaLabel === void 0 || childrenOrAriaLabel === null) return null;
1055
- const transform = (text) => {
1056
- if (dimColor) text = chalk.dim(text);
1057
- if (color) text = colorize(text, color, "foreground");
1058
- const effectiveBackgroundColor = backgroundColor ?? inheritedBackgroundColor;
1059
- if (effectiveBackgroundColor) text = colorize(text, effectiveBackgroundColor, "background");
1060
- if (bold) text = chalk.bold(text);
1061
- if (italic) text = chalk.italic(text);
1062
- if (underline) text = chalk.underline(text);
1063
- if (strikethrough) text = chalk.strikethrough(text);
1064
- if (inverse) text = chalk.inverse(text);
1065
- return text;
1066
- };
1067
- if (isScreenReaderEnabled && ariaHidden) return null;
1068
- return /* @__PURE__ */ jsx("ink-text", {
1069
- style: {
1070
- flexGrow: 0,
1071
- flexShrink: 1,
1072
- flexDirection: "row",
1073
- textWrap: wrap
1074
- },
1075
- internal_transform: transform,
1076
- children: childrenOrAriaLabel
1077
- });
1078
196
  }
1079
197
  //#endregion
1080
198
  //#region src/parse-stack-line.ts
@@ -1263,219 +381,37 @@ var ErrorBoundary = class extends PureComponent {
1263
381
  this.props.onError(error);
1264
382
  }
1265
383
  render() {
1266
- if (this.state.error) return /* @__PURE__ */ jsx(ErrorOverview, { error: this.state.error });
1267
- return this.props.children;
1268
- }
1269
- };
1270
- //#endregion
1271
- //#region src/components/FocusContext.ts
1272
- const FocusContext = createContext({
1273
- activeId: void 0,
1274
- add() {},
1275
- remove() {},
1276
- activate() {},
1277
- deactivate() {},
1278
- enableFocus() {},
1279
- disableFocus() {},
1280
- focusNext() {},
1281
- focusPrevious() {},
1282
- focus() {}
1283
- });
1284
- FocusContext.displayName = "InternalFocusContext";
1285
- //#endregion
1286
- //#region src/components/StderrContext.ts
1287
- /**
1288
- `StderrContext` is a React context that exposes the stderr stream.
1289
- */
1290
- const StderrContext = createContext({
1291
- stderr: process.stderr,
1292
- write() {}
1293
- });
1294
- StderrContext.displayName = "InternalStderrContext";
1295
- //#endregion
1296
- //#region src/components/StdinContext.ts
1297
- /**
1298
- `StdinContext` is a React context that exposes the input stream.
1299
- */
1300
- const StdinContext = createContext({
1301
- stdin: process.stdin,
1302
- internal_eventEmitter: new EventEmitter(),
1303
- setRawMode() {},
1304
- setBracketedPasteMode() {},
1305
- isRawModeSupported: false,
1306
- internal_exitOnCtrlC: true
1307
- });
1308
- StdinContext.displayName = "InternalStdinContext";
1309
- //#endregion
1310
- //#region src/components/StdoutContext.ts
1311
- /**
1312
- `StdoutContext` is a React context that exposes the stdout stream where Ink renders your app.
1313
- */
1314
- const StdoutContext = createContext({
1315
- stdout: process.stdout,
1316
- write() {}
1317
- });
1318
- StdoutContext.displayName = "InternalStdoutContext";
1319
- //#endregion
1320
- //#region src/input-parser.ts
1321
- const escape$1 = "\x1B";
1322
- const isCsiParameterByte = (byte) => {
1323
- return byte >= 48 && byte <= 63;
1324
- };
1325
- const isCsiIntermediateByte = (byte) => {
1326
- return byte >= 32 && byte <= 47;
1327
- };
1328
- const isCsiFinalByte = (byte) => {
1329
- return byte >= 64 && byte <= 126;
1330
- };
1331
- const parseCsiSequence = (input, startIndex, prefixLength) => {
1332
- const csiPayloadStart = startIndex + prefixLength + 1;
1333
- let index = csiPayloadStart;
1334
- for (; index < input.length; index++) {
1335
- const byte = input.codePointAt(index);
1336
- if (byte === void 0) return "pending";
1337
- if (isCsiParameterByte(byte) || isCsiIntermediateByte(byte)) continue;
1338
- if (byte === 91 && index === csiPayloadStart) continue;
1339
- if (isCsiFinalByte(byte)) return {
1340
- sequence: input.slice(startIndex, index + 1),
1341
- nextIndex: index + 1
1342
- };
1343
- return;
1344
- }
1345
- return "pending";
1346
- };
1347
- const parseSs3Sequence = (input, startIndex, prefixLength) => {
1348
- const nextIndex = startIndex + prefixLength + 2;
1349
- if (nextIndex > input.length) return "pending";
1350
- const finalByte = input.codePointAt(nextIndex - 1);
1351
- if (finalByte === void 0 || !isCsiFinalByte(finalByte)) return;
1352
- return {
1353
- sequence: input.slice(startIndex, nextIndex),
1354
- nextIndex
1355
- };
1356
- };
1357
- const parseControlSequence = (input, startIndex, prefixLength) => {
1358
- const sequenceType = input[startIndex + prefixLength];
1359
- if (sequenceType === void 0) return "pending";
1360
- if (sequenceType === "[") return parseCsiSequence(input, startIndex, prefixLength);
1361
- if (sequenceType === "O") return parseSs3Sequence(input, startIndex, prefixLength);
1362
- };
1363
- const parseEscapedCodePoint = (input, escapeIndex) => {
1364
- const nextCodePoint = input.codePointAt(escapeIndex + 1);
1365
- const nextCodePointLength = nextCodePoint !== void 0 && nextCodePoint > 65535 ? 2 : 1;
1366
- const nextIndex = escapeIndex + 1 + nextCodePointLength;
1367
- return {
1368
- sequence: input.slice(escapeIndex, nextIndex),
1369
- nextIndex
1370
- };
1371
- };
1372
- const parseEscapeSequence = (input, escapeIndex) => {
1373
- if (escapeIndex === input.length - 1) return "pending";
1374
- if (input[escapeIndex + 1] === escape$1) {
1375
- if (escapeIndex + 2 >= input.length) return "pending";
1376
- const doubleEscapeSequence = parseControlSequence(input, escapeIndex, 2);
1377
- if (doubleEscapeSequence === "pending") return "pending";
1378
- if (doubleEscapeSequence) return doubleEscapeSequence;
1379
- return {
1380
- sequence: input.slice(escapeIndex, escapeIndex + 2),
1381
- nextIndex: escapeIndex + 2
1382
- };
1383
- }
1384
- const controlSequence = parseControlSequence(input, escapeIndex, 1);
1385
- if (controlSequence === "pending") return "pending";
1386
- if (controlSequence) return controlSequence;
1387
- return parseEscapedCodePoint(input, escapeIndex);
1388
- };
1389
- /**
1390
- 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.
1391
-
1392
- Other control characters like `\r` and `\t` are NOT split because they can legitimately appear inside pasted text.
1393
- */
1394
- const splitBackspaceBytes = (text, events) => {
1395
- let textSegmentStart = 0;
1396
- for (let index = 0; index < text.length; index++) {
1397
- const character = text[index];
1398
- if (character === "" || character === "\b") {
1399
- if (index > textSegmentStart) events.push(text.slice(textSegmentStart, index));
1400
- events.push(character);
1401
- textSegmentStart = index + 1;
1402
- }
1403
- }
1404
- if (textSegmentStart < text.length) events.push(text.slice(textSegmentStart));
1405
- };
1406
- const parseKeypresses = (input) => {
1407
- const events = [];
1408
- let index = 0;
1409
- const pendingFrom = (pendingStartIndex) => ({
1410
- events,
1411
- pending: input.slice(pendingStartIndex)
1412
- });
1413
- while (index < input.length) {
1414
- const escapeIndex = input.indexOf(escape$1, index);
1415
- if (escapeIndex === -1) {
1416
- splitBackspaceBytes(input.slice(index), events);
1417
- return {
1418
- events,
1419
- pending: ""
1420
- };
1421
- }
1422
- if (escapeIndex > index) splitBackspaceBytes(input.slice(index, escapeIndex), events);
1423
- const parsedEscapeSequence = parseEscapeSequence(input, escapeIndex);
1424
- if (parsedEscapeSequence === "pending") return pendingFrom(escapeIndex);
1425
- if (parsedEscapeSequence.sequence === pasteStart) {
1426
- const afterStart = parsedEscapeSequence.nextIndex;
1427
- const endIndex = input.indexOf(pasteEnd, afterStart);
1428
- if (endIndex === -1) return pendingFrom(escapeIndex);
1429
- events.push({ paste: input.slice(afterStart, endIndex) });
1430
- index = endIndex + pasteEnd.length;
1431
- continue;
1432
- }
1433
- events.push(parsedEscapeSequence.sequence);
1434
- index = parsedEscapeSequence.nextIndex;
1435
- }
1436
- return {
1437
- events,
1438
- pending: ""
1439
- };
1440
- };
1441
- const createInputParser = () => {
1442
- let pending = "";
1443
- return {
1444
- push(chunk) {
1445
- const parsedInput = parseKeypresses(pending + chunk);
1446
- pending = parsedInput.pending;
1447
- return parsedInput.events;
1448
- },
1449
- hasPendingEscape() {
1450
- return pending.startsWith(escape$1) && !pending.startsWith(pasteStart) && pending !== pasteStart.slice(0, -1);
1451
- },
1452
- flushPendingEscape() {
1453
- if (!pending.startsWith(escape$1)) return;
1454
- const pendingEscape = pending;
1455
- pending = "";
1456
- return pendingEscape;
1457
- },
1458
- reset() {
1459
- pending = "";
1460
- }
1461
- };
384
+ if (this.state.error) return /* @__PURE__ */ jsx(ErrorOverview, { error: this.state.error });
385
+ return this.props.children;
386
+ }
1462
387
  };
1463
388
  //#endregion
1464
- //#region src/stream.ts
1465
- const isRawModeStream = (stdin) => {
1466
- return isTty(stdin) && "setRawMode" in stdin && typeof stdin.setRawMode === "function";
1467
- };
1468
- const getRawModeStream = (stdin) => {
1469
- if (!isRawModeStream(stdin)) return;
1470
- return stdin;
1471
- };
389
+ //#region src/components/StderrContext.ts
390
+ /**
391
+ `StderrContext` is a React context that exposes the stderr stream.
392
+ */
393
+ const StderrContext = createContext({
394
+ stderr: process.stderr,
395
+ write() {}
396
+ });
397
+ StderrContext.displayName = "InternalStderrContext";
398
+ //#endregion
399
+ //#region src/components/StdoutContext.ts
400
+ /**
401
+ `StdoutContext` is a React context that exposes the stdout stream where Ink renders your app.
402
+ */
403
+ const StdoutContext = createContext({
404
+ stdout: process.stdout,
405
+ write() {}
406
+ });
407
+ StdoutContext.displayName = "InternalStdoutContext";
1472
408
  //#endregion
1473
409
  //#region src/components/App.tsx
1474
410
  /** @jsxImportSource react */
1475
411
  const tab = " ";
1476
412
  const shiftTab = `${CSI}Z`;
1477
413
  const escape = "\x1B";
1478
- function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, exitOnCtrlC, onExit, onWaitUntilRenderFlush, onSuspendTerminal, onRegisterInputControl, setCursorPosition, interactive, renderThrottleMs }) {
414
+ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, exitOnCtrlC, onExit, onWaitUntilRenderFlush, onSuspendTerminal, onRegisterInputControl, setCursorPosition, interactive, renderThrottleMs, terminalInput }) {
1479
415
  const [isFocusEnabled, setIsFocusEnabled] = useState(true);
1480
416
  const [activeFocusId, setActiveFocusId] = useState(void 0);
1481
417
  const [, setFocusables] = useState([]);
@@ -1488,7 +424,6 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
1488
424
  const internal_eventEmitter = useRef(new EventEmitter());
1489
425
  internal_eventEmitter.current.setMaxListeners(Infinity);
1490
426
  const readableListenerRef = useRef(void 0);
1491
- const inputParserRef = useRef(createInputParser());
1492
427
  const pendingInputFlushRef = useRef(void 0);
1493
428
  const pendingInputFlushDelayMilliseconds = 20;
1494
429
  const clearPendingInputFlush = useCallback(() => {
@@ -1554,10 +489,14 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
1554
489
  readableListenerRef.current = void 0;
1555
490
  }, [stdin]);
1556
491
  const clearInputState = useCallback(() => {
1557
- inputParserRef.current.reset();
492
+ terminalInput.reset();
1558
493
  clearPendingInputFlush();
1559
494
  detachReadableListener();
1560
- }, [clearPendingInputFlush, detachReadableListener]);
495
+ }, [
496
+ clearPendingInputFlush,
497
+ detachReadableListener,
498
+ terminalInput
499
+ ]);
1561
500
  const disableRawMode = useCallback(() => {
1562
501
  if (!rawModeStdin) return;
1563
502
  pendingDisableRawModeRef.current = false;
@@ -1593,16 +532,20 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
1593
532
  clearPendingInputFlush();
1594
533
  pendingInputFlushRef.current = setTimeout(() => {
1595
534
  pendingInputFlushRef.current = void 0;
1596
- const pendingEscape = inputParserRef.current.flushPendingEscape();
535
+ const pendingEscape = terminalInput.flushPendingEscape();
1597
536
  if (!pendingEscape) return;
1598
537
  emitInput(pendingEscape);
1599
538
  }, pendingInputFlushDelayMilliseconds);
1600
- }, [clearPendingInputFlush, emitInput]);
539
+ }, [
540
+ clearPendingInputFlush,
541
+ emitInput,
542
+ terminalInput
543
+ ]);
1601
544
  const handleReadable = useCallback(() => {
1602
545
  clearPendingInputFlush();
1603
546
  let chunk;
1604
547
  while ((chunk = stdin.read()) !== null) {
1605
- const inputEvents = inputParserRef.current.push(chunk);
548
+ const inputEvents = terminalInput.push(chunk);
1606
549
  for (const event of inputEvents) if (typeof event === "string") emitInput(event);
1607
550
  else {
1608
551
  if (internal_eventEmitter.current.listenerCount("paste") === 0) {
@@ -1612,12 +555,13 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
1612
555
  internal_eventEmitter.current.emit("paste", event.paste);
1613
556
  }
1614
557
  }
1615
- if (inputParserRef.current.hasPendingEscape()) schedulePendingInputFlush();
558
+ if (terminalInput.hasPendingEscape()) schedulePendingInputFlush();
1616
559
  }, [
1617
560
  stdin,
1618
561
  emitInput,
1619
562
  clearPendingInputFlush,
1620
- schedulePendingInputFlush
563
+ schedulePendingInputFlush,
564
+ terminalInput
1621
565
  ]);
1622
566
  const attachReadableListener = useCallback(() => {
1623
567
  if (readableListenerRef.current) return;
@@ -1659,6 +603,49 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
1659
603
  clearInputState,
1660
604
  disableRawMode
1661
605
  ]);
606
+ const handleQueryTerminal = useCallback(async ({ refresh = false } = {}) => {
607
+ if (!interactive || !isRawModeSupported || !stdout.isTTY) return;
608
+ if (!refresh) {
609
+ const existing = getTerminalQueryPromise(stdout);
610
+ if (existing) return existing;
611
+ }
612
+ handleSetRawMode(true);
613
+ detachReadableListener();
614
+ try {
615
+ return await (refresh ? refreshTerminalQuery(stdin, stdout) : ensureTerminalQuery(stdin, stdout));
616
+ } finally {
617
+ attachReadableListener();
618
+ handleSetRawMode(false);
619
+ }
620
+ }, [
621
+ interactive,
622
+ isRawModeSupported,
623
+ stdin,
624
+ stdout,
625
+ handleSetRawMode,
626
+ detachReadableListener,
627
+ attachReadableListener
628
+ ]);
629
+ const reportFeedActiveRef = useRef(false);
630
+ const handleSetReportFeed = useCallback((active) => {
631
+ if (active === reportFeedActiveRef.current || !isRawModeSupported) return;
632
+ reportFeedActiveRef.current = active;
633
+ handleSetRawMode(active);
634
+ }, [isRawModeSupported, handleSetRawMode]);
635
+ useInsertionEffect(() => {
636
+ const unregister = registerTerminalIntegration(stdout, {
637
+ runQuery: handleQueryTerminal,
638
+ setReportFeed: handleSetReportFeed
639
+ });
640
+ return () => {
641
+ unregister();
642
+ handleSetReportFeed(false);
643
+ };
644
+ }, [
645
+ stdout,
646
+ handleQueryTerminal,
647
+ handleSetReportFeed
648
+ ]);
1662
649
  const handleSetBracketedPasteMode = useCallback((isEnabled) => {
1663
650
  if (!stdout.isTTY) return;
1664
651
  if (isEnabled) {
@@ -1932,94 +919,21 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
1932
919
  }
1933
920
  App.displayName = "InternalApp";
1934
921
  //#endregion
922
+ //#region src/components/TerminalOscContext.ts
923
+ const noop$1 = () => {};
924
+ const TerminalOscContext = createContext({
925
+ publishProgress: noop$1,
926
+ copyToClipboard: noop$1,
927
+ publishTitle: noop$1,
928
+ setWorkingDirectory: noop$1,
929
+ notify: noop$1,
930
+ setPointerShape: noop$1
931
+ });
932
+ TerminalOscContext.displayName = "InternalTerminalOscContext";
933
+ //#endregion
1935
934
  //#region src/instances.ts
1936
935
  const instances = /* @__PURE__ */ new WeakMap();
1937
936
  //#endregion
1938
- //#region src/kitty-keyboard.ts
1939
- const kittyFlags = {
1940
- disambiguateEscapeCodes: 1,
1941
- reportEventTypes: 2,
1942
- reportAlternateKeys: 4,
1943
- reportAllKeysAsEscapeCodes: 8,
1944
- reportAssociatedText: 16
1945
- };
1946
- function resolveFlags(flags) {
1947
- let result = 0;
1948
- for (const flag of flags) result |= kittyFlags[flag];
1949
- return result;
1950
- }
1951
- const kittyModifiers = {
1952
- shift: 1,
1953
- alt: 2,
1954
- ctrl: 4,
1955
- super: 8,
1956
- hyper: 16,
1957
- meta: 32,
1958
- capsLock: 64,
1959
- numLock: 128
1960
- };
1961
- const textEncoder = new TextEncoder();
1962
- const kittyQueryEscapeByte = 27;
1963
- const kittyQueryOpenBracketByte = 91;
1964
- const kittyQueryQuestionMarkByte = 63;
1965
- const kittyQueryLetterByte = 117;
1966
- const zeroByte = 48;
1967
- const nineByte = 57;
1968
- const isDigitByte = (byte) => byte >= zeroByte && byte <= nineByte;
1969
- const matchKittyQueryResponse = (buffer, startIndex) => {
1970
- if (buffer[startIndex] !== kittyQueryEscapeByte || buffer[startIndex + 1] !== kittyQueryOpenBracketByte || buffer[startIndex + 2] !== kittyQueryQuestionMarkByte) return;
1971
- let index = startIndex + 3;
1972
- const digitsStartIndex = index;
1973
- while (index < buffer.length && isDigitByte(buffer[index])) index++;
1974
- if (index === digitsStartIndex) return;
1975
- if (index === buffer.length) return { state: "partial" };
1976
- if (buffer[index] === kittyQueryLetterByte) return {
1977
- state: "complete",
1978
- endIndex: index
1979
- };
1980
- };
1981
- const hasCompleteKittyQueryResponse = (buffer) => {
1982
- for (let index = 0; index < buffer.length; index++) if (matchKittyQueryResponse(buffer, index)?.state === "complete") return true;
1983
- return false;
1984
- };
1985
- const stripKittyQueryResponsesAndTrailingPartial = (buffer) => {
1986
- const keptBytes = [];
1987
- let index = 0;
1988
- while (index < buffer.length) {
1989
- const match = matchKittyQueryResponse(buffer, index);
1990
- if (match?.state === "complete") {
1991
- index = match.endIndex + 1;
1992
- continue;
1993
- }
1994
- if (match?.state === "partial") break;
1995
- keptBytes.push(buffer[index]);
1996
- index++;
1997
- }
1998
- return keptBytes;
1999
- };
2000
- const detectKittySupport = (stdin, stdout, onSupported) => {
2001
- let responseBuffer = [];
2002
- const cleanup = () => {
2003
- clearTimeout(timer);
2004
- stdin.removeListener("data", onData);
2005
- const remaining = stripKittyQueryResponsesAndTrailingPartial(responseBuffer);
2006
- responseBuffer = [];
2007
- if (remaining.length > 0) stdin.unshift(Uint8Array.from(remaining));
2008
- };
2009
- const onData = (data) => {
2010
- const chunk = typeof data === "string" ? textEncoder.encode(data) : data;
2011
- for (const byte of chunk) responseBuffer.push(byte);
2012
- if (hasCompleteKittyQueryResponse(responseBuffer)) {
2013
- cleanup();
2014
- onSupported();
2015
- }
2016
- };
2017
- stdin.on("data", onData);
2018
- const timer = setTimeout(cleanup, 200);
2019
- stdout.write(kittyQuery);
2020
- return cleanup;
2021
- };
2022
- //#endregion
2023
937
  //#region src/patch-console.ts
2024
938
  const consoleMethods = [
2025
939
  "assert",
@@ -2076,429 +990,604 @@ const patchStreamWrite = (stream, onData) => {
2076
990
  };
2077
991
  };
2078
992
  //#endregion
2079
- //#region package.json
2080
- var name = "@alchemy.run/sigil";
2081
- var version = "0.0.0-alpha.4";
993
+ //#region src/get-max-width.ts
994
+ const getMaxWidth = (yogaNode) => {
995
+ return yogaNode.getComputedWidth() - yogaNode.getComputedPadding(Yoga.EDGE_LEFT) - yogaNode.getComputedPadding(Yoga.EDGE_RIGHT) - yogaNode.getComputedBorder(Yoga.EDGE_LEFT) - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT);
996
+ };
2082
997
  //#endregion
2083
- //#region src/reconciler.ts
2084
- if (isSigilDev) await import("./devtools-DbthxoD1.js").catch(() => {});
2085
- const diff = (before, after) => {
2086
- if (before === after) return;
2087
- if (!before) return after;
2088
- const changed = {};
2089
- let isChanged = false;
2090
- for (const key of Object.keys(before)) if (after ? !Object.hasOwn(after, key) : true) {
2091
- changed[key] = void 0;
2092
- isChanged = true;
2093
- }
2094
- if (after) {
2095
- for (const key of Object.keys(after)) if (after[key] !== before[key]) {
2096
- changed[key] = after[key];
2097
- isChanged = true;
2098
- }
2099
- }
2100
- return isChanged ? changed : void 0;
998
+ //#region src/render-background.ts
999
+ const renderBackground = (x, y, node, output) => {
1000
+ if (node.style.backgroundColor === void 0) return;
1001
+ const width = node.yogaNode.getComputedWidth();
1002
+ const height = node.yogaNode.getComputedHeight();
1003
+ const leftBorderWidth = node.style.borderStyle && node.style.borderLeft !== false ? 1 : 0;
1004
+ const rightBorderWidth = node.style.borderStyle && node.style.borderRight !== false ? 1 : 0;
1005
+ const topBorderHeight = node.style.borderStyle && node.style.borderTop !== false ? 1 : 0;
1006
+ const bottomBorderHeight = node.style.borderStyle && node.style.borderBottom !== false ? 1 : 0;
1007
+ const contentWidth = width - leftBorderWidth - rightBorderWidth;
1008
+ const contentHeight = height - topBorderHeight - bottomBorderHeight;
1009
+ if (!(contentWidth > 0 && contentHeight > 0)) return;
1010
+ const bounds = {
1011
+ x: x + leftBorderWidth,
1012
+ y: y + topBorderHeight,
1013
+ width: contentWidth,
1014
+ height: contentHeight
1015
+ };
1016
+ const lines = Array.from({ length: contentHeight }, (_unused, row) => Array.from({ length: contentWidth }, (_empty, column) => {
1017
+ const resetBackground = node.style.backgroundColor === "";
1018
+ const background = resetBackground ? void 0 : samplePaint(node.style.backgroundColor, bounds.x + column, bounds.y + row, bounds, output.paintContext);
1019
+ return createCell(" ", 1, {
1020
+ ...background ? { background } : {},
1021
+ underline: "none",
1022
+ attributes: cellAttributes.none
1023
+ }, void 0, resetBackground ? { background: true } : void 0);
1024
+ }));
1025
+ output.writeCells(bounds.x, bounds.y, lines);
2101
1026
  };
2102
- const findRootNode$1 = (node) => {
2103
- let current = node;
2104
- while (current) {
2105
- if (current.nodeName === "ink-root") return current;
2106
- current = current.parentNode;
1027
+ //#endregion
1028
+ //#region src/render-border.ts
1029
+ const stylePiece = (segment, x, y, bounds, context, fg, bg, dim) => {
1030
+ let column = x;
1031
+ return [...graphemes(segment)].map((grapheme) => {
1032
+ const foreground = fg ? samplePaint(fg, column, y, bounds, context) : void 0;
1033
+ const background = bg ? samplePaint(bg, column, y, bounds, context) : void 0;
1034
+ const width = Math.max(1, stringWidth(grapheme));
1035
+ column += width;
1036
+ return createCell(grapheme, width, {
1037
+ ...foreground ? { foreground } : {},
1038
+ ...background ? { background } : {},
1039
+ underline: "none",
1040
+ attributes: dim ? cellAttributes.faint : cellAttributes.none
1041
+ });
1042
+ });
1043
+ };
1044
+ const renderBorder = (x, y, node, output) => {
1045
+ if (node.style.borderStyle) {
1046
+ const width = node.yogaNode.getComputedWidth();
1047
+ const height = node.yogaNode.getComputedHeight();
1048
+ const bounds = {
1049
+ x,
1050
+ y,
1051
+ width,
1052
+ height
1053
+ };
1054
+ const box = typeof node.style.borderStyle === "string" ? BOXES[node.style.borderStyle] : node.style.borderStyle;
1055
+ const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
1056
+ const bottomBorderColor = node.style.borderBottomColor ?? node.style.borderColor;
1057
+ const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor;
1058
+ const rightBorderColor = node.style.borderRightColor ?? node.style.borderColor;
1059
+ const topBorderBackgroundColor = node.style.borderTopBackgroundColor ?? node.style.borderBackgroundColor;
1060
+ const bottomBorderBackgroundColor = node.style.borderBottomBackgroundColor ?? node.style.borderBackgroundColor;
1061
+ const leftBorderBackgroundColor = node.style.borderLeftBackgroundColor ?? node.style.borderBackgroundColor;
1062
+ const rightBorderBackgroundColor = node.style.borderRightBackgroundColor ?? node.style.borderBackgroundColor;
1063
+ const dimTopBorderColor = node.style.borderTopDimColor ?? node.style.borderDimColor;
1064
+ const dimBottomBorderColor = node.style.borderBottomDimColor ?? node.style.borderDimColor;
1065
+ const dimLeftBorderColor = node.style.borderLeftDimColor ?? node.style.borderDimColor;
1066
+ const dimRightBorderColor = node.style.borderRightDimColor ?? node.style.borderDimColor;
1067
+ const showTopBorder = node.style.borderTop !== false;
1068
+ const showBottomBorder = node.style.borderBottom !== false;
1069
+ const showLeftBorder = node.style.borderLeft !== false;
1070
+ const showRightBorder = node.style.borderRight !== false;
1071
+ const contentWidth = width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0);
1072
+ const topBorder = showTopBorder ? (showLeftBorder ? box.topLeft : "") + box.top.repeat(contentWidth) + (showRightBorder ? box.topRight : "") : void 0;
1073
+ let verticalBorderHeight = height;
1074
+ if (showTopBorder) verticalBorderHeight -= 1;
1075
+ if (showBottomBorder) verticalBorderHeight -= 1;
1076
+ const offsetY = showTopBorder ? 1 : 0;
1077
+ const bottomBorder = showBottomBorder ? (showLeftBorder ? box.bottomLeft : "") + box.bottom.repeat(contentWidth) + (showRightBorder ? box.bottomRight : "") : void 0;
1078
+ if (topBorder) output.writeCells(x, y, [stylePiece(topBorder, x, y, bounds, output.paintContext, topBorderColor, topBorderBackgroundColor, dimTopBorderColor)]);
1079
+ if (showLeftBorder) output.writeCells(x, y + offsetY, Array.from({ length: verticalBorderHeight }, (_, row) => stylePiece(box.left, x, y + offsetY + row, bounds, output.paintContext, leftBorderColor, leftBorderBackgroundColor, dimLeftBorderColor)));
1080
+ if (showRightBorder) output.writeCells(x + width - 1, y + offsetY, Array.from({ length: verticalBorderHeight }, (_, row) => stylePiece(box.right, x + width - 1, y + offsetY + row, bounds, output.paintContext, rightBorderColor, rightBorderBackgroundColor, dimRightBorderColor)));
1081
+ if (bottomBorder) output.writeCells(x, y + height - 1, [stylePiece(bottomBorder, x, y + height - 1, bounds, output.paintContext, bottomBorderColor, bottomBorderBackgroundColor, dimBottomBorderColor)]);
2107
1082
  }
2108
1083
  };
2109
- /**
2110
- * Clear the root's cached `staticNode` when the node it points at is being
2111
- * removed as part of a larger subtree.
2112
- *
2113
- * The previous identity check (`staticNode === removeNode`) only caught direct
2114
- * removal of the `<Static>` element. When an *ancestor* of `<Static>` is
2115
- * removed, the stale `staticNode` reference survives and the next render would
2116
- * replay stale static output (and, before `detachYogaSubtree`, trap on detached
2117
- * WASM memory — see QwenLM/qwen-code#6820).
2118
- *
2119
- * The owning root is derived from the host parent passed to the removal hook,
2120
- * not a module-level global, so instances with separate stdout streams don't
2121
- * clobber each other's pointers.
2122
- */
2123
- const clearStaticNodeIfContained = (rootNode, removeNode) => {
2124
- if (!rootNode?.staticNode) return;
2125
- let current = rootNode.staticNode;
2126
- while (current) {
2127
- if (current === removeNode) {
2128
- rootNode.staticNode = void 0;
2129
- return;
1084
+ //#endregion
1085
+ //#region src/structured-text.ts
1086
+ /** Whether a text subtree must retain the ANSI compatibility pipeline. */
1087
+ function hasCompatibilityText(node) {
1088
+ if (node.internal_transform && !node.internal_textStyle) return true;
1089
+ return node.childNodes.some((child) => child.nodeName !== "#text" && hasCompatibilityText(child));
1090
+ }
1091
+ /** Rasterizes an unwrapped native Text subtree into semantic cell lines. */
1092
+ function structuredTextLines(node) {
1093
+ const lines = [[]];
1094
+ appendNode(node, emptySemanticTextStyle, lines, false);
1095
+ return lines;
1096
+ }
1097
+ /** Samples semantic paints only after wrapping and final layout are known. */
1098
+ function sampleStructuredText(lines, bounds, context) {
1099
+ return lines.map((line, row) => {
1100
+ let column = 0;
1101
+ return line.map((cell) => {
1102
+ const foreground = cell.foregroundPaint ? samplePaint(cell.foregroundPaint, bounds.x + column, bounds.y + row, bounds, context) : cell.style.foreground;
1103
+ const background = cell.backgroundPaint ? samplePaint(cell.backgroundPaint, bounds.x + column, bounds.y + row, bounds, context) : cell.style.background;
1104
+ column += cell.width;
1105
+ return createCell(cell.grapheme, cell.width, {
1106
+ ...cell.style,
1107
+ ...foreground ? { foreground } : {},
1108
+ ...background ? { background } : {}
1109
+ }, cell.hyperlink, cell.reset);
1110
+ });
1111
+ });
1112
+ }
1113
+ function structuredTextBaseStyle(node) {
1114
+ return semanticTextStyleToCellStyle(mergeSemanticTextStyles(emptySemanticTextStyle, node.internal_textStyle));
1115
+ }
1116
+ /** Wraps or truncates semantic cells without converting them through ANSI. */
1117
+ function wrapStructuredText(input, maxWidth, wrap, baseStyle) {
1118
+ if (wrap === "none") return input.map((line) => [...line]);
1119
+ if (maxWidth < 1) return [[]];
1120
+ return input.flatMap((line) => {
1121
+ if (lineWidth(line) <= maxWidth) return [[...line]];
1122
+ if (wrap === "hard") return hardWrap(line, maxWidth);
1123
+ if (wrap === "wrap") return wordWrap(line, maxWidth);
1124
+ return [truncate(line, maxWidth, wrap, baseStyle)];
1125
+ });
1126
+ }
1127
+ function wordWrap(line, maxWidth) {
1128
+ const words = [[]];
1129
+ const spaces = [];
1130
+ for (const cell of line) if (cell.grapheme === " ") {
1131
+ spaces.push(cell);
1132
+ words.push([]);
1133
+ } else words.at(-1).push(cell);
1134
+ const output = [[]];
1135
+ appendHard(output, words[0], maxWidth);
1136
+ for (let index = 1; index < words.length; index++) {
1137
+ appendHard(output, [spaces[index - 1]], maxWidth);
1138
+ const word = words[index];
1139
+ const current = output.at(-1);
1140
+ const wordWidth = lineWidth(word);
1141
+ const currentWidth = lineWidth(current);
1142
+ if (wordWidth <= maxWidth && currentWidth + wordWidth > maxWidth) output.push([]);
1143
+ else if (wordWidth > maxWidth && currentWidth > 0) {
1144
+ const remaining = maxWidth - currentWidth;
1145
+ const breaksHere = 1 + Math.floor((wordWidth - remaining - 1) / maxWidth);
1146
+ if (Math.floor((wordWidth - 1) / maxWidth) < breaksHere) output.push([]);
1147
+ }
1148
+ appendHard(output, word, maxWidth);
1149
+ }
1150
+ return output;
1151
+ }
1152
+ function hardWrap(line, maxWidth) {
1153
+ const output = [[]];
1154
+ appendHard(output, line, maxWidth);
1155
+ return output;
1156
+ }
1157
+ function appendHard(output, cells, maxWidth) {
1158
+ for (const cell of cells) {
1159
+ const current = output.at(-1);
1160
+ if (current.length > 0 && lineWidth(current) + cell.width > maxWidth) output.push([]);
1161
+ output.at(-1).push(cell);
1162
+ }
1163
+ }
1164
+ function truncate(line, maxWidth, wrap, baseStyle) {
1165
+ if (maxWidth === 1) return [ellipsis(baseStyle ?? cellStyleIntersection(line.at(0), line.at(-1)))];
1166
+ if (wrap === "truncate-start") {
1167
+ const right = sliceCells(line, lineWidth(line) - maxWidth + 1, lineWidth(line));
1168
+ return [ellipsis(right[0]?.style), ...right];
1169
+ }
1170
+ if (wrap === "truncate-middle") {
1171
+ const half = Math.min(Math.floor(maxWidth / 2), maxWidth - 1);
1172
+ const left = sliceCells(line, 0, half);
1173
+ const rightWidth = maxWidth - half - 1;
1174
+ const right = sliceCells(line, lineWidth(line) - rightWidth, lineWidth(line));
1175
+ return [
1176
+ ...left,
1177
+ ellipsis(baseStyle ?? cellStyleIntersection(left.at(-1), right.at(0))),
1178
+ ...right
1179
+ ];
1180
+ }
1181
+ const left = sliceCells(line, 0, maxWidth - 1);
1182
+ return [...left, ellipsis(left.at(-1)?.style)];
1183
+ }
1184
+ function sliceCells(line, start, end) {
1185
+ const output = [];
1186
+ let column = 0;
1187
+ for (const cell of line) {
1188
+ const nextColumn = column + cell.width;
1189
+ if (column >= start && nextColumn <= end) output.push(cell);
1190
+ column = nextColumn;
1191
+ }
1192
+ return output;
1193
+ }
1194
+ function ellipsis(style = semanticTextStyleToCellStyle(emptySemanticTextStyle)) {
1195
+ return createCell("…", 1, style);
1196
+ }
1197
+ function cellStyleIntersection(left, right) {
1198
+ if (!left) return right?.style ?? semanticTextStyleToCellStyle(emptySemanticTextStyle);
1199
+ if (!right) return left.style;
1200
+ return {
1201
+ ...sameColor(left.style.foreground, right.style.foreground) ? { foreground: left.style.foreground } : {},
1202
+ ...sameColor(left.style.background, right.style.background) ? { background: left.style.background } : {},
1203
+ ...sameColor(left.style.underlineColor, right.style.underlineColor) ? { underlineColor: left.style.underlineColor } : {},
1204
+ underline: left.style.underline === right.style.underline ? left.style.underline : "none",
1205
+ attributes: left.style.attributes & right.style.attributes
1206
+ };
1207
+ }
1208
+ function sameColor(left, right) {
1209
+ return JSON.stringify(left) === JSON.stringify(right);
1210
+ }
1211
+ function lineWidth(line) {
1212
+ return line.reduce((width, cell) => width + cell.width, 0);
1213
+ }
1214
+ function appendNode(node, inherited, lines, ansi) {
1215
+ if (node.nodeName === "#text") {
1216
+ if (ansi) appendAnsiText(node.nodeValue, inherited, lines);
1217
+ else appendText(node.nodeValue, inherited, lines);
1218
+ return;
1219
+ }
1220
+ const style = mergeSemanticTextStyles(inherited, node.internal_textStyle);
1221
+ for (const child of node.childNodes) appendNode(child, style, lines, ansi || node.internal_ansi === true);
1222
+ }
1223
+ function appendAnsiText(text, semantic, lines) {
1224
+ const base = semanticTextStyleToCellStyle(semantic);
1225
+ for (const cell of cellsFromAnsi(text)) {
1226
+ if (cell.grapheme === "\n" || cell.grapheme === "\r\n") {
1227
+ lines.push([]);
1228
+ continue;
2130
1229
  }
2131
- current = current.parentNode;
1230
+ const hasForeground = cell.style.foreground !== void 0;
1231
+ const hasBackground = cell.style.background !== void 0;
1232
+ lines.at(-1).push({
1233
+ ...cell,
1234
+ style: {
1235
+ ...cell.style.foreground ?? base.foreground ? { foreground: cell.style.foreground ?? base.foreground } : {},
1236
+ ...cell.style.background ?? base.background ? { background: cell.style.background ?? base.background } : {},
1237
+ ...cell.style.underlineColor ? { underlineColor: cell.style.underlineColor } : {},
1238
+ underline: cell.style.underline === "none" ? base.underline : cell.style.underline,
1239
+ attributes: base.attributes | cell.style.attributes
1240
+ },
1241
+ ...!hasForeground && semantic.foreground ? { foregroundPaint: semantic.foreground } : {},
1242
+ ...!hasBackground && semantic.background ? { backgroundPaint: semantic.background } : {}
1243
+ });
2132
1244
  }
2133
- };
2134
- let currentUpdatePriority = NoEventPriority;
2135
- const reconciler = createReconciler({
2136
- getRootHostContext: () => ({ isInsideText: false }),
2137
- prepareForCommit: () => null,
2138
- preparePortalMount: () => null,
2139
- clearContainer: () => false,
2140
- resetAfterCommit(rootNode) {
2141
- if (typeof rootNode.onComputeLayout === "function") rootNode.onComputeLayout();
2142
- emitLayoutListeners(rootNode);
2143
- if (rootNode.staticNode !== rootNode.previousStaticNode) {
2144
- rootNode.previousStaticNode = rootNode.staticNode;
2145
- if (typeof rootNode.onStaticChange === "function") rootNode.onStaticChange();
1245
+ }
1246
+ function appendText(text, style, lines) {
1247
+ const cellStyle = semanticTextStyleToCellStyle(style);
1248
+ const plainText = tokenizeAnsi(text).filter((token) => token.type === "text").map((token) => token.value).join("");
1249
+ for (const segment of graphemes(stripAnsi(plainText))) {
1250
+ if (segment === "\n" || segment === "\r\n") {
1251
+ lines.push([]);
1252
+ continue;
2146
1253
  }
2147
- if (rootNode.isStaticDirty) {
2148
- rootNode.isStaticDirty = false;
2149
- if (typeof rootNode.onImmediateRender === "function") rootNode.onImmediateRender();
2150
- return;
1254
+ lines.at(-1).push({
1255
+ ...createCell(segment, Math.max(1, stringWidth(segment)), cellStyle, void 0, style.resetForeground || style.resetBackground ? {
1256
+ foreground: style.resetForeground,
1257
+ background: style.resetBackground
1258
+ } : void 0),
1259
+ ...style.foreground ? { foregroundPaint: style.foreground } : {},
1260
+ ...style.background ? { backgroundPaint: style.background } : {}
1261
+ });
1262
+ }
1263
+ }
1264
+ //#endregion
1265
+ //#region src/paint-tree.ts
1266
+ const renderAccessibleText = (node, options = {}) => {
1267
+ if (options.skipStaticElements && node.internal_static) return "";
1268
+ if (node.yogaNode?.getDisplay() === Yoga.DISPLAY_NONE) return "";
1269
+ let output = "";
1270
+ if (node.nodeName === "ink-text") output = squashTextNodes(node);
1271
+ else if (node.nodeName === "ink-box" || node.nodeName === "ink-root") {
1272
+ const separator = node.style.flexDirection === "row" || node.style.flexDirection === "row-reverse" ? " " : "\n";
1273
+ output = (node.style.flexDirection === "row-reverse" || node.style.flexDirection === "column-reverse" ? [...node.childNodes].reverse() : [...node.childNodes]).map((childNode) => {
1274
+ return renderAccessibleText(childNode, {
1275
+ parentRole: node.internal_accessibility?.role,
1276
+ skipStaticElements: options.skipStaticElements
1277
+ });
1278
+ }).filter(Boolean).join(separator);
1279
+ }
1280
+ if (node.internal_accessibility) {
1281
+ const { role, state } = node.internal_accessibility;
1282
+ if (state) {
1283
+ const stateDescription = Object.keys(state).filter((key) => state[key]).join(", ");
1284
+ if (stateDescription) output = `(${stateDescription}) ${output}`;
2151
1285
  }
2152
- if (typeof rootNode.onRender === "function") rootNode.onRender();
2153
- },
2154
- getChildHostContext(parentHostContext, type) {
2155
- const previousIsInsideText = parentHostContext.isInsideText;
2156
- const isInsideText = type === "ink-text" || type === "ink-virtual-text";
2157
- if (previousIsInsideText === isInsideText) return parentHostContext;
2158
- return { isInsideText };
2159
- },
2160
- shouldSetTextContent: () => false,
2161
- createInstance(originalType, newProps, rootNode, hostContext) {
2162
- if (hostContext.isInsideText && originalType === "ink-box") throw new Error(`<Box> can't be nested inside <Text> component`);
2163
- const type = originalType === "ink-text" && hostContext.isInsideText ? "ink-virtual-text" : originalType;
2164
- const node = createNode(type);
2165
- for (const [key, value] of Object.entries(newProps)) {
2166
- if (key === "children") continue;
2167
- if (key === "style") {
2168
- setStyle(node, value);
2169
- if (node.yogaNode) styles(node.yogaNode, value);
2170
- continue;
2171
- }
2172
- if (key === "internal_transform") {
2173
- node.internal_transform = value;
2174
- continue;
2175
- }
2176
- if (key === "internal_static") {
2177
- node.internal_static = true;
2178
- rootNode.isStaticDirty = true;
2179
- rootNode.staticNode = node;
2180
- continue;
1286
+ if (role && role !== options.parentRole) output = `${role}: ${output}`;
1287
+ }
1288
+ return output;
1289
+ };
1290
+ const paintTree = (node, output, options) => {
1291
+ const { offsetX = 0, offsetY = 0, transformers = [], skipStaticElements } = options;
1292
+ if (skipStaticElements && node.internal_static) return;
1293
+ const { yogaNode } = node;
1294
+ if (yogaNode) {
1295
+ if (yogaNode.getDisplay() === Yoga.DISPLAY_NONE) return;
1296
+ const x = offsetX + yogaNode.getComputedLeft();
1297
+ const y = offsetY + yogaNode.getComputedTop();
1298
+ const newTransformers = node.internal_transform ? [node.internal_transform, ...transformers] : transformers;
1299
+ if (node.nodeName === "ink-text") {
1300
+ if (squashTextNodes(node).length > 0) {
1301
+ const maxWidth = getMaxWidth(yogaNode);
1302
+ const firstChildYoga = node.childNodes[0]?.yogaNode;
1303
+ const paddingX = firstChildYoga?.getComputedLeft() ?? 0;
1304
+ const paddingY = firstChildYoga?.getComputedTop() ?? 0;
1305
+ const textWrap = node.style.textWrap ?? "wrap";
1306
+ const overflow = textWrap === "none";
1307
+ const lines = wrapStructuredText(structuredTextLines(node), maxWidth, textWrap, structuredTextBaseStyle(node));
1308
+ const paintBounds = {
1309
+ x: x + paddingX,
1310
+ y: y + paddingY,
1311
+ width: Math.max(1, ...lines.map((line) => line.reduce((width, cell) => width + cell.width, 0))),
1312
+ height: lines.length
1313
+ };
1314
+ const sampled = sampleStructuredText(lines, paintBounds, output.paintContext);
1315
+ if (hasCompatibilityText(node)) {
1316
+ const textTransformers = collectTransformers(node, transformers);
1317
+ output.writeAnsi(paintBounds.x, paintBounds.y, sampled.map((line) => serializeLine(line, {
1318
+ colorProfile: output.paintContext.profile ?? "truecolor",
1319
+ trimEnd: false
1320
+ })).join("\n"), {
1321
+ transformers: textTransformers,
1322
+ overflow
1323
+ });
1324
+ } else output.writeCells(paintBounds.x, paintBounds.y, sampled, { overflow });
2181
1325
  }
2182
- setAttribute(node, key, value);
2183
- }
2184
- return node;
2185
- },
2186
- createTextInstance(text, _root, hostContext) {
2187
- if (!hostContext.isInsideText) throw new Error(`Text string "${text}" must be rendered inside <Text> component`);
2188
- return createTextNode(text);
2189
- },
2190
- resetTextContent() {},
2191
- hideTextInstance(node) {
2192
- setTextNodeValue(node, "");
2193
- },
2194
- unhideTextInstance(node, text) {
2195
- setTextNodeValue(node, text);
2196
- },
2197
- getPublicInstance: (instance) => instance,
2198
- hideInstance(node) {
2199
- node.yogaNode?.setDisplay(Yoga.DISPLAY_NONE);
2200
- },
2201
- unhideInstance(node) {
2202
- node.yogaNode?.setDisplay(Yoga.DISPLAY_FLEX);
2203
- },
2204
- appendInitialChild: appendChildNode,
2205
- appendChild: appendChildNode,
2206
- insertBefore: insertBeforeNode,
2207
- finalizeInitialChildren() {
2208
- return false;
2209
- },
2210
- isPrimaryRenderer: true,
2211
- supportsMutation: true,
2212
- supportsPersistence: false,
2213
- supportsHydration: false,
2214
- supportsMicrotasks: true,
2215
- scheduleMicrotask: queueMicrotask,
2216
- scheduleCallback: Scheduler.unstable_scheduleCallback,
2217
- cancelCallback: Scheduler.unstable_cancelCallback,
2218
- shouldYield: Scheduler.unstable_shouldYield,
2219
- now: Scheduler.unstable_now,
2220
- scheduleTimeout: setTimeout,
2221
- cancelTimeout: clearTimeout,
2222
- noTimeout: -1,
2223
- beforeActiveInstanceBlur() {},
2224
- afterActiveInstanceBlur() {},
2225
- detachDeletedInstance() {},
2226
- getInstanceFromNode: () => null,
2227
- prepareScopeUpdate() {},
2228
- getInstanceFromScope: () => null,
2229
- appendChildToContainer: appendChildNode,
2230
- insertInContainerBefore: insertBeforeNode,
2231
- removeChildFromContainer(node, removeNode) {
2232
- clearStaticNodeIfContained(findRootNode$1(node), removeNode);
2233
- removeChildNode(node, removeNode);
2234
- detachYogaSubtree(removeNode);
2235
- },
2236
- commitUpdate(node, _type, oldProps, newProps) {
2237
- if (node.internal_static) {
2238
- const rootNode = findRootNode$1(node);
2239
- if (rootNode) rootNode.isStaticDirty = true;
1326
+ return;
2240
1327
  }
2241
- const props = diff(oldProps, newProps);
2242
- const style = diff(oldProps["style"], newProps["style"]);
2243
- if (!props && !style) return;
2244
- if (props) for (const [key, value] of Object.entries(props)) {
2245
- if (key === "style") {
2246
- setStyle(node, value);
2247
- continue;
2248
- }
2249
- if (key === "internal_transform") {
2250
- node.internal_transform = value;
2251
- continue;
2252
- }
2253
- if (key === "internal_static") {
2254
- node.internal_static = true;
2255
- continue;
1328
+ let clipped = false;
1329
+ if (node.nodeName === "ink-box") {
1330
+ renderBackground(x, y, node, output);
1331
+ renderBorder(x, y, node, output);
1332
+ const clipHorizontally = node.style.overflowX === "hidden" || node.style.overflow === "hidden";
1333
+ const clipVertically = node.style.overflowY === "hidden" || node.style.overflow === "hidden";
1334
+ if (clipHorizontally || clipVertically) {
1335
+ const x1 = clipHorizontally ? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT) : void 0;
1336
+ const x2 = clipHorizontally ? x + yogaNode.getComputedWidth() - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT) : void 0;
1337
+ const y1 = clipVertically ? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP) : void 0;
1338
+ const y2 = clipVertically ? y + yogaNode.getComputedHeight() - yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM) : void 0;
1339
+ output.clip({
1340
+ x1,
1341
+ x2,
1342
+ y1,
1343
+ y2
1344
+ });
1345
+ clipped = true;
2256
1346
  }
2257
- setAttribute(node, key, value);
2258
- }
2259
- if (style && node.yogaNode) styles(node.yogaNode, style, newProps["style"] ?? {});
2260
- },
2261
- commitTextUpdate(node, _oldText, newText) {
2262
- setTextNodeValue(node, newText);
2263
- },
2264
- removeChild(node, removeNode) {
2265
- clearStaticNodeIfContained(findRootNode$1(node), removeNode);
2266
- removeChildNode(node, removeNode);
2267
- detachYogaSubtree(removeNode);
2268
- },
2269
- setCurrentUpdatePriority(newPriority) {
2270
- currentUpdatePriority = newPriority;
2271
- },
2272
- getCurrentUpdatePriority: () => currentUpdatePriority,
2273
- resolveUpdatePriority() {
2274
- if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
2275
- return DefaultEventPriority;
2276
- },
2277
- maySuspendCommit() {
2278
- return true;
2279
- },
2280
- NotPendingTransition: void 0,
2281
- HostTransitionContext: createContext(null),
2282
- resetFormInstance() {},
2283
- requestPostPaintCallback() {},
2284
- shouldAttemptEagerTransition() {
2285
- return false;
2286
- },
2287
- trackSchedulerEvent() {},
2288
- resolveEventType() {
2289
- return null;
2290
- },
2291
- resolveEventTimeStamp() {
2292
- return -1.1;
2293
- },
2294
- preloadInstance() {
2295
- return true;
2296
- },
2297
- startSuspendingCommit() {},
2298
- suspendInstance() {},
2299
- waitForCommitToBeReady() {
2300
- return null;
2301
- },
2302
- rendererPackageName: name,
2303
- rendererVersion: version
2304
- });
2305
- //#endregion
2306
- //#region src/output.ts
2307
- var OutputCaches = class {
2308
- widths = /* @__PURE__ */ new Map();
2309
- blockWidths = /* @__PURE__ */ new Map();
2310
- styledChars = /* @__PURE__ */ new Map();
2311
- getStyledChars(line) {
2312
- let cached = this.styledChars.get(line);
2313
- if (cached === void 0) {
2314
- cached = styledCharsFromTokens(tokenize(line));
2315
- this.styledChars.set(line, cached);
2316
- }
2317
- return cached;
2318
- }
2319
- getStringWidth(text) {
2320
- let cached = this.widths.get(text);
2321
- if (cached === void 0) {
2322
- cached = stringWidth(text);
2323
- this.widths.set(text, cached);
2324
1347
  }
2325
- return cached;
2326
- }
2327
- getWidestLine(text) {
2328
- let cached = this.blockWidths.get(text);
2329
- if (cached === void 0) {
2330
- let lineWidth = 0;
2331
- for (const line of text.split("\n")) lineWidth = Math.max(lineWidth, this.getStringWidth(line));
2332
- cached = lineWidth;
2333
- this.blockWidths.set(text, cached);
1348
+ if (node.nodeName === "ink-root" || node.nodeName === "ink-box") {
1349
+ for (const childNode of node.childNodes) paintTree(childNode, output, {
1350
+ offsetX: x,
1351
+ offsetY: y,
1352
+ transformers: newTransformers,
1353
+ skipStaticElements
1354
+ });
1355
+ if (clipped) output.unclip();
2334
1356
  }
2335
- return cached;
2336
1357
  }
2337
1358
  };
2338
- var Output = class {
1359
+ function collectTransformers(node, inherited) {
1360
+ return [
1361
+ ...node.childNodes.flatMap((child) => child.nodeName === "#text" ? [] : collectTransformers(child, [])),
1362
+ ...node.internal_transform ? [node.internal_transform] : [],
1363
+ ...inherited
1364
+ ];
1365
+ }
1366
+ //#endregion
1367
+ //#region src/screen/canvas.ts
1368
+ /** Immediate structured drawing canvas with one explicit ANSI compatibility adapter. */
1369
+ var Canvas = class {
2339
1370
  width;
2340
1371
  height;
2341
- operations = [];
2342
- caches = new OutputCaches();
1372
+ paintContext;
1373
+ #screen;
1374
+ #clips = [];
2343
1375
  constructor(options) {
2344
- const { width, height } = options;
2345
- this.width = width;
2346
- this.height = height;
1376
+ this.width = options.width;
1377
+ this.height = options.height;
1378
+ this.paintContext = {
1379
+ profile: options.colorProfile ?? "truecolor",
1380
+ ...options.paintContext
1381
+ };
1382
+ this.#screen = new Screen(this.width, this.height);
1383
+ }
1384
+ writeCells(x, y, lines, options) {
1385
+ const clip = this.#clips.at(-1);
1386
+ const overflow = options?.overflow === true;
1387
+ for (const [rowOffset, line] of lines.entries()) {
1388
+ const currentY = y + rowOffset;
1389
+ if (!this.#insideY(currentY, clip)) continue;
1390
+ let currentX = x;
1391
+ for (const cell of line) {
1392
+ const endX = currentX + cell.width;
1393
+ if (!(clip?.x1 !== void 0 && currentX < clip.x1 || clip?.x2 !== void 0 && endX > clip.x2)) this.#composeNativeCell(currentX, currentY, cell, overflow);
1394
+ currentX = endX;
1395
+ }
1396
+ }
2347
1397
  }
2348
- write(x, y, text, options) {
2349
- const { transformers } = options;
1398
+ writeAnsi(x, y, text, options) {
2350
1399
  if (!text) return;
2351
- this.operations.push({
2352
- type: "write",
2353
- x,
2354
- y,
2355
- text,
2356
- transformers
2357
- });
1400
+ let lines = text.split("\n");
1401
+ const clip = this.#clips.at(-1);
1402
+ const overflow = options.overflow === true;
1403
+ if (clip?.y1 !== void 0 && y < clip.y1) {
1404
+ lines = lines.slice(clip.y1 - y);
1405
+ y = clip.y1;
1406
+ }
1407
+ if (clip?.y2 !== void 0) lines = lines.slice(0, Math.max(0, clip.y2 - y));
1408
+ for (const [lineIndex, original] of lines.entries()) {
1409
+ const currentY = y + lineIndex;
1410
+ if (!this.#insideY(currentY, clip)) continue;
1411
+ let line = original;
1412
+ let currentX = x;
1413
+ if (clip?.x1 !== void 0 && currentX < clip.x1) {
1414
+ line = sliceAnsi(line, clip.x1 - currentX);
1415
+ currentX = clip.x1;
1416
+ }
1417
+ if (clip?.x2 !== void 0) line = sliceAnsi(line, 0, Math.max(0, clip.x2 - currentX));
1418
+ for (const character of transformAnsiLine(line, lineIndex, options.transformers)) {
1419
+ const width = Math.max(1, stringWidth(character.value));
1420
+ if (clip?.x2 !== void 0 && currentX + width > clip.x2) break;
1421
+ this.#writeCompatibilityCell(currentX, currentY, character, width, overflow);
1422
+ currentX += width;
1423
+ }
1424
+ }
2358
1425
  }
2359
1426
  clip(clip) {
2360
- this.operations.push({
2361
- type: "clip",
2362
- clip
2363
- });
1427
+ this.#clips.push(clip);
2364
1428
  }
2365
1429
  unclip() {
2366
- this.operations.push({ type: "unclip" });
2367
- }
2368
- get() {
2369
- const output = [];
2370
- for (let y = 0; y < this.height; y++) {
2371
- const row = [];
2372
- for (let x = 0; x < this.width; x++) row.push({
2373
- type: "char",
2374
- value: " ",
2375
- fullWidth: false,
2376
- styles: []
2377
- });
2378
- output.push(row);
2379
- }
2380
- const clips = [];
2381
- for (const operation of this.operations) {
2382
- if (operation.type === "clip") clips.push(operation.clip);
2383
- if (operation.type === "unclip") clips.pop();
2384
- if (operation.type === "write") {
2385
- const { text, transformers } = operation;
2386
- let { x, y } = operation;
2387
- let lines = text.split("\n");
2388
- const clip = clips.at(-1);
2389
- if (clip) {
2390
- const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
2391
- const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
2392
- if (clipHorizontally) {
2393
- const width = this.caches.getWidestLine(text);
2394
- if (x + width < clip.x1 || x > clip.x2) continue;
2395
- }
2396
- if (clipVertically) {
2397
- const height = lines.length;
2398
- if (y + height < clip.y1 || y > clip.y2) continue;
2399
- }
2400
- if (clipHorizontally) {
2401
- lines = lines.map((line) => {
2402
- const from = x < clip.x1 ? clip.x1 - x : 0;
2403
- const width = this.caches.getStringWidth(line);
2404
- const to = x + width > clip.x2 ? clip.x2 - x : width;
2405
- return sliceAnsi(line, from, to);
2406
- });
2407
- if (x < clip.x1) x = clip.x1;
2408
- }
2409
- if (clipVertically) {
2410
- const from = y < clip.y1 ? clip.y1 - y : 0;
2411
- const height = lines.length;
2412
- const to = y + height > clip.y2 ? clip.y2 - y : height;
2413
- lines = lines.slice(from, to);
2414
- if (y < clip.y1) y = clip.y1;
2415
- }
2416
- }
2417
- let offsetY = 0;
2418
- for (let [index, line] of lines.entries()) {
2419
- const currentLine = output[y + offsetY];
2420
- if (!currentLine) continue;
2421
- for (const transformer of transformers) line = transformer(line, index);
2422
- const characters = this.caches.getStyledChars(line);
2423
- let offsetX = x;
2424
- if (characters.length === 0) {
2425
- offsetY++;
2426
- continue;
2427
- }
2428
- const spaceCell = {
2429
- type: "char",
2430
- value: " ",
2431
- fullWidth: false,
2432
- styles: []
2433
- };
2434
- if (currentLine[offsetX]?.value === "" && offsetX > 0 && this.caches.getStringWidth(currentLine[offsetX - 1]?.value ?? "") > 1) currentLine[offsetX - 1] = spaceCell;
2435
- for (const character of characters) {
2436
- currentLine[offsetX] = character;
2437
- const characterWidth = Math.max(1, this.caches.getStringWidth(character.value));
2438
- if (characterWidth > 1) for (let columnOffset = 1; columnOffset < characterWidth; columnOffset++) currentLine[offsetX + columnOffset] = {
2439
- type: "char",
2440
- value: "",
2441
- fullWidth: false,
2442
- styles: character.styles
2443
- };
2444
- offsetX += characterWidth;
2445
- }
2446
- if (currentLine[offsetX]?.value === "") currentLine[offsetX] = spaceCell;
2447
- offsetY++;
2448
- }
2449
- }
2450
- }
2451
- return {
2452
- output: output.map((line) => {
2453
- const lineWithoutEmptyItems = line.filter((item) => item !== void 0);
2454
- return styledCharsToString(lineWithoutEmptyItems).trimEnd();
2455
- }).join("\n"),
2456
- height: output.length
2457
- };
2458
- }
2459
- };
2460
- //#endregion
2461
- //#region src/renderer.ts
2462
- const renderer = (node, isScreenReaderEnabled) => {
1430
+ this.#clips.pop();
1431
+ }
1432
+ finish() {
1433
+ return this.#screen;
1434
+ }
1435
+ #insideY(y, clip) {
1436
+ return y >= 0 && y < this.#screen.height && (clip?.y1 === void 0 || y >= clip.y1) && (clip?.y2 === void 0 || y < clip.y2);
1437
+ }
1438
+ #composeNativeCell(x, y, cell, overflow = false) {
1439
+ if (!overflow && x >= this.#screen.width) return;
1440
+ this.#screen.composeCell(x, y, {
1441
+ content: {
1442
+ grapheme: cell.grapheme,
1443
+ width: cell.width
1444
+ },
1445
+ foreground: cell.reset?.foreground ? null : cell.style.foreground ?? null,
1446
+ background: cell.reset?.background ? null : cell.style.background,
1447
+ underlineColor: cell.style.underlineColor ?? null,
1448
+ underline: cell.style.underline,
1449
+ attributes: cell.style.attributes,
1450
+ hyperlink: cell.hyperlink ?? null
1451
+ }, { overflow });
1452
+ }
1453
+ #writeCompatibilityCell(x, y, character, width, overflow = false) {
1454
+ if (!overflow && x >= this.#screen.width) return;
1455
+ const cell = cellFromStyledChar(character, width);
1456
+ this.#screen.composeCell(x, y, {
1457
+ content: {
1458
+ grapheme: cell.grapheme,
1459
+ width: cell.width
1460
+ },
1461
+ foreground: cell.style.foreground ?? null,
1462
+ background: cell.style.background,
1463
+ underlineColor: cell.style.underlineColor ?? null,
1464
+ underline: cell.style.underline,
1465
+ attributes: cell.style.attributes,
1466
+ hyperlink: cell.hyperlink ?? null
1467
+ }, { overflow });
1468
+ }
1469
+ };
1470
+ //#endregion
1471
+ //#region src/render-frame.ts
1472
+ const renderDimension = (value) => Number.isFinite(value) ? Math.max(0, Math.ceil(value)) : 0;
1473
+ const renderFrame = (node, isScreenReaderEnabled, options = {}) => {
2463
1474
  if (node.yogaNode) {
2464
1475
  if (isScreenReaderEnabled) {
2465
- const output = renderNodeToScreenReaderOutput(node, { skipStaticElements: true });
2466
- const outputHeight = output === "" ? 0 : output.split("\n").length;
1476
+ const output = renderAccessibleText(node, { skipStaticElements: true });
2467
1477
  let staticOutput = "";
2468
- if (node.staticNode) staticOutput = renderNodeToScreenReaderOutput(node.staticNode, { skipStaticElements: false });
1478
+ if (node.staticNode) staticOutput = renderAccessibleText(node.staticNode, { skipStaticElements: false });
2469
1479
  return {
2470
- output,
2471
- outputHeight,
2472
- staticOutput: staticOutput ? `${staticOutput}\n` : ""
1480
+ accessibleText: output,
1481
+ staticAccessibleText: staticOutput
2473
1482
  };
2474
1483
  }
2475
- const output = new Output({
2476
- width: node.yogaNode.getComputedWidth(),
2477
- height: node.yogaNode.getComputedHeight()
1484
+ const output = new Canvas({
1485
+ width: renderDimension(node.yogaNode.getComputedWidth()),
1486
+ height: renderDimension(node.yogaNode.getComputedHeight()),
1487
+ colorProfile: options.colorProfile,
1488
+ paintContext: options.paintContext
2478
1489
  });
2479
- renderNodeToOutput(node, output, { skipStaticElements: true });
1490
+ paintTree(node, output, { skipStaticElements: true });
2480
1491
  let staticOutput;
2481
1492
  if (node.staticNode?.yogaNode) {
2482
- staticOutput = new Output({
2483
- width: node.staticNode.yogaNode.getComputedWidth(),
2484
- height: node.staticNode.yogaNode.getComputedHeight()
1493
+ staticOutput = new Canvas({
1494
+ width: renderDimension(node.staticNode.yogaNode.getComputedWidth()),
1495
+ height: renderDimension(node.staticNode.yogaNode.getComputedHeight()),
1496
+ colorProfile: options.colorProfile,
1497
+ paintContext: options.paintContext
2485
1498
  });
2486
- renderNodeToOutput(node.staticNode, staticOutput, { skipStaticElements: false });
1499
+ paintTree(node.staticNode, staticOutput, { skipStaticElements: false });
2487
1500
  }
2488
- const { output: generatedOutput, height: outputHeight } = output.get();
2489
1501
  return {
2490
- output: generatedOutput,
2491
- outputHeight,
2492
- staticOutput: staticOutput ? `${staticOutput.get().output}\n` : ""
1502
+ screen: output.finish(),
1503
+ ...staticOutput ? { staticScreen: staticOutput.finish() } : {}
2493
1504
  };
2494
1505
  }
2495
- return {
2496
- output: "",
2497
- outputHeight: 0,
2498
- staticOutput: ""
2499
- };
1506
+ return { accessibleText: "" };
2500
1507
  };
2501
1508
  //#endregion
1509
+ //#region src/terminal/inline-presenter.ts
1510
+ /** Full-rewrite fallback and cursor state for the structured inline renderer. */
1511
+ function createInlinePresenter(stream, options = {}) {
1512
+ let previousLineCount = 0;
1513
+ let previousOutput = "";
1514
+ let hasHiddenCursor = false;
1515
+ let cursorPosition;
1516
+ let cursorDirty = false;
1517
+ let previousCursorPosition;
1518
+ let cursorWasShown = false;
1519
+ const showCursor = options.showCursor ?? false;
1520
+ const activeCursor = () => cursorDirty ? cursorPosition : void 0;
1521
+ const hasChanges = (output, cursor) => output !== previousOutput || cursorPositionChanged(cursor, previousCursorPosition);
1522
+ const present = (output) => {
1523
+ if (!showCursor && !hasHiddenCursor) {
1524
+ cliCursor.hide(stream);
1525
+ hasHiddenCursor = true;
1526
+ }
1527
+ const cursor = activeCursor();
1528
+ cursorDirty = false;
1529
+ const cursorChanged = cursorPositionChanged(cursor, previousCursorPosition);
1530
+ if (!hasChanges(output, cursor)) return false;
1531
+ const lines = output.split("\n");
1532
+ const suffix = buildCursorSuffix(lines.length - 1, cursor);
1533
+ if (output === previousOutput && cursorChanged) stream.write(buildCursorOnlySequence({
1534
+ cursorWasShown,
1535
+ previousLineCount,
1536
+ previousCursorPosition,
1537
+ cursorPosition: cursor
1538
+ }));
1539
+ else {
1540
+ previousOutput = output;
1541
+ stream.write(buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition) + ansiEscapes.eraseLines(previousLineCount) + output + suffix);
1542
+ previousLineCount = lines.length;
1543
+ }
1544
+ previousCursorPosition = cursor ? { ...cursor } : void 0;
1545
+ cursorWasShown = cursor !== void 0;
1546
+ return true;
1547
+ };
1548
+ present.clear = () => {
1549
+ stream.write(buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition) + ansiEscapes.eraseLines(previousLineCount));
1550
+ previousOutput = "";
1551
+ previousLineCount = 0;
1552
+ previousCursorPosition = void 0;
1553
+ cursorWasShown = false;
1554
+ };
1555
+ present.done = () => {
1556
+ previousOutput = "";
1557
+ previousLineCount = 0;
1558
+ previousCursorPosition = void 0;
1559
+ cursorWasShown = false;
1560
+ if (!showCursor) {
1561
+ cliCursor.show(stream);
1562
+ hasHiddenCursor = false;
1563
+ }
1564
+ };
1565
+ present.reset = () => {
1566
+ previousOutput = "";
1567
+ previousLineCount = 0;
1568
+ previousCursorPosition = void 0;
1569
+ cursorWasShown = false;
1570
+ };
1571
+ present.sync = (output) => {
1572
+ const cursor = activeCursor();
1573
+ cursorDirty = false;
1574
+ const lines = output.split("\n");
1575
+ previousOutput = output;
1576
+ previousLineCount = lines.length;
1577
+ if (!cursor && cursorWasShown) stream.write(hideCursorEscape);
1578
+ if (cursor) stream.write(buildCursorSuffix(lines.length - 1, cursor));
1579
+ previousCursorPosition = cursor ? { ...cursor } : void 0;
1580
+ cursorWasShown = cursor !== void 0;
1581
+ };
1582
+ present.setCursorPosition = (position) => {
1583
+ cursorPosition = position;
1584
+ cursorDirty = true;
1585
+ };
1586
+ present.isCursorDirty = () => cursorDirty;
1587
+ present.willRender = (output) => hasChanges(output, activeCursor());
1588
+ return present;
1589
+ }
1590
+ //#endregion
2502
1591
  //#region src/throttle.ts
2503
1592
  /**
2504
1593
  Invokes `fn` at most once per `wait` milliseconds.
@@ -2548,32 +1637,28 @@ const throttle = (fn, wait = 0) => {
2548
1637
  return throttled;
2549
1638
  };
2550
1639
  //#endregion
2551
- //#region src/terminal-size.ts
2552
- const create = (columns, rows) => ({
2553
- columns: Number.parseInt(String(columns), 10),
2554
- rows: Number.parseInt(String(rows), 10)
2555
- });
2556
- const devTty = () => {
2557
- try {
2558
- const { O_EVTONLY: evtOnly } = constants;
2559
- const flags = isMacos && evtOnly !== void 0 ? evtOnly | constants.O_NONBLOCK : constants.O_NONBLOCK;
2560
- const { columns, rows } = new WriteStream(openSync("/dev/tty", flags));
2561
- if (columns && rows) return {
2562
- columns,
2563
- rows
2564
- };
2565
- } catch {}
2566
- };
2567
- const terminalSize = () => {
2568
- const { env, stdout, stderr } = process;
2569
- if (stdout?.columns && stdout?.rows) return create(stdout.columns, stdout.rows);
2570
- if (stderr?.columns && stderr?.rows) return create(stderr.columns, stderr.rows);
2571
- if (env["COLUMNS"] && env["LINES"]) return create(env["COLUMNS"], env["LINES"]);
2572
- return devTty() ?? {
2573
- columns: 80,
2574
- rows: 24
1640
+ //#region src/terminal/render-scheduler.ts
1641
+ /** Owns frame cadence independently of React reconciliation and terminal presentation. */
1642
+ function createRenderScheduler(render, options) {
1643
+ const frameInterval = options.maxFps > 0 ? Math.max(1, Math.ceil(1e3 / options.maxFps)) : 0;
1644
+ let pending = false;
1645
+ const throttled = options.unthrottled ? void 0 : throttle(render, frameInterval);
1646
+ return {
1647
+ intervalMs: options.unthrottled ? 0 : frameInterval,
1648
+ throttled,
1649
+ get pending() {
1650
+ return pending;
1651
+ },
1652
+ schedule: options.unthrottled ? render : () => {
1653
+ pending = true;
1654
+ throttled();
1655
+ },
1656
+ immediate: render,
1657
+ markRendered() {
1658
+ pending = false;
1659
+ }
2575
1660
  };
2576
- };
1661
+ }
2577
1662
  //#endregion
2578
1663
  //#region src/utils.ts
2579
1664
  const resolveDimension = (value, fallback, defaultValue) => {
@@ -2603,6 +1688,28 @@ const getWindowSize = (stdout) => {
2603
1688
  //#region src/ink.tsx
2604
1689
  /** @jsxImportSource react */
2605
1690
  const noop = () => {};
1691
+ const beforeExitCallbacks = /* @__PURE__ */ new Set();
1692
+ const runBeforeExitCallbacks = () => {
1693
+ for (const callback of beforeExitCallbacks) callback();
1694
+ };
1695
+ function registerBeforeExit(callback) {
1696
+ if (beforeExitCallbacks.size === 0) process.on("beforeExit", runBeforeExitCallbacks);
1697
+ beforeExitCallbacks.add(callback);
1698
+ return () => {
1699
+ beforeExitCallbacks.delete(callback);
1700
+ if (beforeExitCallbacks.size === 0) process.off("beforeExit", runBeforeExitCallbacks);
1701
+ };
1702
+ }
1703
+ function bottomRows(screen, height) {
1704
+ if (screen.height <= height) return screen;
1705
+ const cropped = new Screen(screen.width, height);
1706
+ const offset = screen.height - height;
1707
+ for (let y = 0; y < height; y++) for (let x = 0; x < screen.width; x++) {
1708
+ const cell = screen.cellAt(x, y + offset);
1709
+ if (cell && cell.width > 0) cropped.setCell(x, y, cell);
1710
+ }
1711
+ return cropped;
1712
+ }
2606
1713
  const shouldClearTerminalForFrame = ({ isTTY, viewportRows, previousOutputHeight, nextOutputHeight, isUnmounting }) => {
2607
1714
  if (!isTTY) return false;
2608
1715
  const hadPreviousFrame = previousOutputHeight > 0;
@@ -2659,45 +1766,46 @@ const createInk = (options) => {
2659
1766
  rootNode.onComputeLayout = calculateLayout;
2660
1767
  const isScreenReaderEnabled = options.isScreenReaderEnabled ?? isScreenReader;
2661
1768
  const interactive = options.interactive ?? (!isInCi && Boolean(options.stdout.isTTY));
2662
- let alternateScreen = false;
2663
- const unthrottled = options.debug || isScreenReaderEnabled;
2664
- const maxFps = options.maxFps ?? 30;
2665
- const frameIntervalMs = maxFps > 0 ? Math.max(1, Math.ceil(1e3 / maxFps)) : 0;
2666
- const renderThrottleMs = unthrottled ? 0 : frameIntervalMs;
2667
- let hasPendingThrottledRender = false;
2668
- let throttledOnRender;
2669
- if (unthrottled) rootNode.onRender = onRender;
2670
- else {
2671
- const throttled = throttle(onRender, frameIntervalMs);
2672
- rootNode.onRender = () => {
2673
- hasPendingThrottledRender = true;
2674
- throttled();
2675
- };
2676
- throttledOnRender = throttled;
2677
- }
2678
- rootNode.onImmediateRender = onRender;
2679
- rootNode.onStaticChange = handleStaticChange;
2680
- const log = logUpdate.create(options.stdout, { incremental: options.incrementalRendering });
2681
- let cursorPosition;
2682
- const logThrottle = unthrottled ? void 0 : throttle((output) => {
2683
- const shouldWrite = log.willRender(output);
2684
- const sync = shouldSync();
2685
- if (sync && shouldWrite) options.stdout.write(bsu);
2686
- log(output);
2687
- if (sync && shouldWrite) options.stdout.write(esu);
1769
+ const terminal = new TerminalSession({
1770
+ stdin: options.stdin,
1771
+ stdout: options.stdout,
1772
+ stderr: options.stderr,
1773
+ colorPolicy: options.colorProfile ?? "auto",
1774
+ onCapabilitiesChange: () => rootNode.onRender?.()
1775
+ });
1776
+ const terminalOsc = {
1777
+ publishProgress: (owner, state, value) => {
1778
+ terminal.publishProgress(owner, state, value);
1779
+ },
1780
+ copyToClipboard: (text, selection) => {
1781
+ terminal.copyToClipboard(text, selection);
1782
+ },
1783
+ publishTitle: (owner, title) => terminal.publishTitle(owner, title),
1784
+ setWorkingDirectory: (directory) => terminal.setWorkingDirectory(directory),
1785
+ notify: (title) => terminal.notify(title),
1786
+ setPointerShape: (shape) => terminal.setPointerShape(shape)
1787
+ };
1788
+ const capabilitiesStore = terminal.capabilities;
1789
+ const renderScheduler = createRenderScheduler(onRender, {
1790
+ unthrottled: options.debug || isScreenReaderEnabled,
1791
+ maxFps: options.maxFps ?? 30
2688
1792
  });
2689
- const throttledLog = logThrottle ?? log;
1793
+ rootNode.onRender = renderScheduler.schedule;
1794
+ rootNode.onImmediateRender = renderScheduler.immediate;
1795
+ rootNode.onStaticChange = handleStaticChange;
1796
+ const accessiblePresenter = isScreenReaderEnabled ? createInlinePresenter(options.stdout, { showCursor: true }) : void 0;
2690
1797
  let isUnmounted = false;
2691
1798
  let isUnmounting = false;
2692
1799
  const isConcurrent = options.concurrent ?? false;
2693
1800
  let lastOutput = "";
2694
1801
  let lastOutputToRender = "";
2695
1802
  let lastOutputHeight = 0;
1803
+ let lastScreen;
2696
1804
  let lastTerminalWidth = getWindowSize(options.stdout).columns;
2697
1805
  let lastTerminalHeight = getWindowSize(options.stdout).rows;
2698
1806
  let fullStaticOutput = "";
2699
1807
  let exitResult;
2700
- let beforeExitHandler;
1808
+ let unsubscribeBeforeExit;
2701
1809
  let restoreConsole;
2702
1810
  const capturedStdioTails = {
2703
1811
  stdout: "",
@@ -2708,7 +1816,6 @@ const createInk = (options) => {
2708
1816
  let kittyFlags;
2709
1817
  let cancelKittyDetection;
2710
1818
  let nextRenderCommit;
2711
- let isSuspended = false;
2712
1819
  let pauseInput;
2713
1820
  let resumeInput;
2714
1821
  const rootTag = isConcurrent ? ConcurrentRoot : LegacyRoot;
@@ -2730,7 +1837,8 @@ const createInk = (options) => {
2730
1837
  const currentWidth = getWindowSize(options.stdout).columns;
2731
1838
  const currentHeight = getWindowSize(options.stdout).rows;
2732
1839
  if (currentWidth < lastTerminalWidth || currentHeight !== lastTerminalHeight) {
2733
- log.clear();
1840
+ clearLiveOutput();
1841
+ resetLiveOutput();
2734
1842
  lastOutput = "";
2735
1843
  lastOutputToRender = "";
2736
1844
  lastOutputHeight = 0;
@@ -2751,13 +1859,30 @@ const createInk = (options) => {
2751
1859
  unmount();
2752
1860
  }
2753
1861
  function setCursorPosition(position) {
2754
- cursorPosition = position;
2755
- log.setCursorPosition(position);
1862
+ terminal.setCursor(position);
1863
+ accessiblePresenter?.setCursorPosition(position);
2756
1864
  }
2757
1865
  function restoreLastOutput() {
2758
1866
  if (!interactive) return;
2759
- log.setCursorPosition(cursorPosition);
2760
- log(lastOutputToRender || lastOutput + "\n");
1867
+ if (isScreenReaderEnabled) {
1868
+ accessiblePresenter.setCursorPosition(terminal.cursor.position);
1869
+ accessiblePresenter(lastOutputToRender || lastOutput + "\n");
1870
+ } else if (lastScreen) terminal.present(lastScreen, {
1871
+ fullscreen: lastOutputToRender === lastOutput,
1872
+ forceRewrite: true
1873
+ });
1874
+ }
1875
+ function clearLiveOutput() {
1876
+ if (isScreenReaderEnabled) accessiblePresenter.clear();
1877
+ else terminal.clearFrame();
1878
+ }
1879
+ function finishLiveOutput() {
1880
+ if (isScreenReaderEnabled) accessiblePresenter.done();
1881
+ else terminal.finishFrame();
1882
+ }
1883
+ function resetLiveOutput() {
1884
+ if (isScreenReaderEnabled) accessiblePresenter.reset();
1885
+ else terminal.resetFrame();
2761
1886
  }
2762
1887
  function calculateLayout() {
2763
1888
  const terminalWidth = getWindowSize(options.stdout).columns;
@@ -2768,9 +1893,9 @@ const createInk = (options) => {
2768
1893
  fullStaticOutput = "";
2769
1894
  }
2770
1895
  function onRender() {
2771
- hasPendingThrottledRender = false;
1896
+ renderScheduler.markRendered();
2772
1897
  if (isUnmounted) return;
2773
- if (isSuspended) {
1898
+ if (terminal.suspended) {
2774
1899
  if (nextRenderCommit) {
2775
1900
  nextRenderCommit.resolve();
2776
1901
  nextRenderCommit = void 0;
@@ -2782,7 +1907,18 @@ const createInk = (options) => {
2782
1907
  nextRenderCommit = void 0;
2783
1908
  }
2784
1909
  const startTime = performance.now();
2785
- const { output, outputHeight, staticOutput } = renderer(rootNode, isScreenReaderEnabled);
1910
+ const rendered = renderFrame(rootNode, isScreenReaderEnabled, {
1911
+ colorProfile: terminal.colorProfile,
1912
+ paintContext: {
1913
+ appearance: capabilitiesStore.current.theme.appearance,
1914
+ palette: capabilitiesStore.current.theme.palette
1915
+ }
1916
+ });
1917
+ const screen = rendered.screen;
1918
+ const output = rendered.accessibleText ?? (screen ? terminal.encode(screen) : "");
1919
+ const outputHeight = rendered.accessibleText === void 0 ? screen?.height ?? 0 : rendered.accessibleText === "" ? 0 : rendered.accessibleText.split("\n").length;
1920
+ const staticBody = rendered.staticAccessibleText ?? (rendered.staticScreen ? terminal.encode(rendered.staticScreen) : "");
1921
+ const staticOutput = staticBody ? `${staticBody}\n` : "";
2786
1922
  options.onRender?.({ renderTime: performance.now() - startTime });
2787
1923
  const hasStaticOutput = staticOutput && staticOutput !== "\n";
2788
1924
  if (options.debug) {
@@ -2829,26 +1965,30 @@ const createInk = (options) => {
2829
1965
  return;
2830
1966
  }
2831
1967
  if (hasStaticOutput) fullStaticOutput += staticOutput;
2832
- renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : "");
1968
+ renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : "", screen, false);
2833
1969
  }
2834
1970
  function render(node) {
2835
1971
  const tree = /* @__PURE__ */ jsx(accessibilityContext.Provider, {
2836
1972
  value: { isScreenReaderEnabled },
2837
- children: /* @__PURE__ */ jsx(App, {
2838
- stdin: options.stdin,
2839
- stdout: options.stdout,
2840
- stderr: options.stderr,
2841
- exitOnCtrlC: options.exitOnCtrlC,
2842
- interactive,
2843
- renderThrottleMs,
2844
- writeToStdout,
2845
- writeToStderr,
2846
- setCursorPosition,
2847
- onExit: handleAppExit,
2848
- onWaitUntilRenderFlush: waitUntilRenderFlush,
2849
- onSuspendTerminal: suspendTerminal,
2850
- onRegisterInputControl: registerInputControl,
2851
- children: node
1973
+ children: /* @__PURE__ */ jsx(TerminalOscContext.Provider, {
1974
+ value: terminalOsc,
1975
+ children: /* @__PURE__ */ jsx(App, {
1976
+ stdin: options.stdin,
1977
+ stdout: options.stdout,
1978
+ stderr: options.stderr,
1979
+ exitOnCtrlC: options.exitOnCtrlC,
1980
+ interactive,
1981
+ renderThrottleMs: renderScheduler.intervalMs,
1982
+ terminalInput: terminal.input,
1983
+ writeToStdout,
1984
+ writeToStderr,
1985
+ setCursorPosition,
1986
+ onExit: handleAppExit,
1987
+ onWaitUntilRenderFlush: waitUntilRenderFlush,
1988
+ onSuspendTerminal: suspendTerminal,
1989
+ onRegisterInputControl: registerInputControl,
1990
+ children: node
1991
+ })
2852
1992
  })
2853
1993
  });
2854
1994
  if (isConcurrent) reconciler.updateContainer(tree, container, null, noop);
@@ -2859,7 +1999,7 @@ const createInk = (options) => {
2859
1999
  }
2860
2000
  function writeToStdout(data) {
2861
2001
  if (isUnmounted) return;
2862
- if (isSuspended) return;
2002
+ if (terminal.suspended) return;
2863
2003
  if (options.debug) {
2864
2004
  options.stdout.write(data + fullStaticOutput + lastOutput);
2865
2005
  return;
@@ -2870,14 +2010,14 @@ const createInk = (options) => {
2870
2010
  }
2871
2011
  const sync = shouldSync();
2872
2012
  if (sync) options.stdout.write(bsu);
2873
- log.clear();
2013
+ clearLiveOutput();
2874
2014
  options.stdout.write(data);
2875
2015
  restoreLastOutput();
2876
2016
  if (sync) options.stdout.write(esu);
2877
2017
  }
2878
2018
  function writeToStderr(data) {
2879
2019
  if (isUnmounted) return;
2880
- if (isSuspended) return;
2020
+ if (terminal.suspended) return;
2881
2021
  if (options.debug) {
2882
2022
  options.stderr.write(data);
2883
2023
  options.stdout.write(fullStaticOutput + lastOutput);
@@ -2889,7 +2029,7 @@ const createInk = (options) => {
2889
2029
  }
2890
2030
  const sync = shouldSync();
2891
2031
  if (sync) options.stdout.write(bsu);
2892
- log.clear();
2032
+ clearLiveOutput();
2893
2033
  options.stderr.write(data);
2894
2034
  restoreLastOutput();
2895
2035
  if (sync) options.stdout.write(esu);
@@ -2897,35 +2037,32 @@ const createInk = (options) => {
2897
2037
  function unmount(error) {
2898
2038
  if (isUnmounted || isUnmounting) return;
2899
2039
  isUnmounting = true;
2900
- if (beforeExitHandler) {
2901
- process.off("beforeExit", beforeExitHandler);
2902
- beforeExitHandler = void 0;
2903
- }
2040
+ unsubscribeBeforeExit?.();
2041
+ unsubscribeBeforeExit = void 0;
2904
2042
  const { canWriteToStdout } = getWritableStreamState(options.stdout);
2905
2043
  if (canWriteToStdout) flushCapturedStdio();
2906
- settleThrottle(throttledOnRender, canWriteToStdout);
2044
+ settleThrottle(renderScheduler.throttled, canWriteToStdout);
2907
2045
  if (canWriteToStdout) {
2908
- if (!throttledOnRender || !hasPendingThrottledRender && fullStaticOutput === "") {
2046
+ if (!renderScheduler.throttled || !renderScheduler.pending && fullStaticOutput === "") {
2909
2047
  calculateLayout();
2910
2048
  onRender();
2911
2049
  }
2912
2050
  }
2913
2051
  isUnmounted = true;
2914
2052
  unsubscribeExit();
2915
- settleThrottle(logThrottle, canWriteToStdout);
2053
+ terminal.cleanup();
2916
2054
  if (typeof restoreConsole === "function") restoreConsole();
2917
2055
  const finishUnmount = () => {
2918
2056
  if (typeof unsubscribeResize === "function") unsubscribeResize();
2919
2057
  if (cancelKittyDetection) cancelKittyDetection();
2920
2058
  if (canWriteToStdout) {
2921
2059
  if (kittyProtocolEnabled) writeBestEffort(options.stdout, ansiEscapes.popKittyKeyboard);
2922
- if (alternateScreen) {
2923
- writeBestEffort(options.stdout, ansiEscapes.exitAlternativeScreen);
2924
- writeBestEffort(options.stdout, showCursorEscape);
2925
- alternateScreen = false;
2060
+ if (terminal.alternateScreen) {
2061
+ terminal.setAlternateScreen(false);
2062
+ terminal.setCursorAppearance({ visible: true });
2926
2063
  }
2927
2064
  if (!interactive) options.stdout.write(options.debug ? "\n" : lastOutput + "\n");
2928
- else if (!options.debug) log.done();
2065
+ else if (!options.debug) finishLiveOutput();
2929
2066
  }
2930
2067
  kittyProtocolEnabled = false;
2931
2068
  instances.delete(captureTargets?.stdout ?? options.stdout);
@@ -2951,12 +2088,9 @@ const createInk = (options) => {
2951
2088
  }
2952
2089
  }
2953
2090
  async function waitUntilExit() {
2954
- if (!beforeExitHandler) {
2955
- beforeExitHandler = () => {
2956
- unmount();
2957
- };
2958
- process.once("beforeExit", beforeExitHandler);
2959
- }
2091
+ if (!unsubscribeBeforeExit) unsubscribeBeforeExit = registerBeforeExit(() => {
2092
+ unmount();
2093
+ });
2960
2094
  return exitPromise;
2961
2095
  }
2962
2096
  async function waitUntilRenderFlush() {
@@ -2979,8 +2113,7 @@ const createInk = (options) => {
2979
2113
  }
2980
2114
  reconciler.flushSyncWork();
2981
2115
  const { canWriteToStdout } = getWritableStreamState(options.stdout);
2982
- settleThrottle(throttledOnRender, canWriteToStdout);
2983
- settleThrottle(logThrottle, canWriteToStdout);
2116
+ settleThrottle(renderScheduler.throttled, canWriteToStdout);
2984
2117
  if (canWriteToStdout) {
2985
2118
  await new Promise((resolve) => {
2986
2119
  options.stdout.write("", () => {
@@ -2993,8 +2126,8 @@ const createInk = (options) => {
2993
2126
  }
2994
2127
  function clear() {
2995
2128
  if (interactive && !options.debug) {
2996
- log.clear();
2997
- log.sync(lastOutputToRender || lastOutput + "\n");
2129
+ clearLiveOutput();
2130
+ if (isScreenReaderEnabled) accessiblePresenter.sync(lastOutputToRender || lastOutput + "\n");
2998
2131
  }
2999
2132
  }
3000
2133
  function installConsolePatch() {
@@ -3066,11 +2199,7 @@ const createInk = (options) => {
3066
2199
  };
3067
2200
  }
3068
2201
  function setAlternateScreen(enabled) {
3069
- alternateScreen = enabled && interactive && Boolean(options.stdout.isTTY);
3070
- if (alternateScreen) {
3071
- writeBestEffort(options.stdout, ansiEscapes.enterAlternativeScreen);
3072
- writeBestEffort(options.stdout, hideCursorEscape);
3073
- }
2202
+ terminal.setAlternateScreen(enabled && interactive && Boolean(options.stdout.isTTY), { hideCursor: true });
3074
2203
  }
3075
2204
  function shouldSync() {
3076
2205
  return Boolean(options.stdout.isTTY) && interactive;
@@ -3088,7 +2217,8 @@ const createInk = (options) => {
3088
2217
  nextRenderCommit ??= Promise.withResolvers();
3089
2218
  return nextRenderCommit.promise;
3090
2219
  }
3091
- function renderInteractiveFrame(output, outputHeight, staticOutput) {
2220
+ function renderInteractiveFrame(output, outputHeight, staticOutput, screen, hasOverflow) {
2221
+ if (!screen) return;
3092
2222
  const hasStaticOutput = staticOutput !== "";
3093
2223
  const isTTY = Boolean(options.stdout.isTTY);
3094
2224
  const viewportRows = isTTY ? getWindowSize(options.stdout).rows : 24;
@@ -3096,8 +2226,10 @@ const createInk = (options) => {
3096
2226
  const lines = output.split("\n");
3097
2227
  output = lines.slice(lines.length - viewportRows).join("\n");
3098
2228
  outputHeight = viewportRows;
2229
+ screen = bottomRows(screen, viewportRows);
3099
2230
  }
3100
- const outputToRender = isTTY && outputHeight >= viewportRows ? output : output + "\n";
2231
+ const isFullscreen = isTTY && outputHeight >= viewportRows;
2232
+ const outputToRender = isFullscreen ? output : output + "\n";
3101
2233
  if (shouldClearTerminalForFrame({
3102
2234
  isTTY,
3103
2235
  viewportRows,
@@ -3111,21 +2243,30 @@ const createInk = (options) => {
3111
2243
  lastOutput = output;
3112
2244
  lastOutputToRender = outputToRender;
3113
2245
  lastOutputHeight = outputHeight;
3114
- log.sync(outputToRender);
2246
+ lastScreen = screen;
2247
+ terminal.resetFrame();
3115
2248
  if (sync) options.stdout.write(esu);
3116
2249
  return;
3117
2250
  }
2251
+ const willPresent = terminal.willPresent(screen, {
2252
+ fullscreen: isFullscreen,
2253
+ forceRewrite: hasStaticOutput || hasOverflow
2254
+ });
2255
+ const sync = shouldSync() && willPresent;
2256
+ if (sync) terminal.write(bsu);
3118
2257
  if (hasStaticOutput) {
3119
- const sync = shouldSync();
3120
- if (sync) options.stdout.write(bsu);
3121
- log.clear();
3122
- options.stdout.write(staticOutput);
3123
- log(outputToRender);
3124
- if (sync) options.stdout.write(esu);
3125
- } else if (output !== lastOutput || log.isCursorDirty()) throttledLog(outputToRender);
2258
+ terminal.clearFrame();
2259
+ terminal.write(staticOutput);
2260
+ }
2261
+ terminal.present(screen, {
2262
+ fullscreen: isFullscreen,
2263
+ forceRewrite: hasStaticOutput || hasOverflow
2264
+ });
2265
+ if (sync) terminal.write(esu);
3126
2266
  lastOutput = output;
3127
2267
  lastOutputToRender = outputToRender;
3128
2268
  lastOutputHeight = outputHeight;
2269
+ lastScreen = screen;
3129
2270
  }
3130
2271
  function initKittyKeyboard() {
3131
2272
  if (!options.kittyKeyboard) return;
@@ -3149,23 +2290,21 @@ const createInk = (options) => {
3149
2290
  kittyFlags = flags;
3150
2291
  }
3151
2292
  function beginSuspend() {
3152
- if (isSuspended) throw new Error("The terminal is already suspended. Resume the current suspension before suspending again.");
3153
- isSuspended = true;
2293
+ terminal.beginSuspension();
3154
2294
  if (!interactive || isUnmounted || isUnmounting) return;
3155
2295
  try {
3156
2296
  const { canWriteToStdout } = getWritableStreamState(options.stdout);
3157
- settleThrottle(throttledOnRender, canWriteToStdout);
3158
- settleThrottle(logThrottle, canWriteToStdout);
2297
+ settleThrottle(renderScheduler.throttled, canWriteToStdout);
3159
2298
  if (canWriteToStdout) flushCapturedStdio();
3160
2299
  if (canWriteToStdout) {
3161
- log.clear();
3162
- log.done();
2300
+ clearLiveOutput();
2301
+ finishLiveOutput();
3163
2302
  if (kittyProtocolEnabled) writeBestEffort(options.stdout, ansiEscapes.popKittyKeyboard);
3164
- if (alternateScreen) writeBestEffort(options.stdout, ansiEscapes.exitAlternativeScreen);
2303
+ if (terminal.alternateScreen) writeBestEffort(options.stdout, ansiEscapes.exitAlternativeScreen);
3165
2304
  }
3166
2305
  pauseInput?.();
3167
2306
  } catch (error) {
3168
- isSuspended = false;
2307
+ terminal.resume();
3169
2308
  try {
3170
2309
  resumeInput?.();
3171
2310
  } catch {}
@@ -3173,19 +2312,19 @@ const createInk = (options) => {
3173
2312
  }
3174
2313
  }
3175
2314
  async function endSuspend() {
3176
- if (!isSuspended) return;
3177
- isSuspended = false;
2315
+ if (!terminal.suspended) return;
2316
+ terminal.resume();
3178
2317
  resumeInput?.();
3179
2318
  if (!interactive || isUnmounted || isUnmounting) return;
3180
2319
  const { canWriteToStdout } = getWritableStreamState(options.stdout);
3181
2320
  if (canWriteToStdout) {
3182
- if (alternateScreen) writeBestEffort(options.stdout, ansiEscapes.enterAlternativeScreen);
2321
+ if (terminal.alternateScreen) writeBestEffort(options.stdout, ansiEscapes.enterAlternativeScreen);
3183
2322
  if (kittyProtocolEnabled && kittyFlags) writeBestEffort(options.stdout, ansiEscapes.pushKittyKeyboard(resolveFlags(kittyFlags)));
3184
2323
  }
3185
2324
  lastOutput = "";
3186
2325
  lastOutputToRender = "";
3187
2326
  lastOutputHeight = 0;
3188
- log.reset();
2327
+ resetLiveOutput();
3189
2328
  try {
3190
2329
  calculateLayout();
3191
2330
  onRender();
@@ -3197,7 +2336,9 @@ const createInk = (options) => {
3197
2336
  unmount,
3198
2337
  waitUntilExit,
3199
2338
  waitUntilRenderFlush,
3200
- clear
2339
+ clear,
2340
+ copyToClipboard: (text, selection) => terminal.copyToClipboard(text, selection),
2341
+ setProgress: (state, value) => terminal.setProgress(state, value)
3201
2342
  };
3202
2343
  };
3203
2344
  //#endregion
@@ -3214,7 +2355,6 @@ const render = (node, options) => {
3214
2355
  exitOnCtrlC: true,
3215
2356
  patchConsole: true,
3216
2357
  maxFps: 30,
3217
- incrementalRendering: false,
3218
2358
  concurrent: false,
3219
2359
  alternateScreen: false,
3220
2360
  ...getOptions(options)
@@ -3231,7 +2371,9 @@ const render = (node, options) => {
3231
2371
  cleanup() {
3232
2372
  instance.unmount();
3233
2373
  },
3234
- clear: instance.clear
2374
+ clear: instance.clear,
2375
+ copyToClipboard: instance.copyToClipboard,
2376
+ setProgress: instance.setProgress
3235
2377
  };
3236
2378
  };
3237
2379
  const getOptions = (stdout = {}) => {
@@ -3282,6 +2424,7 @@ console.log(output);
3282
2424
  */
3283
2425
  const renderToString = (node, options) => {
3284
2426
  const columns = options?.columns ?? 80;
2427
+ const colorProfile = options?.colorProfile ?? colorProfileFromLevel(detectColorLevel());
3285
2428
  const rootNode = createNode("ink-root");
3286
2429
  let capturedStaticOutput = "";
3287
2430
  rootNode.onComputeLayout = () => {
@@ -3289,7 +2432,11 @@ const renderToString = (node, options) => {
3289
2432
  rootNode.yogaNode.calculateLayout(void 0, void 0, Yoga.DIRECTION_LTR);
3290
2433
  };
3291
2434
  rootNode.onImmediateRender = () => {
3292
- const { staticOutput } = renderer(rootNode, false);
2435
+ const { staticScreen } = renderFrame(rootNode, false, { colorProfile });
2436
+ const staticOutput = staticScreen ? `${serializeScreen(staticScreen, {
2437
+ colorProfile,
2438
+ styles: colorProfile !== "none"
2439
+ })}\n` : "";
3293
2440
  if (staticOutput && staticOutput !== "\n") capturedStaticOutput += staticOutput;
3294
2441
  };
3295
2442
  let uncaughtError;
@@ -3298,7 +2445,11 @@ const renderToString = (node, options) => {
3298
2445
  }, () => {}, () => {}, () => {});
3299
2446
  reconciler.updateContainerSync(node, container, null, () => {});
3300
2447
  reconciler.flushSyncWork();
3301
- const { output } = renderer(rootNode, false);
2448
+ const { screen } = renderFrame(rootNode, false, { colorProfile });
2449
+ const output = screen ? serializeScreen(screen, {
2450
+ colorProfile,
2451
+ styles: colorProfile !== "none"
2452
+ }) : "";
3302
2453
  reconciler.updateContainerSync(null, container, null, () => {});
3303
2454
  reconciler.flushSyncWork();
3304
2455
  if (uncaughtError !== void 0) throw uncaughtError instanceof Error ? uncaughtError : new Error(String(uncaughtError));
@@ -3307,6 +2458,30 @@ const renderToString = (node, options) => {
3307
2458
  return normalizedStaticOutput || output;
3308
2459
  };
3309
2460
  //#endregion
2461
+ //#region src/components/AnsiText.tsx
2462
+ /** @jsxImportSource react */
2463
+ /**
2464
+ * Renders explicitly trusted ANSI-styled output as structured terminal cells.
2465
+ * Ordinary `Text` continues to strip terminal control sequences.
2466
+ */
2467
+ function AnsiText({ children, wrap = "wrap", "aria-label": ariaLabel, "aria-hidden": ariaHidden = false }) {
2468
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
2469
+ if (isScreenReaderEnabled) {
2470
+ if (ariaHidden) return null;
2471
+ return /* @__PURE__ */ jsx(Text, { children: ariaLabel ?? stripAnsi(children) });
2472
+ }
2473
+ return /* @__PURE__ */ jsx("ink-text", {
2474
+ style: {
2475
+ flexGrow: 0,
2476
+ flexShrink: 1,
2477
+ flexDirection: "row",
2478
+ textWrap: wrap
2479
+ },
2480
+ internal_ansi: true,
2481
+ children
2482
+ });
2483
+ }
2484
+ //#endregion
3310
2485
  //#region src/components/Static.tsx
3311
2486
  /** @jsxImportSource react */
3312
2487
  /**
@@ -3359,6 +2534,44 @@ function Transform({ children, transform, accessibilityLabel }) {
3359
2534
  });
3360
2535
  }
3361
2536
  //#endregion
2537
+ //#region src/hooks/use-stdout.ts
2538
+ /**
2539
+ A React hook that returns the stdout stream where Ink renders your app.
2540
+ */
2541
+ const useStdout = () => useContext(StdoutContext);
2542
+ //#endregion
2543
+ //#region src/components/Hyperlink.tsx
2544
+ /**
2545
+ A clickable OSC 8 hyperlink — the counterpart to the router's `<Link>`, which
2546
+ navigates between screens. On terminals without hyperlink support it falls
2547
+ back to `text (url)`.
2548
+
2549
+ ```tsx
2550
+ <Hyperlink url="https://example.com">Documentation</Hyperlink>
2551
+ ```
2552
+ */
2553
+ function Hyperlink({ url, fallback = true, children, ...textProps }) {
2554
+ const { stdout } = useStdout();
2555
+ if (!detectHyperlinkSupport(stdout)) return /* @__PURE__ */ jsxs(Text, {
2556
+ ...textProps,
2557
+ children: [children, fallback ? /* @__PURE__ */ jsxs(Text, {
2558
+ dimColor: true,
2559
+ children: [
2560
+ " (",
2561
+ url,
2562
+ ")"
2563
+ ]
2564
+ }) : null]
2565
+ });
2566
+ return /* @__PURE__ */ jsx(Transform, {
2567
+ transform: (text) => link(text, url),
2568
+ children: /* @__PURE__ */ jsx(Text, {
2569
+ ...textProps,
2570
+ children
2571
+ })
2572
+ });
2573
+ }
2574
+ //#endregion
3362
2575
  //#region src/components/Newline.tsx
3363
2576
  /**
3364
2577
  Adds one or more newline (`\n`) characters. Must be used within `<Text>` components.
@@ -3378,486 +2591,60 @@ function Spacer() {
3378
2591
  return /* @__PURE__ */ jsx(Box, { flexGrow: 1 });
3379
2592
  }
3380
2593
  //#endregion
3381
- //#region src/hooks/use-stdin.ts
2594
+ //#region src/hooks/use-capabilities.ts
3382
2595
  /**
3383
- A React hook that returns the stdin stream and stdin-related utilities.
2596
+ Returns everything knowable about the terminal: size, identity, platform,
2597
+ color depth, theme, and feature support.
2598
+
2599
+ A thin wrapper over the framework-free capabilities store (`getCapabilities`):
2600
+ environment-derived facts are available immediately; facts only the terminal
2601
+ itself can answer fill in after a lazy one-time query, and re-mounting
2602
+ consumers refreshes the dynamic facts (theme colors, pixel geometry).
2603
+ Re-renders on terminal resize and whenever query answers arrive.
3384
2604
  */
3385
- const useStdin = () => useContext(StdinContext);
3386
- const useStdinContext = () => useContext(StdinContext);
3387
- //#endregion
3388
- //#region src/parse-keypress.ts
3389
- const textDecoder = new TextDecoder();
3390
- const metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
3391
- const fnKeyRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
3392
- const keyName = {
3393
- OP: "f1",
3394
- OQ: "f2",
3395
- OR: "f3",
3396
- OS: "f4",
3397
- "[P": "f1",
3398
- "[Q": "f2",
3399
- "[R": "f3",
3400
- "[S": "f4",
3401
- "[11~": "f1",
3402
- "[12~": "f2",
3403
- "[13~": "f3",
3404
- "[14~": "f4",
3405
- "[[A": "f1",
3406
- "[[B": "f2",
3407
- "[[C": "f3",
3408
- "[[D": "f4",
3409
- "[[E": "f5",
3410
- "[15~": "f5",
3411
- "[17~": "f6",
3412
- "[18~": "f7",
3413
- "[19~": "f8",
3414
- "[20~": "f9",
3415
- "[21~": "f10",
3416
- "[23~": "f11",
3417
- "[24~": "f12",
3418
- "[A": "up",
3419
- "[B": "down",
3420
- "[C": "right",
3421
- "[D": "left",
3422
- "[E": "clear",
3423
- "[F": "end",
3424
- "[H": "home",
3425
- OA: "up",
3426
- OB: "down",
3427
- OC: "right",
3428
- OD: "left",
3429
- OE: "clear",
3430
- OF: "end",
3431
- OH: "home",
3432
- "[1~": "home",
3433
- "[2~": "insert",
3434
- "[3~": "delete",
3435
- "[4~": "end",
3436
- "[5~": "pageup",
3437
- "[6~": "pagedown",
3438
- "[[5~": "pageup",
3439
- "[[6~": "pagedown",
3440
- "[7~": "home",
3441
- "[8~": "end",
3442
- "[a": "up",
3443
- "[b": "down",
3444
- "[c": "right",
3445
- "[d": "left",
3446
- "[e": "clear",
3447
- "[2$": "insert",
3448
- "[3$": "delete",
3449
- "[5$": "pageup",
3450
- "[6$": "pagedown",
3451
- "[7$": "home",
3452
- "[8$": "end",
3453
- Oa: "up",
3454
- Ob: "down",
3455
- Oc: "right",
3456
- Od: "left",
3457
- Oe: "clear",
3458
- "[2^": "insert",
3459
- "[3^": "delete",
3460
- "[5^": "pageup",
3461
- "[6^": "pagedown",
3462
- "[7^": "home",
3463
- "[8^": "end",
3464
- "[Z": "tab"
3465
- };
3466
- const nonAlphanumericKeys = [...Object.values(keyName), "backspace"];
3467
- const isShiftKey = (code) => {
3468
- return [
3469
- "[a",
3470
- "[b",
3471
- "[c",
3472
- "[d",
3473
- "[e",
3474
- "[2$",
3475
- "[3$",
3476
- "[5$",
3477
- "[6$",
3478
- "[7$",
3479
- "[8$",
3480
- "[Z"
3481
- ].includes(code);
3482
- };
3483
- const isCtrlKey = (code) => {
3484
- return [
3485
- "Oa",
3486
- "Ob",
3487
- "Oc",
3488
- "Od",
3489
- "Oe",
3490
- "[2^",
3491
- "[3^",
3492
- "[5^",
3493
- "[6^",
3494
- "[7^",
3495
- "[8^"
3496
- ].includes(code);
3497
- };
3498
- const kittyKeyRe = /^\x1b\[(\d+)(?:;(\d+)(?::(\d+))?(?:;([\d:]+))?)?u$/;
3499
- const kittySpecialKeyRe = /^\x1b\[(\d+);(\d+):(\d+)([A-Za-z~])$/;
3500
- const kittySpecialLetterKeys = {
3501
- A: "up",
3502
- B: "down",
3503
- C: "right",
3504
- D: "left",
3505
- E: "clear",
3506
- F: "end",
3507
- H: "home",
3508
- P: "f1",
3509
- Q: "f2",
3510
- R: "f3",
3511
- S: "f4"
3512
- };
3513
- const kittySpecialNumberKeys = {
3514
- 2: "insert",
3515
- 3: "delete",
3516
- 5: "pageup",
3517
- 6: "pagedown",
3518
- 7: "home",
3519
- 8: "end",
3520
- 11: "f1",
3521
- 12: "f2",
3522
- 13: "f3",
3523
- 14: "f4",
3524
- 15: "f5",
3525
- 17: "f6",
3526
- 18: "f7",
3527
- 19: "f8",
3528
- 20: "f9",
3529
- 21: "f10",
3530
- 23: "f11",
3531
- 24: "f12"
3532
- };
3533
- const kittyCodepointNames = {
3534
- 27: "escape",
3535
- 9: "tab",
3536
- 127: "backspace",
3537
- 8: "backspace",
3538
- 57358: "capslock",
3539
- 57359: "scrolllock",
3540
- 57360: "numlock",
3541
- 57361: "printscreen",
3542
- 57362: "pause",
3543
- 57363: "menu",
3544
- 57376: "f13",
3545
- 57377: "f14",
3546
- 57378: "f15",
3547
- 57379: "f16",
3548
- 57380: "f17",
3549
- 57381: "f18",
3550
- 57382: "f19",
3551
- 57383: "f20",
3552
- 57384: "f21",
3553
- 57385: "f22",
3554
- 57386: "f23",
3555
- 57387: "f24",
3556
- 57388: "f25",
3557
- 57389: "f26",
3558
- 57390: "f27",
3559
- 57391: "f28",
3560
- 57392: "f29",
3561
- 57393: "f30",
3562
- 57394: "f31",
3563
- 57395: "f32",
3564
- 57396: "f33",
3565
- 57397: "f34",
3566
- 57398: "f35",
3567
- 57399: "kp0",
3568
- 57400: "kp1",
3569
- 57401: "kp2",
3570
- 57402: "kp3",
3571
- 57403: "kp4",
3572
- 57404: "kp5",
3573
- 57405: "kp6",
3574
- 57406: "kp7",
3575
- 57407: "kp8",
3576
- 57408: "kp9",
3577
- 57409: "kpdecimal",
3578
- 57410: "kpdivide",
3579
- 57411: "kpmultiply",
3580
- 57412: "kpsubtract",
3581
- 57413: "kpadd",
3582
- 57414: "kpenter",
3583
- 57415: "kpequal",
3584
- 57416: "kpseparator",
3585
- 57417: "kpleft",
3586
- 57418: "kpright",
3587
- 57419: "kpup",
3588
- 57420: "kpdown",
3589
- 57421: "kppageup",
3590
- 57422: "kppagedown",
3591
- 57423: "kphome",
3592
- 57424: "kpend",
3593
- 57425: "kpinsert",
3594
- 57426: "kpdelete",
3595
- 57427: "kpbegin",
3596
- 57428: "mediaplay",
3597
- 57429: "mediapause",
3598
- 57430: "mediaplaypause",
3599
- 57431: "mediareverse",
3600
- 57432: "mediastop",
3601
- 57433: "mediafastforward",
3602
- 57434: "mediarewind",
3603
- 57435: "mediatracknext",
3604
- 57436: "mediatrackprevious",
3605
- 57437: "mediarecord",
3606
- 57438: "lowervolume",
3607
- 57439: "raisevolume",
3608
- 57440: "mutevolume",
3609
- 57441: "leftshift",
3610
- 57442: "leftcontrol",
3611
- 57443: "leftalt",
3612
- 57444: "leftsuper",
3613
- 57445: "lefthyper",
3614
- 57446: "leftmeta",
3615
- 57447: "rightshift",
3616
- 57448: "rightcontrol",
3617
- 57449: "rightalt",
3618
- 57450: "rightsuper",
3619
- 57451: "righthyper",
3620
- 57452: "rightmeta",
3621
- 57453: "isoLevel3Shift",
3622
- 57454: "isoLevel5Shift"
3623
- };
3624
- const isValidCodepoint = (cp) => cp >= 0 && cp <= 1114111 && !(cp >= 55296 && cp <= 57343);
3625
- const safeFromCodePoint = (cp) => isValidCodepoint(cp) ? String.fromCodePoint(cp) : "?";
3626
- function resolveEventType(value) {
3627
- if (value === 3) return "release";
3628
- if (value === 2) return "repeat";
3629
- return "press";
3630
- }
3631
- function parseKittyModifiers(modifiers) {
3632
- return {
3633
- ctrl: !!(modifiers & kittyModifiers.ctrl),
3634
- shift: !!(modifiers & kittyModifiers.shift),
3635
- meta: !!(modifiers & (kittyModifiers.meta | kittyModifiers.alt)),
3636
- super: !!(modifiers & kittyModifiers.super),
3637
- hyper: !!(modifiers & kittyModifiers.hyper),
3638
- capsLock: !!(modifiers & kittyModifiers.capsLock),
3639
- numLock: !!(modifiers & kittyModifiers.numLock)
3640
- };
3641
- }
3642
- const parseKittyKeypress = (s) => {
3643
- const match = kittyKeyRe.exec(s);
3644
- if (!match) return null;
3645
- const codepoint = parseInt(match[1], 10);
3646
- const modifiers = match[2] ? Math.max(0, parseInt(match[2], 10) - 1) : 0;
3647
- const eventType = match[3] ? parseInt(match[3], 10) : 1;
3648
- const textField = match[4];
3649
- if (!isValidCodepoint(codepoint)) return null;
3650
- let text;
3651
- if (textField) text = textField.split(":").map((cp) => safeFromCodePoint(parseInt(cp, 10))).join("");
3652
- let name;
3653
- let isPrintable;
3654
- if (codepoint === 32) {
3655
- name = "space";
3656
- isPrintable = true;
3657
- } else if (codepoint === 13) {
3658
- name = "return";
3659
- isPrintable = true;
3660
- } else if (kittyCodepointNames[codepoint]) {
3661
- name = kittyCodepointNames[codepoint];
3662
- isPrintable = false;
3663
- } else if (codepoint >= 1 && codepoint <= 26) {
3664
- name = String.fromCodePoint(codepoint + 96);
3665
- isPrintable = false;
3666
- } else {
3667
- name = safeFromCodePoint(codepoint).toLowerCase();
3668
- isPrintable = true;
3669
- }
3670
- if (isPrintable && !text) text = safeFromCodePoint(codepoint);
3671
- return {
3672
- name,
3673
- ...parseKittyModifiers(modifiers),
3674
- eventType: resolveEventType(eventType),
3675
- sequence: s,
3676
- raw: s,
3677
- isKittyProtocol: true,
3678
- isPrintable,
3679
- text
3680
- };
3681
- };
3682
- const parseKittySpecialKey = (s) => {
3683
- const match = kittySpecialKeyRe.exec(s);
3684
- if (!match) return null;
3685
- const number = parseInt(match[1], 10);
3686
- const modifiers = Math.max(0, parseInt(match[2], 10) - 1);
3687
- const eventType = parseInt(match[3], 10);
3688
- const terminator = match[4];
3689
- const name = terminator === "~" ? kittySpecialNumberKeys[number] : kittySpecialLetterKeys[terminator];
3690
- if (!name) return null;
3691
- return {
3692
- name,
3693
- ...parseKittyModifiers(modifiers),
3694
- eventType: resolveEventType(eventType),
3695
- sequence: s,
3696
- raw: s,
3697
- isKittyProtocol: true,
3698
- isPrintable: false
3699
- };
3700
- };
3701
- const parseKeypress = (s = "") => {
3702
- let parts;
3703
- if (s instanceof Uint8Array) {
3704
- if (s[0] > 127 && s[1] === void 0) s = "\x1B" + textDecoder.decode(Uint8Array.of(s[0] - 128));
3705
- else s = textDecoder.decode(s);
3706
- } else if (s !== void 0 && typeof s !== "string") s = String(s);
3707
- else if (!s) s = "";
3708
- const kittyResult = parseKittyKeypress(s);
3709
- if (kittyResult) return kittyResult;
3710
- const kittySpecialResult = parseKittySpecialKey(s);
3711
- if (kittySpecialResult) return kittySpecialResult;
3712
- if (kittyKeyRe.test(s)) return {
3713
- name: "",
3714
- ctrl: false,
3715
- meta: false,
3716
- shift: false,
3717
- sequence: s,
3718
- raw: s,
3719
- isKittyProtocol: true,
3720
- isPrintable: false
3721
- };
3722
- const key = {
3723
- name: "",
3724
- ctrl: false,
3725
- meta: false,
3726
- shift: false,
3727
- sequence: s,
3728
- raw: s
3729
- };
3730
- key.sequence = key.sequence || s || key.name;
3731
- if (s === "\r" || s === "\x1B\r") {
3732
- key.raw = void 0;
3733
- key.name = "return";
3734
- key.meta = s.length === 2;
3735
- } else if (s === "\n") key.name = "enter";
3736
- else if (s === " ") key.name = "tab";
3737
- else if (s === "\b" || s === "\x1B\b") {
3738
- key.name = "backspace";
3739
- key.meta = s.charAt(0) === "\x1B";
3740
- } else if (s === "" || s === "\x1B") {
3741
- key.name = "backspace";
3742
- key.meta = s.charAt(0) === "\x1B";
3743
- } else if (s === "\x1B" || s === "\x1B\x1B") {
3744
- key.name = "escape";
3745
- key.meta = s.length === 2;
3746
- } else if (s === " " || s === "\x1B ") {
3747
- key.name = "space";
3748
- key.meta = s.length === 2;
3749
- } else if (s.length === 1 && s <= "") {
3750
- key.name = String.fromCharCode(s.charCodeAt(0) + "a".charCodeAt(0) - 1);
3751
- key.ctrl = true;
3752
- } else if (s.length === 1 && s >= "0" && s <= "9") key.name = "number";
3753
- else if (s.length === 1 && s >= "a" && s <= "z") key.name = s;
3754
- else if (s.length === 1 && s >= "A" && s <= "Z") {
3755
- key.name = s.toLowerCase();
3756
- key.shift = true;
3757
- } else if (parts = metaKeyCodeRe.exec(s)) {
3758
- key.name = parts[1].toLowerCase();
3759
- key.meta = true;
3760
- key.shift = /^[A-Z]$/.test(parts[1]);
3761
- } else if (parts = fnKeyRe.exec(s)) {
3762
- const segs = [...s];
3763
- if (segs[0] === "\x1B" && segs[1] === "\x1B") key.meta = true;
3764
- const code = [
3765
- parts[1],
3766
- parts[2],
3767
- parts[4],
3768
- parts[6]
3769
- ].filter(Boolean).join("");
3770
- const modifier = Number(parts[3] || parts[5] || 1) - 1;
3771
- key.ctrl = !!(modifier & 4);
3772
- key.meta = key.meta || !!(modifier & 10);
3773
- key.shift = !!(modifier & 1);
3774
- key.code = code;
3775
- key.name = keyName[code] ?? "";
3776
- key.shift = isShiftKey(code) || key.shift;
3777
- key.ctrl = isCtrlKey(code) || key.ctrl;
3778
- }
3779
- return key;
2605
+ const useCapabilities = () => {
2606
+ const { stdout } = useStdout();
2607
+ const { stdin } = useStdinContext();
2608
+ const store = getCapabilities(stdin, stdout);
2609
+ const capabilities = useSyncExternalStore(store.subscribe, () => store.current);
2610
+ useEffect(() => {
2611
+ store.query();
2612
+ }, [store]);
2613
+ return capabilities;
3780
2614
  };
3781
- //#endregion
3782
- //#region src/hooks/use-input.ts
3783
2615
  /**
3784
- A React hook that returns `void` and handles user input.
3785
- 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`.
3786
-
3787
- ```
3788
- import {useInput} from 'ink';
2616
+ Calls `onChange` whenever the terminal changes: resizes (including in-band
2617
+ pixel geometry), color scheme switches, window focus, and query answers
2618
+ arriving. The React wrapper over `capabilities.subscribe()` for side effects —
2619
+ for rendering, use `useCapabilities` instead.
3789
2620
 
3790
- const UserInput = () => {
3791
- useInput((input, key) => {
3792
- if (input === 'q') {
3793
- // Exit program
3794
- }
2621
+ The callback always sees the latest render's closure and changing it does not
2622
+ resubscribe. Both the new and previous snapshot are passed, so handlers can
2623
+ react to the specific change:
3795
2624
 
3796
- if (key.leftArrow) {
3797
- // Left arrow key pressed
2625
+ ```tsx
2626
+ useCapabilitiesChange((next, previous) => {
2627
+ if (next.theme.appearance !== previous.theme.appearance) {
2628
+ // re-theme
3798
2629
  }
3799
2630
  });
3800
-
3801
- return …
3802
- };
3803
2631
  ```
3804
2632
  */
3805
- const useInput = (inputHandler, options = {}) => {
3806
- const { setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdinContext();
2633
+ const useCapabilitiesChange = (onChange) => {
2634
+ const { stdout } = useStdout();
2635
+ const { stdin } = useStdinContext();
2636
+ const store = getCapabilities(stdin, stdout);
2637
+ const handleChange = useEffectEvent(onChange);
3807
2638
  useEffect(() => {
3808
- if (options.isActive === false) return;
3809
- setRawMode(true);
3810
- return () => {
3811
- setRawMode(false);
3812
- };
3813
- }, [options.isActive, setRawMode]);
3814
- const handleData = useEffectEvent((data) => {
3815
- const keypress = parseKeypress(data);
3816
- const key = {
3817
- upArrow: keypress.name === "up",
3818
- downArrow: keypress.name === "down",
3819
- leftArrow: keypress.name === "left",
3820
- rightArrow: keypress.name === "right",
3821
- pageDown: keypress.name === "pagedown",
3822
- pageUp: keypress.name === "pageup",
3823
- home: keypress.name === "home",
3824
- end: keypress.name === "end",
3825
- return: keypress.name === "return",
3826
- escape: keypress.name === "escape",
3827
- ctrl: keypress.ctrl,
3828
- shift: keypress.shift,
3829
- tab: keypress.name === "tab",
3830
- backspace: keypress.name === "backspace",
3831
- delete: keypress.name === "delete",
3832
- meta: keypress.meta,
3833
- super: keypress.super ?? false,
3834
- hyper: keypress.hyper ?? false,
3835
- capsLock: keypress.capsLock ?? false,
3836
- numLock: keypress.numLock ?? false,
3837
- eventType: keypress.eventType
3838
- };
3839
- let input;
3840
- if (keypress.isKittyProtocol) {
3841
- if (keypress.isPrintable) input = keypress.text ?? keypress.name;
3842
- else if (keypress.ctrl && keypress.name.length === 1) input = keypress.name;
3843
- else input = "";
3844
- } else if (keypress.ctrl) input = keypress.name ?? "";
3845
- else input = keypress.sequence;
3846
- if (!keypress.isKittyProtocol && nonAlphanumericKeys.includes(keypress.name)) input = "";
3847
- if (input.startsWith("\x1B")) input = input.slice(1);
3848
- if (input.length === 1 && /[A-Z]/.test(input)) key.shift = true;
3849
- if (input === "c" && key.ctrl && internal_exitOnCtrlC) return;
3850
- reconciler.discreteUpdates(() => {
3851
- inputHandler(input, key);
2639
+ let previous = store.current;
2640
+ const unsubscribe = store.subscribe((next) => {
2641
+ const before = previous;
2642
+ previous = next;
2643
+ handleChange(next, before);
3852
2644
  });
3853
- });
3854
- useEffect(() => {
3855
- if (options.isActive === false) return;
3856
- internal_eventEmitter.on("input", handleData);
3857
- return () => {
3858
- internal_eventEmitter.removeListener("input", handleData);
3859
- };
3860
- }, [options.isActive, internal_eventEmitter]);
2645
+ store.query();
2646
+ return unsubscribe;
2647
+ }, [store]);
3861
2648
  };
3862
2649
  //#endregion
3863
2650
  //#region src/hooks/use-paste.ts
@@ -3921,65 +2708,12 @@ A React hook that returns app lifecycle methods like `exit()` and `waitUntilRend
3921
2708
  */
3922
2709
  const useApp = () => useContext(AppContext);
3923
2710
  //#endregion
3924
- //#region src/hooks/use-stdout.ts
3925
- /**
3926
- A React hook that returns the stdout stream where Ink renders your app.
3927
- */
3928
- const useStdout = () => useContext(StdoutContext);
3929
- //#endregion
3930
2711
  //#region src/hooks/use-stderr.ts
3931
2712
  /**
3932
2713
  A React hook that returns the stderr stream.
3933
2714
  */
3934
2715
  const useStderr = () => useContext(StderrContext);
3935
2716
  //#endregion
3936
- //#region src/hooks/use-focus.ts
3937
- /**
3938
- A React hook that returns focus state and focus controls for the current component.
3939
- 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.
3940
- */
3941
- const useFocus = ({ isActive = true, autoFocus = false, id: customId } = {}) => {
3942
- const { isRawModeSupported, setRawMode } = useStdin();
3943
- const { activeId, add, remove, activate, deactivate, focus } = useContext(FocusContext);
3944
- const autoId = useId();
3945
- const id = customId ?? autoId;
3946
- useEffect(() => {
3947
- add(id, { autoFocus });
3948
- return () => {
3949
- remove(id);
3950
- };
3951
- }, [
3952
- id,
3953
- autoFocus,
3954
- add,
3955
- remove
3956
- ]);
3957
- useEffect(() => {
3958
- if (isActive) activate(id);
3959
- else deactivate(id);
3960
- }, [
3961
- isActive,
3962
- id,
3963
- activate,
3964
- deactivate
3965
- ]);
3966
- useEffect(() => {
3967
- if (!isRawModeSupported || !isActive) return;
3968
- setRawMode(true);
3969
- return () => {
3970
- setRawMode(false);
3971
- };
3972
- }, [
3973
- isActive,
3974
- isRawModeSupported,
3975
- setRawMode
3976
- ]);
3977
- return {
3978
- isFocused: Boolean(id) && activeId === id,
3979
- focus
3980
- };
3981
- };
3982
- //#endregion
3983
2717
  //#region src/hooks/use-focus-manager.ts
3984
2718
  /**
3985
2719
  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.
@@ -4122,6 +2856,54 @@ function normalizeAnimationInterval(interval) {
4122
2856
  return Math.min(maximumTimerInterval, Math.max(1, interval));
4123
2857
  }
4124
2858
  //#endregion
2859
+ //#region src/hooks/use-terminal-osc.ts
2860
+ const useProgress = ({ state, value }) => {
2861
+ const terminal = useContext(TerminalOscContext);
2862
+ const owner = useRef(void 0);
2863
+ owner.current ??= Symbol("terminal-progress");
2864
+ useEffect(() => {
2865
+ terminal.publishProgress(owner.current, state, value);
2866
+ }, [
2867
+ state,
2868
+ terminal,
2869
+ value
2870
+ ]);
2871
+ useEffect(() => {
2872
+ return () => {
2873
+ terminal.publishProgress(owner.current, "inactive");
2874
+ };
2875
+ }, [terminal]);
2876
+ };
2877
+ const useClipboard = () => {
2878
+ const terminal = useContext(TerminalOscContext);
2879
+ return useCallback((text, selection) => terminal.copyToClipboard(text, selection), [terminal]);
2880
+ };
2881
+ const useTitle = (title) => {
2882
+ const terminal = useContext(TerminalOscContext);
2883
+ const owner = useRef(void 0);
2884
+ owner.current ??= Symbol("terminal-title");
2885
+ useEffect(() => {
2886
+ terminal.publishTitle(owner.current, title);
2887
+ }, [terminal, title]);
2888
+ useEffect(() => () => terminal.publishTitle(owner.current, void 0), [terminal]);
2889
+ };
2890
+ const useWorkingDirectory = (directory) => {
2891
+ const terminal = useContext(TerminalOscContext);
2892
+ useEffect(() => {
2893
+ terminal.setWorkingDirectory(directory);
2894
+ }, [directory, terminal]);
2895
+ };
2896
+ const useNotification = () => {
2897
+ const terminal = useContext(TerminalOscContext);
2898
+ return useCallback((title) => terminal.notify(title), [terminal]);
2899
+ };
2900
+ const usePointerShape = (shape) => {
2901
+ const terminal = useContext(TerminalOscContext);
2902
+ useEffect(() => {
2903
+ terminal.setPointerShape(shape);
2904
+ }, [shape, terminal]);
2905
+ };
2906
+ //#endregion
4125
2907
  //#region src/hooks/use-window-size.ts
4126
2908
  /**
4127
2909
  A React hook that returns the current terminal window dimensions and re-renders the component whenever the terminal is resized.
@@ -4236,4 +3018,4 @@ const measureElement = (node) => {
4236
3018
  };
4237
3019
  };
4238
3020
  //#endregion
4239
- 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 };
3021
+ export { AnsiText, Box, Hyperlink, Newline, Spacer, Static, Text, Transform, applyTerminalQuery, capabilities, createSupportsColor, detectCapabilities, detectColorLevel, detectHyperlinkSupport, detectTerminal, detectUnicodeSupport, getCapabilities, getTerminalQuery, kittyFlags, kittyModifiers, measureElement, queryTerminal, refreshTerminalQuery, render, renderToString, useAnimation, useApp, useBoxMetrics, useCapabilities, useCapabilitiesChange, useClipboard, useCursor, useFocus, useFocusManager, useInput, useIsScreenReaderEnabled, useNotification, usePaste, usePointerShape, useProgress, useStderr, useStdin, useStdout, useTitle, useWindowSize, useWorkingDirectory };