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

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 (212) hide show
  1. package/README.md +499 -313
  2. package/THIRD_PARTY_NOTICES.md +70 -23
  3. package/dist/Text-DV9CuzAT.d.ts +452 -0
  4. package/dist/ansi.d.ts +217 -0
  5. package/dist/ansi.js +87 -0
  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-BAC9-TZX.js +592 -0
  11. package/dist/color-policy-CCxuHIdD.d.ts +22 -0
  12. package/dist/color-profile-CyeHnG1T.d.ts +97 -0
  13. package/dist/color-profile-DHhQHY55.js +36 -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-B3dL4Q11.js +374 -0
  18. package/dist/detect-Db6GbKOm.d.ts +195 -0
  19. package/dist/{devtools-QpCMm9JH.mjs → devtools-DbthxoD1.js} +23 -24
  20. package/dist/env-YVw64yZS.js +9 -0
  21. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  22. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  23. package/dist/index-D48vQhhe.d.ts +21 -0
  24. package/dist/index-DDVME65c.d.ts +919 -0
  25. package/dist/index.d.ts +1310 -0
  26. package/dist/index.js +3209 -0
  27. package/dist/osc-BFKKSqpg.js +71 -0
  28. package/dist/osc-Cn0fw77g.d.ts +23 -0
  29. package/dist/paint-Cx-zC_sX.d.ts +81 -0
  30. package/dist/query-BNc2B8GD.d.ts +152 -0
  31. package/dist/router.d.ts +392 -0
  32. package/dist/router.js +709 -0
  33. package/dist/sample-Cqw1bjUL.js +445 -0
  34. package/dist/screen-CgC2WlVM.d.ts +49 -0
  35. package/dist/screen-CiPytswf.js +342 -0
  36. package/dist/screen.d.ts +5 -0
  37. package/dist/screen.js +5 -0
  38. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  39. package/dist/serialize-BTkAZgw1.js +79 -0
  40. package/dist/session-DDQ5V300.js +723 -0
  41. package/dist/sgr-BhwaWAJB.js +246 -0
  42. package/dist/store-C1P5fOUi.d.ts +72 -0
  43. package/dist/string-width-CijQwpIk.js +69 -0
  44. package/dist/strip-BvU4toXG.js +6 -0
  45. package/dist/terminal.d.ts +121 -0
  46. package/dist/terminal.js +2 -0
  47. package/dist/tokenize-AjqbvtiT.js +1242 -0
  48. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  49. package/dist/truncate-D31fhU6i.js +562 -0
  50. package/dist/use-focus-CcCIdCWA.js +1337 -0
  51. package/dist/yoga-5jKhYCJC.js +3465 -0
  52. package/dist/yoga.d.ts +2 -0
  53. package/dist/yoga.js +2 -0
  54. package/package.json +70 -24
  55. package/src/ansi/chalk.ts +138 -0
  56. package/src/ansi/cursor.ts +46 -0
  57. package/src/ansi/east-asian-width.ts +259 -0
  58. package/src/ansi/escapes.ts +120 -0
  59. package/src/ansi/graphemes.ts +8 -0
  60. package/src/ansi/hyperlink.ts +44 -0
  61. package/src/ansi/index.ts +27 -0
  62. package/src/ansi/osc.ts +77 -0
  63. package/src/ansi/sgr.ts +234 -0
  64. package/src/ansi/slice.ts +43 -0
  65. package/src/ansi/string-width.ts +121 -0
  66. package/src/ansi/strip.ts +13 -0
  67. package/src/ansi/tokenize.ts +380 -0
  68. package/src/ansi/truncate.ts +196 -0
  69. package/src/ansi/wrap.ts +765 -0
  70. package/src/ansi-tokenizer.ts +510 -0
  71. package/src/capabilities/color-policy.ts +34 -0
  72. package/src/capabilities/detect.ts +608 -0
  73. package/src/capabilities/index.ts +37 -0
  74. package/src/capabilities/query.ts +679 -0
  75. package/src/capabilities/store.ts +394 -0
  76. package/src/code-excerpt.ts +39 -0
  77. package/src/color/index.ts +3 -0
  78. package/src/color/paint.ts +169 -0
  79. package/src/color/palette.ts +48 -0
  80. package/src/color/sample.ts +323 -0
  81. package/src/color.ts +1 -0
  82. package/src/components/AccessibilityContext.ts +5 -0
  83. package/src/components/AnimationContext.ts +24 -0
  84. package/src/components/AnsiText.tsx +42 -0
  85. package/src/components/App.tsx +878 -0
  86. package/src/components/AppContext.ts +111 -0
  87. package/src/components/BackgroundContext.ts +7 -0
  88. package/src/components/Box.tsx +100 -0
  89. package/src/components/CursorContext.ts +19 -0
  90. package/src/components/ErrorBoundary.tsx +39 -0
  91. package/src/components/ErrorOverview.tsx +135 -0
  92. package/src/components/FocusContext.ts +30 -0
  93. package/src/components/Hyperlink.tsx +56 -0
  94. package/src/components/Newline.tsx +16 -0
  95. package/src/components/Spacer.tsx +11 -0
  96. package/src/components/Static.tsx +60 -0
  97. package/src/components/StderrContext.ts +24 -0
  98. package/src/components/StdinContext.ts +48 -0
  99. package/src/components/StdoutContext.ts +26 -0
  100. package/src/components/TerminalOscContext.ts +25 -0
  101. package/src/components/Text.tsx +122 -0
  102. package/src/components/Transform.tsx +38 -0
  103. package/src/components/VirtualList.tsx +128 -0
  104. package/src/cursor-position.ts +103 -0
  105. package/src/devtools.ts +103 -0
  106. package/src/dom.ts +301 -0
  107. package/src/env.ts +12 -0
  108. package/src/get-max-width.ts +11 -0
  109. package/src/global.d.ts +38 -0
  110. package/src/glyphs.ts +99 -0
  111. package/src/hooks/use-animation.ts +142 -0
  112. package/src/hooks/use-app.ts +8 -0
  113. package/src/hooks/use-box-metrics.ts +134 -0
  114. package/src/hooks/use-capabilities.ts +73 -0
  115. package/src/hooks/use-cursor.ts +33 -0
  116. package/src/hooks/use-focus-manager.ts +62 -0
  117. package/src/hooks/use-focus.ts +82 -0
  118. package/src/hooks/use-input.ts +267 -0
  119. package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
  120. package/src/hooks/use-paste.ts +78 -0
  121. package/src/hooks/use-stderr.ts +8 -0
  122. package/src/hooks/use-stdin.ts +10 -0
  123. package/src/hooks/use-stdout.ts +8 -0
  124. package/src/hooks/use-terminal-osc.ts +59 -0
  125. package/src/hooks/use-virtual-scroll.ts +84 -0
  126. package/src/hooks/use-window-size.ts +37 -0
  127. package/src/index.ts +96 -0
  128. package/src/ink.tsx +1452 -0
  129. package/src/input-parser.ts +303 -0
  130. package/src/instances.ts +9 -0
  131. package/src/kitty-keyboard.ts +185 -0
  132. package/src/measure-element.ts +62 -0
  133. package/src/measure-text.ts +31 -0
  134. package/src/paint-tree.ts +220 -0
  135. package/src/parse-keypress.ts +515 -0
  136. package/src/parse-stack-line.ts +138 -0
  137. package/src/patch-console.ts +106 -0
  138. package/src/quick-lru.ts +85 -0
  139. package/src/reconciler.ts +425 -0
  140. package/src/render-background.ts +59 -0
  141. package/src/render-border.ts +167 -0
  142. package/src/render-frame.ts +83 -0
  143. package/src/render-to-string.ts +146 -0
  144. package/src/render.ts +284 -0
  145. package/src/router/components.tsx +343 -0
  146. package/src/router/context.ts +41 -0
  147. package/src/router/history.ts +194 -0
  148. package/src/router/hooks.tsx +391 -0
  149. package/src/router/index.ts +34 -0
  150. package/src/router/matcher.ts +571 -0
  151. package/src/sanitize-ansi.ts +33 -0
  152. package/src/screen/ansi.ts +184 -0
  153. package/src/screen/canvas.ts +160 -0
  154. package/src/screen/cell.ts +138 -0
  155. package/src/screen/color-profile.ts +47 -0
  156. package/src/screen/geometry.ts +9 -0
  157. package/src/screen/index.ts +6 -0
  158. package/src/screen/screen.ts +305 -0
  159. package/src/screen/serialize.ts +129 -0
  160. package/src/screen.ts +1 -0
  161. package/src/semantic-text-style.ts +118 -0
  162. package/src/signal-exit.ts +106 -0
  163. package/src/squash-text-nodes.ts +37 -0
  164. package/src/stream.ts +28 -0
  165. package/src/structured-text.ts +325 -0
  166. package/src/styles.ts +753 -0
  167. package/src/terminal/index.ts +2 -0
  168. package/src/terminal/inline-presenter.ts +120 -0
  169. package/src/terminal/input.ts +92 -0
  170. package/src/terminal/render-scheduler.ts +37 -0
  171. package/src/terminal/screen-presenter.ts +242 -0
  172. package/src/terminal/session.ts +408 -0
  173. package/src/terminal-size.ts +58 -0
  174. package/src/terminal.ts +1 -0
  175. package/src/testing/browser.ts +588 -0
  176. package/src/testing/emulators.ts +205 -0
  177. package/src/testing/explorer-app/index.html +12 -0
  178. package/src/testing/explorer-app/main.ts +381 -0
  179. package/src/testing/explorer-app/style.css +194 -0
  180. package/src/testing/explorer-app/tsconfig.json +15 -0
  181. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  182. package/src/testing/index.ts +26 -0
  183. package/src/testing/keys.ts +56 -0
  184. package/src/testing/live.ts +85 -0
  185. package/src/testing/matchers.ts +70 -0
  186. package/src/testing/public.ts +94 -0
  187. package/src/testing/terminal.ts +361 -0
  188. package/src/testing/vitest.ts +157 -0
  189. package/src/throttle.ts +73 -0
  190. package/src/transform-adapter.ts +14 -0
  191. package/src/types.ts +10 -0
  192. package/src/virtual-scroll.ts +133 -0
  193. package/src/wrap-text.ts +54 -0
  194. package/src/yoga/config.ts +57 -0
  195. package/src/yoga/core/absoluteLayout.ts +626 -0
  196. package/src/yoga/core/baseline.ts +66 -0
  197. package/src/yoga/core/cache.ts +136 -0
  198. package/src/yoga/core/calculateLayout.ts +2926 -0
  199. package/src/yoga/core/config.ts +104 -0
  200. package/src/yoga/core/flexLine.ts +177 -0
  201. package/src/yoga/core/helpers.ts +293 -0
  202. package/src/yoga/core/layoutResults.ts +167 -0
  203. package/src/yoga/core/node.ts +611 -0
  204. package/src/yoga/core/numeric.ts +44 -0
  205. package/src/yoga/core/pixelGrid.ts +151 -0
  206. package/src/yoga/core/style.ts +887 -0
  207. package/src/yoga/core/types.ts +224 -0
  208. package/src/yoga/generated/YGEnums.ts +263 -0
  209. package/src/yoga/index.ts +19 -0
  210. package/src/yoga/node.ts +1140 -0
  211. package/dist/index.d.mts +0 -2379
  212. package/dist/index.mjs +0 -10072
@@ -0,0 +1,723 @@
1
+ import { G as pasteEnd, K as pasteStart, S as ansiEscapes } from "./sgr-BhwaWAJB.js";
2
+ import { a as setPointerShape, c as setWorkingDirectory, i as setClipboard, l as tmuxPassthrough, n as notify, o as setTerminalProgress, s as setWindowTitle, u as cliCursor } from "./osc-BFKKSqpg.js";
3
+ import { d as isTerminalQueryResponse, i as getCapabilities, t as colorState } from "./color-policy-BAC9-TZX.js";
4
+ import { n as cellsEqual, t as cellAttributes } from "./cell-_ZVhbfl0.js";
5
+ import { n as serializeScreen, t as serializeLine } from "./serialize-BTkAZgw1.js";
6
+ //#region src/cursor-position.ts
7
+ const showCursorEscape = ansiEscapes.cursorShow;
8
+ const hideCursorEscape = ansiEscapes.cursorHide;
9
+ /**
10
+ Compare two cursor positions. Returns true if they differ.
11
+ */
12
+ const cursorPositionChanged = (a, b) => a?.x !== b?.x || a?.y !== b?.y;
13
+ /**
14
+ Build escape sequence to move cursor from the bottom of the output to the target position and show it.
15
+
16
+ `bottomLine` is the row the renderer left the cursor on, counted from the top of the output.
17
+ That is always `lines.length - 1` for `lines = str.split('\n')`, whether or not the output ends
18
+ with a newline:
19
+
20
+ - With a trailing newline, `split` yields one extra empty element and the renderer stops just
21
+ past the last visible line — which is `lines.length - 1`.
22
+ - Without one, there is no extra element and the renderer deliberately stops on the last visible
23
+ line instead of moving past it — which is also `lines.length - 1`.
24
+
25
+ This is the same row basis `buildReturnToBottom` measures from, so the two stay in step.
26
+ */
27
+ const buildCursorSuffix = (bottomLine, cursorPosition) => {
28
+ if (!cursorPosition) return "";
29
+ const moveUp = bottomLine - cursorPosition.y;
30
+ return (moveUp > 0 ? ansiEscapes.cursorUp(moveUp) : "") + ansiEscapes.cursorTo(cursorPosition.x) + showCursorEscape;
31
+ };
32
+ /**
33
+ Build escape sequence to move cursor from previousCursorPosition back to the bottom of output.
34
+ This must be done before eraseLines or any operation that assumes cursor is at the bottom.
35
+ */
36
+ const buildReturnToBottom = (previousLineCount, previousCursorPosition) => {
37
+ if (!previousCursorPosition) return "";
38
+ const down = previousLineCount - 1 - previousCursorPosition.y;
39
+ return (down > 0 ? ansiEscapes.cursorDown(down) : "") + ansiEscapes.cursorTo(0);
40
+ };
41
+ /**
42
+ Build the escape sequence for cursor-only updates (output unchanged, cursor moved).
43
+ Hides cursor if it was previously shown, returns to bottom, then repositions.
44
+
45
+ `buildReturnToBottom` has just placed the cursor on row `previousLineCount - 1`, so the
46
+ suffix measures from there rather than recomputing the row from the output.
47
+ */
48
+ const buildCursorOnlySequence = (input) => {
49
+ const hidePrefix = input.cursorWasShown ? hideCursorEscape : "";
50
+ const returnToBottom = buildReturnToBottom(input.previousLineCount, input.previousCursorPosition);
51
+ const cursorSuffix = buildCursorSuffix(input.previousLineCount - 1, input.cursorPosition);
52
+ return hidePrefix + returnToBottom + cursorSuffix;
53
+ };
54
+ /**
55
+ Build the prefix that hides cursor and returns to bottom before erasing or rewriting.
56
+ Returns empty string if cursor was not shown.
57
+ */
58
+ const buildReturnToBottomPrefix = (cursorWasShown, previousLineCount, previousCursorPosition) => {
59
+ if (!cursorWasShown) return "";
60
+ return hideCursorEscape + buildReturnToBottom(previousLineCount, previousCursorPosition);
61
+ };
62
+ //#endregion
63
+ //#region src/input-parser.ts
64
+ const escape = "\x1B";
65
+ const isCsiParameterByte = (byte) => {
66
+ return byte >= 48 && byte <= 63;
67
+ };
68
+ const isCsiIntermediateByte = (byte) => {
69
+ return byte >= 32 && byte <= 47;
70
+ };
71
+ const isCsiFinalByte = (byte) => {
72
+ return byte >= 64 && byte <= 126;
73
+ };
74
+ const parseCsiSequence = (input, startIndex, prefixLength) => {
75
+ const csiPayloadStart = startIndex + prefixLength + 1;
76
+ let index = csiPayloadStart;
77
+ for (; index < input.length; index++) {
78
+ const byte = input.codePointAt(index);
79
+ if (byte === void 0) return "pending";
80
+ if (byte === 27 || byte === 3) return {
81
+ sequence: "",
82
+ nextIndex: index
83
+ };
84
+ if (isCsiParameterByte(byte) || isCsiIntermediateByte(byte)) continue;
85
+ if (byte === 91 && index === csiPayloadStart) continue;
86
+ if (isCsiFinalByte(byte)) return {
87
+ sequence: input.slice(startIndex, index + 1),
88
+ nextIndex: index + 1
89
+ };
90
+ return;
91
+ }
92
+ return "pending";
93
+ };
94
+ const parseSs3Sequence = (input, startIndex, prefixLength) => {
95
+ const nextIndex = startIndex + prefixLength + 2;
96
+ if (nextIndex > input.length) return "pending";
97
+ const finalByte = input.codePointAt(nextIndex - 1);
98
+ if (finalByte === void 0 || !isCsiFinalByte(finalByte)) return;
99
+ return {
100
+ sequence: input.slice(startIndex, nextIndex),
101
+ nextIndex
102
+ };
103
+ };
104
+ const parseControlSequence = (input, startIndex, prefixLength) => {
105
+ const sequenceType = input[startIndex + prefixLength];
106
+ if (sequenceType === void 0) return "pending";
107
+ if (sequenceType === "[") return parseCsiSequence(input, startIndex, prefixLength);
108
+ if (sequenceType === "]" || sequenceType === "P" || sequenceType === "_") {
109
+ for (let index = startIndex + prefixLength + 1; index < input.length; index++) {
110
+ if (input[index] === "" || input[index] === escape && index + 1 < input.length && input[index + 1] !== "\\") return {
111
+ sequence: "",
112
+ nextIndex: index
113
+ };
114
+ const bel = sequenceType === "]" && input[index] === "\x07";
115
+ const st = input[index] === escape && input[index + 1] === "\\";
116
+ if (bel || st) {
117
+ const nextIndex = index + (st ? 2 : 1);
118
+ return {
119
+ sequence: input.slice(startIndex, nextIndex),
120
+ nextIndex
121
+ };
122
+ }
123
+ }
124
+ return "pending";
125
+ }
126
+ if (sequenceType === "O") return parseSs3Sequence(input, startIndex, prefixLength);
127
+ };
128
+ const parseEscapedCodePoint = (input, escapeIndex) => {
129
+ const nextCodePoint = input.codePointAt(escapeIndex + 1);
130
+ const nextCodePointLength = nextCodePoint !== void 0 && nextCodePoint > 65535 ? 2 : 1;
131
+ const nextIndex = escapeIndex + 1 + nextCodePointLength;
132
+ return {
133
+ sequence: input.slice(escapeIndex, nextIndex),
134
+ nextIndex
135
+ };
136
+ };
137
+ const parseEscapeSequence = (input, escapeIndex) => {
138
+ if (escapeIndex === input.length - 1) return "pending";
139
+ if (input[escapeIndex + 1] === escape) {
140
+ if (escapeIndex + 2 >= input.length) return "pending";
141
+ const doubleEscapeSequence = parseControlSequence(input, escapeIndex, 2);
142
+ if (doubleEscapeSequence === "pending") return "pending";
143
+ if (doubleEscapeSequence) return doubleEscapeSequence;
144
+ return {
145
+ sequence: input.slice(escapeIndex, escapeIndex + 2),
146
+ nextIndex: escapeIndex + 2
147
+ };
148
+ }
149
+ const controlSequence = parseControlSequence(input, escapeIndex, 1);
150
+ if (controlSequence === "pending") return "pending";
151
+ if (controlSequence) return controlSequence;
152
+ return parseEscapedCodePoint(input, escapeIndex);
153
+ };
154
+ /**
155
+ 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.
156
+
157
+ Other control characters like `\r` and `\t` are NOT split because they can legitimately appear inside pasted text.
158
+ */
159
+ const splitBackspaceBytes = (text, events) => {
160
+ let textSegmentStart = 0;
161
+ for (let index = 0; index < text.length; index++) {
162
+ const character = text[index];
163
+ if (character === "" || character === "\b") {
164
+ if (index > textSegmentStart) events.push(text.slice(textSegmentStart, index));
165
+ events.push(character);
166
+ textSegmentStart = index + 1;
167
+ }
168
+ }
169
+ if (textSegmentStart < text.length) events.push(text.slice(textSegmentStart));
170
+ };
171
+ const parseKeypresses = (input) => {
172
+ const events = [];
173
+ let index = 0;
174
+ const pendingFrom = (pendingStartIndex) => ({
175
+ events,
176
+ pending: input.slice(pendingStartIndex)
177
+ });
178
+ while (index < input.length) {
179
+ const escapeIndex = input.indexOf(escape, index);
180
+ if (escapeIndex === -1) {
181
+ splitBackspaceBytes(input.slice(index), events);
182
+ return {
183
+ events,
184
+ pending: ""
185
+ };
186
+ }
187
+ if (escapeIndex > index) splitBackspaceBytes(input.slice(index, escapeIndex), events);
188
+ const parsedEscapeSequence = parseEscapeSequence(input, escapeIndex);
189
+ if (parsedEscapeSequence === "pending") return pendingFrom(escapeIndex);
190
+ if (parsedEscapeSequence.sequence === pasteStart) {
191
+ const afterStart = parsedEscapeSequence.nextIndex;
192
+ const endIndex = input.indexOf(pasteEnd, afterStart);
193
+ if (endIndex === -1) return pendingFrom(escapeIndex);
194
+ events.push({ paste: input.slice(afterStart, endIndex) });
195
+ index = endIndex + pasteEnd.length;
196
+ continue;
197
+ }
198
+ if (parsedEscapeSequence.sequence.length > 0) events.push(parsedEscapeSequence.sequence);
199
+ index = parsedEscapeSequence.nextIndex;
200
+ }
201
+ return {
202
+ events,
203
+ pending: ""
204
+ };
205
+ };
206
+ const createInputParser = () => {
207
+ let pending = "";
208
+ const hasPendingEscape = () => pending.startsWith(escape) && !/^\u001B{1,2}[[\]P_]/.test(pending);
209
+ return {
210
+ push(chunk) {
211
+ const parsedInput = parseKeypresses(pending + chunk);
212
+ pending = parsedInput.pending;
213
+ return parsedInput.events;
214
+ },
215
+ hasPendingEscape,
216
+ flushPendingEscape() {
217
+ if (!hasPendingEscape()) return;
218
+ const pendingEscape = pending;
219
+ pending = "";
220
+ return pendingEscape;
221
+ },
222
+ reset() {
223
+ pending = "";
224
+ }
225
+ };
226
+ };
227
+ //#endregion
228
+ //#region src/terminal/input.ts
229
+ const sgrMouse = /^\u001B\[<(\d+);(\d+);(\d+)([Mm])$/;
230
+ /** Stateful stdin decoder that separates terminal reports from application input. */
231
+ var TerminalInput = class {
232
+ #parser = createInputParser();
233
+ #capabilities;
234
+ #mouseListeners = /* @__PURE__ */ new Set();
235
+ constructor(capabilities) {
236
+ this.#capabilities = capabilities;
237
+ }
238
+ push(chunk) {
239
+ return this.#parser.push(chunk).flatMap((event) => {
240
+ if (typeof event !== "string") return [event];
241
+ if (event.startsWith("\x1B\x1B") && isTerminalQueryResponse(event.slice(1))) return ["\x1B"];
242
+ if (this.#capabilities.ingest(event)) return [];
243
+ if (isTerminalQueryResponse(event)) return [];
244
+ const mouse = parseMouseEvent(event);
245
+ if (!mouse) return [event];
246
+ for (const listener of this.#mouseListeners) listener(mouse);
247
+ return [];
248
+ });
249
+ }
250
+ subscribeMouse(listener) {
251
+ this.#mouseListeners.add(listener);
252
+ return () => this.#mouseListeners.delete(listener);
253
+ }
254
+ hasPendingEscape() {
255
+ return this.#parser.hasPendingEscape();
256
+ }
257
+ flushPendingEscape() {
258
+ return this.#parser.flushPendingEscape();
259
+ }
260
+ reset() {
261
+ this.#parser.reset();
262
+ }
263
+ };
264
+ function parseMouseEvent(sequence) {
265
+ const match = sequence.match(sgrMouse);
266
+ if (!match) return;
267
+ const code = Number(match[1]);
268
+ const column = Number(match[2]);
269
+ const row = Number(match[3]);
270
+ if (column < 1 || row < 1) return;
271
+ const wheel = (code & 64) !== 0;
272
+ const move = (code & 32) !== 0;
273
+ const buttonCode = code & 3;
274
+ const button = wheel ? buttonCode === 0 ? "wheel-up" : "wheel-down" : [
275
+ "left",
276
+ "middle",
277
+ "right",
278
+ "none"
279
+ ][buttonCode];
280
+ return {
281
+ type: wheel ? "wheel" : move ? "move" : match[4] === "m" || buttonCode === 3 ? "release" : "press",
282
+ x: column - 1,
283
+ y: row - 1,
284
+ button,
285
+ shift: (code & 4) !== 0,
286
+ alt: (code & 8) !== 0,
287
+ ctrl: (code & 16) !== 0
288
+ };
289
+ }
290
+ //#endregion
291
+ //#region src/terminal/screen-presenter.ts
292
+ /** Presents structured frames in an inline terminal region. */
293
+ var ScreenPresenter = class {
294
+ #screen;
295
+ #lineCount = 0;
296
+ #cursor;
297
+ #cursorWasShown = false;
298
+ #fullscreen = false;
299
+ #hidden = false;
300
+ #write;
301
+ #stream;
302
+ constructor(write, stream) {
303
+ this.#write = write;
304
+ this.#stream = stream;
305
+ }
306
+ present(screen, options) {
307
+ if (!this.#hidden) {
308
+ cliCursor.hide(this.#stream);
309
+ this.#hidden = true;
310
+ }
311
+ const previousScreen = this.#screen;
312
+ const sameScreen = previousScreen !== void 0 && screensEqual(previousScreen, screen);
313
+ const cursorChanged = cursorPositionChanged(options.cursor, this.#cursor);
314
+ const fullscreen = options.fullscreen ?? false;
315
+ const presentationModeChanged = this.#fullscreen !== fullscreen;
316
+ if (!options.forceRewrite && sameScreen && !cursorChanged && !presentationModeChanged) return false;
317
+ if (!options.forceRewrite && sameScreen && !presentationModeChanged) {
318
+ this.#write(buildCursorOnlySequence({
319
+ cursorWasShown: this.#cursorWasShown,
320
+ previousLineCount: this.#lineCount,
321
+ previousCursorPosition: this.#cursor,
322
+ cursorPosition: options.cursor
323
+ }));
324
+ this.#remember(screen, options.cursor, options.fullscreen ?? false);
325
+ return true;
326
+ }
327
+ const firstChangedRow = !options.forceRewrite && previousScreen !== void 0 && previousScreen.width === screen.width && this.#fullscreen === fullscreen ? findFirstChangedRow(previousScreen, screen) : void 0;
328
+ const serializeOptions = {
329
+ colorProfile: options.colorProfile,
330
+ styles: options.colorProfile !== "none"
331
+ };
332
+ if (firstChangedRow !== void 0) {
333
+ const eraseCount = this.#lineCount - firstChangedRow;
334
+ const body = screen.toRows().slice(firstChangedRow).map((line) => serializeLine(line, serializeOptions)).join("\n");
335
+ const output = fullscreen || body === "" ? body : `${body}\n`;
336
+ this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(eraseCount) + output + buildCursorSuffix(screen.height - (fullscreen ? 1 : 0), options.cursor));
337
+ } else {
338
+ const body = serializeScreen(screen, serializeOptions);
339
+ const output = fullscreen ? body : `${body}\n`;
340
+ this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(this.#lineCount) + output + buildCursorSuffix(screen.height - (fullscreen ? 1 : 0), options.cursor));
341
+ }
342
+ this.#remember(screen, options.cursor, fullscreen);
343
+ return true;
344
+ }
345
+ willPresent(screen, cursor, fullscreen = false, forceRewrite = false) {
346
+ return forceRewrite || this.#screen === void 0 || !screensEqual(this.#screen, screen) || cursorPositionChanged(cursor, this.#cursor) || fullscreen !== this.#fullscreen;
347
+ }
348
+ /**
349
+ Erases the presented frame from the cursor upward.
350
+
351
+ `columns` is the terminal's current width. When it is narrower than the width
352
+ the frame was painted at, a reflowing emulator (xterm.js, Ghostty, kitty,
353
+ iTerm2, WezTerm, tmux, …) has already rewrapped every wider row onto several
354
+ physical rows before the resize event reaches us, so the erase has to cover
355
+ that physical footprint. Erasing only the logical row count leaves the frame's
356
+ top rows behind as ghosts, one more batch per resize event.
357
+ */
358
+ clear(options = {}) {
359
+ this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(this.#physicalLineCount(options.columns)));
360
+ this.#lineCount = 0;
361
+ this.#cursor = void 0;
362
+ this.#cursorWasShown = false;
363
+ this.#fullscreen = false;
364
+ }
365
+ #physicalLineCount(columns) {
366
+ if (this.#screen === void 0 || this.#lineCount === 0 || columns === void 0 || columns < 1) return this.#lineCount;
367
+ let rows = 0;
368
+ for (const line of this.#screen.toRows()) rows += Math.max(1, Math.ceil(contentWidth(line) / columns));
369
+ return rows + (this.#fullscreen ? 0 : 1);
370
+ }
371
+ reset() {
372
+ this.#screen = void 0;
373
+ this.#lineCount = 0;
374
+ this.#cursor = void 0;
375
+ this.#cursorWasShown = false;
376
+ }
377
+ done() {
378
+ this.reset();
379
+ if (this.#hidden) {
380
+ cliCursor.show(this.#stream);
381
+ this.#hidden = false;
382
+ }
383
+ }
384
+ get current() {
385
+ return this.#screen;
386
+ }
387
+ #remember(screen, cursor, fullscreen) {
388
+ this.#screen = screen;
389
+ this.#lineCount = screen.height + (fullscreen ? 0 : 1);
390
+ this.#cursor = cursor ? { ...cursor } : void 0;
391
+ this.#cursorWasShown = cursor !== void 0;
392
+ this.#fullscreen = fullscreen;
393
+ }
394
+ };
395
+ /**
396
+ The columns a row occupies once written: everything up to its last visible
397
+ cell. Trailing unstyled blanks are trimmed by the serializer and never reach
398
+ the terminal, while styled blanks (a highlighted tab's padding) do.
399
+ */
400
+ function contentWidth(line) {
401
+ let width = 0;
402
+ let end = 0;
403
+ for (const cell of line) {
404
+ width += cell.width;
405
+ if (!isBlank(cell)) end = width;
406
+ }
407
+ return end;
408
+ }
409
+ function isBlank(cell) {
410
+ const { style } = cell;
411
+ return (cell.grapheme === " " || cell.width === 0) && style.foreground === void 0 && style.background === void 0 && style.underlineColor === void 0 && style.underline === "none" && style.attributes === cellAttributes.none && cell.hyperlink === void 0;
412
+ }
413
+ function findFirstChangedRow(previous, next) {
414
+ const height = Math.max(previous.height, next.height);
415
+ for (let y = 0; y < height; y++) if (!rowsEqual(previous, next, y)) return y;
416
+ }
417
+ function rowsEqual(left, right, y) {
418
+ const length = Math.max(left.rowLength(y), right.rowLength(y));
419
+ for (let x = 0; x < length; x++) if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
420
+ return true;
421
+ }
422
+ function screensEqual(left, right) {
423
+ if (left.width !== right.width || left.height !== right.height) return false;
424
+ for (let y = 0; y < left.height; y++) if (!rowsEqual(left, right, y)) return false;
425
+ return true;
426
+ }
427
+ //#endregion
428
+ //#region src/terminal/session.ts
429
+ const imperativeProgressOwner = Symbol("imperative-progress");
430
+ const imperativeTitleOwner = Symbol("imperative-title");
431
+ /** Instance-scoped terminal state and lifecycle ownership. */
432
+ var TerminalSession = class {
433
+ stdin;
434
+ stdout;
435
+ stderr;
436
+ capabilities;
437
+ input;
438
+ #presenter;
439
+ cursor = {
440
+ visible: true,
441
+ shape: "block",
442
+ blinking: true
443
+ };
444
+ suspended = false;
445
+ alternateScreen = false;
446
+ inlineScreen = true;
447
+ #policy;
448
+ #profile;
449
+ #modes = /* @__PURE__ */ new Map();
450
+ #pendingWrites = /* @__PURE__ */ new Set();
451
+ #unsubscribe;
452
+ #cleaned = false;
453
+ #progressActive = false;
454
+ #progressSequence;
455
+ #progressHeartbeat;
456
+ #progressPublishers = /* @__PURE__ */ new Map();
457
+ #titlePublishers = /* @__PURE__ */ new Map();
458
+ #titleActive = false;
459
+ constructor(options) {
460
+ this.stdin = options.stdin;
461
+ this.stdout = options.stdout;
462
+ this.stderr = options.stderr;
463
+ this.capabilities = getCapabilities(this.stdin, this.stdout);
464
+ this.input = new TerminalInput(this.capabilities);
465
+ this.#presenter = new ScreenPresenter((data) => this.write(data), this.stdout);
466
+ this.#policy = options.colorPolicy ?? "auto";
467
+ this.#profile = colorState(this.capabilities.current, this.#policy).effective;
468
+ this.#unsubscribe = this.capabilities.subscribe((capabilities) => {
469
+ const profile = colorState(capabilities, this.#policy).effective;
470
+ if (profile !== this.#profile) {
471
+ this.#profile = profile;
472
+ this.#presenter.reset();
473
+ }
474
+ options.onCapabilitiesChange?.();
475
+ }, { resizes: false });
476
+ }
477
+ get colorProfile() {
478
+ return this.#profile;
479
+ }
480
+ /** Encodes a structured frame at the terminal boundary. */
481
+ encode(screen) {
482
+ return serializeScreen(screen, {
483
+ colorProfile: this.colorProfile,
484
+ styles: this.colorProfile !== "none"
485
+ });
486
+ }
487
+ present(screen, options = {}) {
488
+ return this.#presenter.present(screen, {
489
+ colorProfile: this.colorProfile,
490
+ cursor: this.cursor.position,
491
+ ...options
492
+ });
493
+ }
494
+ willPresent(screen, options = {}) {
495
+ return this.#presenter.willPresent(screen, this.cursor.position, options.fullscreen, options.forceRewrite);
496
+ }
497
+ /** Erases the presented frame; pass the current `columns` after a resize so the erase covers rows the emulator rewrapped. */
498
+ clearFrame(options = {}) {
499
+ this.#presenter.clear(options);
500
+ }
501
+ resetFrame() {
502
+ this.#presenter.reset();
503
+ }
504
+ finishFrame() {
505
+ this.#presenter.done();
506
+ }
507
+ setColorPolicy(policy) {
508
+ this.#policy = policy;
509
+ const profile = colorState(this.capabilities.current, policy).effective;
510
+ if (profile !== this.#profile) {
511
+ this.#profile = profile;
512
+ this.#presenter.reset();
513
+ }
514
+ }
515
+ /** Writes through the session and tracks callback-based backpressure completion. */
516
+ write(data) {
517
+ let settle;
518
+ const completion = new Promise((resolve) => {
519
+ settle = resolve;
520
+ });
521
+ this.#pendingWrites.add(completion);
522
+ try {
523
+ return this.stdout.write(data, () => {
524
+ this.#pendingWrites.delete(completion);
525
+ settle();
526
+ });
527
+ } catch (error) {
528
+ this.#pendingWrites.delete(completion);
529
+ settle();
530
+ throw error;
531
+ }
532
+ }
533
+ async flush() {
534
+ await Promise.all(this.#pendingWrites);
535
+ }
536
+ /** Copy text through OSC 52, including tmux passthrough when required. */
537
+ copyToClipboard(text, selection = "clipboard") {
538
+ return this.write(this.#wrapOsc(setClipboard(text, selection)));
539
+ }
540
+ setTitle(title) {
541
+ return this.publishTitle(imperativeTitleOwner, title);
542
+ }
543
+ publishTitle(owner, title) {
544
+ this.#titlePublishers.delete(owner);
545
+ if (title !== void 0) this.#titlePublishers.set(owner, title);
546
+ const current = [...this.#titlePublishers.values()].at(-1);
547
+ if (current !== void 0) {
548
+ this.#titleActive = true;
549
+ return this.write(this.#wrapOsc(setWindowTitle(current)));
550
+ }
551
+ if (!this.#titleActive) return true;
552
+ this.#titleActive = false;
553
+ return this.write(this.#wrapOsc(setWindowTitle("")));
554
+ }
555
+ setWorkingDirectory(directory) {
556
+ return this.write(this.#wrapOsc(setWorkingDirectory(directory)));
557
+ }
558
+ notify(title) {
559
+ return this.write(this.#wrapOsc(notify(title)));
560
+ }
561
+ setPointerShape(shape) {
562
+ return this.write(this.#wrapOsc(setPointerShape(shape)));
563
+ }
564
+ publishProgress(owner, state, value) {
565
+ this.#progressPublishers.delete(owner);
566
+ if (state !== "inactive") this.#progressPublishers.set(owner, {
567
+ state,
568
+ value
569
+ });
570
+ return this.#applyPublishedProgress();
571
+ }
572
+ #applyPublishedProgress() {
573
+ const current = [...this.#progressPublishers.values()].at(-1);
574
+ return current === void 0 ? this.#writeProgress("inactive") : this.#writeProgress(current.state, current.value);
575
+ }
576
+ /**
577
+ * Update terminal-native progress. Active progress is reset during cleanup
578
+ * so a completed or failed process cannot leave a stale terminal indicator.
579
+ */
580
+ setProgress(state, value) {
581
+ this.#progressPublishers.delete(imperativeProgressOwner);
582
+ if (state !== "inactive") this.#progressPublishers.set(imperativeProgressOwner, {
583
+ state,
584
+ value
585
+ });
586
+ return this.#applyPublishedProgress();
587
+ }
588
+ #writeProgress(state, value) {
589
+ this.#progressActive = state !== "inactive";
590
+ const sequence = this.#wrapOsc(setTerminalProgress(state, value));
591
+ if (!this.#progressActive) this.#stopProgressHeartbeat();
592
+ else {
593
+ this.#progressSequence = sequence;
594
+ if (this.#progressHeartbeat === void 0) {
595
+ this.#progressHeartbeat = setInterval(() => {
596
+ if (this.#progressSequence !== void 0) this.write(this.#progressSequence);
597
+ }, 1e3);
598
+ this.#progressHeartbeat.unref?.();
599
+ }
600
+ }
601
+ return this.write(sequence);
602
+ }
603
+ #stopProgressHeartbeat() {
604
+ if (this.#progressHeartbeat !== void 0) {
605
+ clearInterval(this.#progressHeartbeat);
606
+ this.#progressHeartbeat = void 0;
607
+ }
608
+ this.#progressSequence = void 0;
609
+ }
610
+ enableMode(modeOrEnable, disable) {
611
+ const mode = typeof modeOrEnable === "string" ? {
612
+ id: disable ?? modeOrEnable,
613
+ enable: modeOrEnable,
614
+ disable: disable ?? ""
615
+ } : modeOrEnable;
616
+ const active = this.#modes.get(mode.id);
617
+ if (active) {
618
+ active.count++;
619
+ return;
620
+ }
621
+ this.write(mode.enable);
622
+ this.#modes.set(mode.id, {
623
+ ...mode,
624
+ count: 1
625
+ });
626
+ }
627
+ disableMode(idOrDisable) {
628
+ const entry = this.#modes.get(idOrDisable) ?? [...this.#modes.values()].find((mode) => mode.disable === idOrDisable);
629
+ if (!entry) return;
630
+ if (--entry.count > 0) return;
631
+ this.#modes.delete(entry.id);
632
+ this.write(entry.disable);
633
+ }
634
+ setCursor(position) {
635
+ this.cursor = {
636
+ ...this.cursor,
637
+ position,
638
+ visible: position !== void 0
639
+ };
640
+ }
641
+ setCursorAppearance(options) {
642
+ const next = {
643
+ ...this.cursor,
644
+ ...options.visible === void 0 ? {} : { visible: options.visible },
645
+ ...options.shape === void 0 ? {} : { shape: options.shape },
646
+ ...options.blinking === void 0 ? {} : { blinking: options.blinking },
647
+ ...options.color === void 0 ? {} : { color: options.color ?? void 0 }
648
+ };
649
+ let output = "";
650
+ if (next.shape !== this.cursor.shape || next.blinking !== this.cursor.blinking) output += ansiEscapes.cursorShape(next.shape, next.blinking);
651
+ if (options.color !== void 0) output += options.color === null ? ansiEscapes.resetCursorColor : ansiEscapes.cursorColor(options.color);
652
+ if (next.visible !== this.cursor.visible) output += next.visible ? ansiEscapes.cursorShow : ansiEscapes.cursorHide;
653
+ this.cursor = next;
654
+ if (output) this.write(output);
655
+ }
656
+ setAlternateScreen(enabled, options = {}) {
657
+ if (enabled === this.alternateScreen) return;
658
+ this.alternateScreen = enabled;
659
+ this.inlineScreen = !enabled;
660
+ if (enabled) {
661
+ this.enableMode({
662
+ id: "alternate-screen",
663
+ enable: ansiEscapes.enterAlternativeScreen,
664
+ disable: ansiEscapes.exitAlternativeScreen
665
+ });
666
+ if (options.hideCursor) this.setCursorAppearance({ visible: false });
667
+ } else this.disableMode("alternate-screen");
668
+ this.#presenter.reset();
669
+ }
670
+ beginSuspension() {
671
+ if (this.suspended) throw new Error("The terminal session is already suspended");
672
+ this.suspended = true;
673
+ }
674
+ resume() {
675
+ if (!this.suspended) return;
676
+ this.suspended = false;
677
+ this.#presenter.reset();
678
+ }
679
+ cleanup() {
680
+ if (this.#cleaned) return;
681
+ this.#cleaned = true;
682
+ this.#unsubscribe();
683
+ this.#stopProgressHeartbeat();
684
+ this.#progressPublishers.clear();
685
+ if (this.#progressActive) {
686
+ try {
687
+ this.stdout.write(this.#wrapOsc(setTerminalProgress("inactive")));
688
+ } catch {}
689
+ this.#progressActive = false;
690
+ }
691
+ this.#titlePublishers.clear();
692
+ if (this.#titleActive) {
693
+ try {
694
+ this.stdout.write(this.#wrapOsc(setWindowTitle("")));
695
+ } catch {}
696
+ this.#titleActive = false;
697
+ }
698
+ for (const mode of [...this.#modes.values()].reverse()) try {
699
+ this.stdout.write(mode.disable);
700
+ } catch {}
701
+ if (this.cursor.color !== void 0) try {
702
+ this.stdout.write(ansiEscapes.resetCursorColor);
703
+ } catch {}
704
+ if (!this.cursor.visible) try {
705
+ this.stdout.write(ansiEscapes.cursorShow);
706
+ } catch {}
707
+ this.#modes.clear();
708
+ this.alternateScreen = false;
709
+ this.inlineScreen = true;
710
+ this.suspended = false;
711
+ this.cursor = {
712
+ visible: true,
713
+ shape: "block",
714
+ blinking: true
715
+ };
716
+ this.#presenter.done();
717
+ }
718
+ #wrapOsc(sequence) {
719
+ return this.capabilities.current.terminal.multiplexer === "tmux" ? tmuxPassthrough(sequence) : sequence;
720
+ }
721
+ };
722
+ //#endregion
723
+ export { buildCursorSuffix as a, hideCursorEscape as c, buildCursorOnlySequence as i, TerminalInput as n, buildReturnToBottomPrefix as o, parseMouseEvent as r, cursorPositionChanged as s, TerminalSession as t };