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

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 (207) hide show
  1. package/README.md +23 -13
  2. package/THIRD_PARTY_NOTICES.md +70 -23
  3. package/dist/Text-D5HUf3Fj.d.ts +452 -0
  4. package/dist/ansi.d.ts +137 -143
  5. package/dist/ansi.js +87 -2
  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/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
  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-Bmc2tRPk.d.ts +21 -0
  24. package/dist/index.d.ts +235 -754
  25. package/dist/index.js +1362 -3001
  26. package/dist/osc-CCH7xDoS.js +71 -0
  27. package/dist/osc-Cn0fw77g.d.ts +23 -0
  28. package/dist/paint-C19minOS.d.ts +81 -0
  29. package/dist/query-vaIeGOkH.d.ts +152 -0
  30. package/dist/router.d.ts +392 -0
  31. package/dist/router.js +709 -0
  32. package/dist/sample-Cqw1bjUL.js +445 -0
  33. package/dist/screen-BOh__-65.js +324 -0
  34. package/dist/screen-BReKIheE.d.ts +40 -0
  35. package/dist/screen.d.ts +5 -0
  36. package/dist/screen.js +5 -0
  37. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  38. package/dist/serialize-BTkAZgw1.js +79 -0
  39. package/dist/session-BJmzX2NJ.js +664 -0
  40. package/dist/sgr-BhwaWAJB.js +246 -0
  41. package/dist/store-CgrG9K4y.d.ts +72 -0
  42. package/dist/string-width-CijQwpIk.js +69 -0
  43. package/dist/strip-BvU4toXG.js +6 -0
  44. package/dist/terminal.d.ts +118 -0
  45. package/dist/terminal.js +2 -0
  46. package/dist/tokenize-AjqbvtiT.js +1242 -0
  47. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  48. package/dist/truncate-D31fhU6i.js +562 -0
  49. package/dist/use-focus-C2sciZOo.js +1335 -0
  50. package/package.json +43 -17
  51. package/src/ansi/chalk.ts +12 -53
  52. package/src/ansi/cursor.ts +2 -4
  53. package/src/ansi/east-asian-width.ts +44 -0
  54. package/src/ansi/escapes.ts +18 -26
  55. package/src/ansi/graphemes.ts +8 -0
  56. package/src/ansi/hyperlink.ts +44 -0
  57. package/src/ansi/index.ts +14 -14
  58. package/src/ansi/osc.ts +77 -0
  59. package/src/ansi/sgr.ts +2 -5
  60. package/src/ansi/slice.ts +1 -1
  61. package/src/ansi/string-width.ts +91 -206
  62. package/src/ansi/strip.ts +13 -33
  63. package/src/ansi/tokenize.ts +96 -169
  64. package/src/ansi/truncate.ts +5 -3
  65. package/src/ansi/wrap.ts +13 -14
  66. package/src/ansi-tokenizer.ts +1 -1
  67. package/src/capabilities/color-policy.ts +34 -0
  68. package/src/capabilities/detect.ts +594 -0
  69. package/src/capabilities/index.ts +37 -0
  70. package/src/capabilities/query.ts +657 -0
  71. package/src/capabilities/store.ts +379 -0
  72. package/src/color/index.ts +3 -0
  73. package/src/color/paint.ts +169 -0
  74. package/src/color/palette.ts +48 -0
  75. package/src/color/sample.ts +323 -0
  76. package/src/color.ts +1 -0
  77. package/src/components/AnsiText.tsx +42 -0
  78. package/src/components/App.tsx +109 -22
  79. package/src/components/BackgroundContext.ts +2 -3
  80. package/src/components/Box.tsx +43 -60
  81. package/src/components/CursorContext.ts +1 -1
  82. package/src/components/ErrorBoundary.tsx +1 -1
  83. package/src/components/ErrorOverview.tsx +8 -7
  84. package/src/components/Hyperlink.tsx +56 -0
  85. package/src/components/Spacer.tsx +1 -1
  86. package/src/components/Static.tsx +1 -1
  87. package/src/components/StderrContext.ts +0 -2
  88. package/src/components/StdinContext.ts +0 -1
  89. package/src/components/StdoutContext.ts +1 -3
  90. package/src/components/TerminalOscContext.ts +25 -0
  91. package/src/components/Text.tsx +23 -46
  92. package/src/components/Transform.tsx +2 -2
  93. package/src/cursor-position.ts +1 -1
  94. package/src/devtools.ts +84 -24
  95. package/src/dom.ts +11 -8
  96. package/src/env.ts +12 -0
  97. package/src/get-max-width.ts +1 -1
  98. package/src/global.d.ts +6 -4
  99. package/src/{boxes.ts → glyphs.ts} +17 -18
  100. package/src/hooks/use-animation.ts +1 -1
  101. package/src/hooks/use-app.ts +1 -1
  102. package/src/hooks/use-box-metrics.ts +1 -1
  103. package/src/hooks/use-capabilities.ts +73 -0
  104. package/src/hooks/use-cursor.ts +3 -3
  105. package/src/hooks/use-focus-manager.ts +1 -1
  106. package/src/hooks/use-focus.ts +5 -6
  107. package/src/hooks/use-input.ts +4 -4
  108. package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
  109. package/src/hooks/use-paste.ts +2 -2
  110. package/src/hooks/use-stderr.ts +1 -1
  111. package/src/hooks/use-stdin.ts +1 -1
  112. package/src/hooks/use-stdout.ts +1 -1
  113. package/src/hooks/use-terminal-osc.ts +59 -0
  114. package/src/hooks/use-window-size.ts +2 -2
  115. package/src/index.ts +88 -44
  116. package/src/ink.tsx +620 -745
  117. package/src/input-parser.ts +1 -1
  118. package/src/instances.ts +1 -1
  119. package/src/kitty-keyboard.ts +128 -0
  120. package/src/measure-element.ts +1 -1
  121. package/src/measure-text.ts +2 -2
  122. package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
  123. package/src/parse-keypress.ts +4 -5
  124. package/src/parse-stack-line.ts +0 -1
  125. package/src/patch-console.ts +44 -0
  126. package/src/reconciler.ts +36 -62
  127. package/src/render-background.ts +37 -16
  128. package/src/render-border.ts +97 -64
  129. package/src/render-frame.ts +83 -0
  130. package/src/render-to-string.ts +24 -9
  131. package/src/render.ts +24 -16
  132. package/src/router/components.tsx +343 -0
  133. package/src/router/context.ts +41 -0
  134. package/src/router/history.ts +194 -0
  135. package/src/router/hooks.tsx +391 -0
  136. package/src/router/index.ts +34 -0
  137. package/src/router/matcher.ts +571 -0
  138. package/src/sanitize-ansi.ts +1 -1
  139. package/src/screen/ansi.ts +184 -0
  140. package/src/screen/canvas.ts +137 -0
  141. package/src/screen/cell.ts +138 -0
  142. package/src/screen/color-profile.ts +47 -0
  143. package/src/screen/geometry.ts +9 -0
  144. package/src/screen/index.ts +6 -0
  145. package/src/screen/screen.ts +272 -0
  146. package/src/screen/serialize.ts +129 -0
  147. package/src/screen.ts +1 -0
  148. package/src/semantic-text-style.ts +118 -0
  149. package/src/signal-exit.ts +4 -5
  150. package/src/squash-text-nodes.ts +4 -7
  151. package/src/stream.ts +2 -4
  152. package/src/structured-text.ts +321 -0
  153. package/src/styles.ts +16 -17
  154. package/src/terminal/index.ts +2 -0
  155. package/src/terminal/inline-presenter.ts +120 -0
  156. package/src/terminal/input.ts +86 -0
  157. package/src/terminal/render-scheduler.ts +37 -0
  158. package/src/terminal/screen-presenter.ts +188 -0
  159. package/src/terminal/session.ts +407 -0
  160. package/src/terminal-size.ts +9 -8
  161. package/src/terminal.ts +1 -0
  162. package/src/testing/browser.ts +588 -0
  163. package/src/testing/emulators.ts +205 -0
  164. package/src/testing/explorer-app/index.html +12 -0
  165. package/src/testing/explorer-app/main.ts +381 -0
  166. package/src/testing/explorer-app/style.css +194 -0
  167. package/src/testing/explorer-app/tsconfig.json +15 -0
  168. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  169. package/src/testing/index.ts +26 -0
  170. package/src/testing/keys.ts +56 -0
  171. package/src/testing/live.ts +85 -0
  172. package/src/testing/matchers.ts +70 -0
  173. package/src/testing/public.ts +94 -0
  174. package/src/testing/terminal.ts +349 -0
  175. package/src/testing/vitest.ts +157 -0
  176. package/src/transform-adapter.ts +14 -0
  177. package/src/types.ts +1 -6
  178. package/src/utils.ts +2 -2
  179. package/src/wrap-text.ts +4 -4
  180. package/src/yoga/config.ts +2 -2
  181. package/src/yoga/core/absoluteLayout.ts +15 -15
  182. package/src/yoga/core/baseline.ts +3 -3
  183. package/src/yoga/core/cache.ts +4 -4
  184. package/src/yoga/core/calculateLayout.ts +33 -27
  185. package/src/yoga/core/config.ts +1 -1
  186. package/src/yoga/core/flexLine.ts +3 -3
  187. package/src/yoga/core/helpers.ts +3 -3
  188. package/src/yoga/core/layoutResults.ts +4 -4
  189. package/src/yoga/core/node.ts +16 -16
  190. package/src/yoga/core/pixelGrid.ts +4 -4
  191. package/src/yoga/core/style.ts +11 -11
  192. package/src/yoga/core/types.ts +2 -2
  193. package/src/yoga/index.ts +6 -6
  194. package/src/yoga/node.ts +8 -8
  195. package/dist/sgr-CMfEpjSk.d.ts +0 -91
  196. package/dist/truncate-CBiyyZzw.js +0 -2156
  197. package/src/ansi/supports-color.ts +0 -213
  198. package/src/ansi/widest-line.ts +0 -12
  199. package/src/auto-bind.ts +0 -41
  200. package/src/colorize.ts +0 -60
  201. package/src/devtools-window-polyfill.ts +0 -73
  202. package/src/indent-string.ts +0 -16
  203. package/src/is-in-ci.ts +0 -7
  204. package/src/log-update.ts +0 -370
  205. package/src/output.ts +0 -308
  206. package/src/renderer.ts +0 -73
  207. package/src/write-synchronized.ts +0 -9
package/src/log-update.ts DELETED
@@ -1,370 +0,0 @@
1
- import { cliCursor } from "./ansi/cursor.ts";
2
- import { ansiEscapes } from "./ansi/escapes.ts";
3
- import {
4
- type CursorPosition,
5
- cursorPositionChanged,
6
- buildCursorSuffix,
7
- buildCursorOnlySequence,
8
- buildReturnToBottomPrefix,
9
- hideCursorEscape,
10
- } from "./cursor-position.ts";
11
-
12
- export type { CursorPosition } from "./cursor-position.ts";
13
-
14
- export type LogUpdate = {
15
- clear: () => void;
16
- done: () => void;
17
- reset: () => void;
18
- sync: (str: string) => void;
19
- setCursorPosition: (position: CursorPosition | undefined) => void;
20
- isCursorDirty: () => boolean;
21
- willRender: (str: string) => boolean;
22
- (str: string): boolean;
23
- };
24
-
25
- // Count visible lines in a string, ignoring the trailing empty element
26
- // that `split('\n')` produces when the string ends with '\n'.
27
- const visibleLineCount = (lines: string[], str: string): number =>
28
- str.endsWith("\n") ? lines.length - 1 : lines.length;
29
-
30
- const createStandard = (stream: NodeJS.WritableStream, { showCursor = false } = {}): LogUpdate => {
31
- let previousLineCount = 0;
32
- let previousOutput = "";
33
- let hasHiddenCursor = false;
34
- let cursorPosition: CursorPosition | undefined;
35
- let cursorDirty = false;
36
- let previousCursorPosition: CursorPosition | undefined;
37
- let cursorWasShown = false;
38
-
39
- const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
40
- const hasChanges = (str: string, activeCursor: CursorPosition | undefined): boolean => {
41
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
42
- return str !== previousOutput || cursorChanged;
43
- };
44
-
45
- const render = (str: string) => {
46
- if (!showCursor && !hasHiddenCursor) {
47
- cliCursor.hide(stream);
48
- hasHiddenCursor = true;
49
- }
50
-
51
- // Only use cursor if setCursorPosition was called since last render.
52
- // This ensures stale positions don't persist after component unmount.
53
- const activeCursor = getActiveCursor();
54
- cursorDirty = false;
55
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
56
-
57
- if (!hasChanges(str, activeCursor)) {
58
- return false;
59
- }
60
-
61
- const lines = str.split("\n");
62
- const cursorSuffix = buildCursorSuffix(lines.length - 1, activeCursor);
63
-
64
- if (str === previousOutput && cursorChanged) {
65
- stream.write(
66
- buildCursorOnlySequence({
67
- cursorWasShown,
68
- previousLineCount,
69
- previousCursorPosition,
70
- cursorPosition: activeCursor,
71
- }),
72
- );
73
- } else {
74
- previousOutput = str;
75
- const returnPrefix = buildReturnToBottomPrefix(
76
- cursorWasShown,
77
- previousLineCount,
78
- previousCursorPosition,
79
- );
80
- stream.write(returnPrefix + ansiEscapes.eraseLines(previousLineCount) + str + cursorSuffix);
81
- previousLineCount = lines.length;
82
- }
83
-
84
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
85
- cursorWasShown = activeCursor !== undefined;
86
- return true;
87
- };
88
-
89
- render.clear = () => {
90
- const prefix = buildReturnToBottomPrefix(
91
- cursorWasShown,
92
- previousLineCount,
93
- previousCursorPosition,
94
- );
95
- stream.write(prefix + ansiEscapes.eraseLines(previousLineCount));
96
- previousOutput = "";
97
- previousLineCount = 0;
98
- previousCursorPosition = undefined;
99
- cursorWasShown = false;
100
- };
101
-
102
- render.done = () => {
103
- previousOutput = "";
104
- previousLineCount = 0;
105
- previousCursorPosition = undefined;
106
- cursorWasShown = false;
107
-
108
- if (!showCursor) {
109
- cliCursor.show(stream);
110
- hasHiddenCursor = false;
111
- }
112
- };
113
-
114
- render.reset = () => {
115
- previousOutput = "";
116
- previousLineCount = 0;
117
- previousCursorPosition = undefined;
118
- cursorWasShown = false;
119
- };
120
-
121
- render.sync = (str: string) => {
122
- const activeCursor = cursorDirty ? cursorPosition : undefined;
123
- cursorDirty = false;
124
-
125
- const lines = str.split("\n");
126
- previousOutput = str;
127
- previousLineCount = lines.length;
128
-
129
- if (!activeCursor && cursorWasShown) {
130
- stream.write(hideCursorEscape);
131
- }
132
-
133
- if (activeCursor) {
134
- stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
135
- }
136
-
137
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
138
- cursorWasShown = activeCursor !== undefined;
139
- };
140
-
141
- render.setCursorPosition = (position: CursorPosition | undefined) => {
142
- cursorPosition = position;
143
- cursorDirty = true;
144
- };
145
-
146
- render.isCursorDirty = () => cursorDirty;
147
- render.willRender = (str: string) => hasChanges(str, getActiveCursor());
148
-
149
- return render;
150
- };
151
-
152
- const createIncremental = (
153
- stream: NodeJS.WritableStream,
154
- { showCursor = false } = {},
155
- ): LogUpdate => {
156
- let previousLines: string[] = [];
157
- let previousOutput = "";
158
- let hasHiddenCursor = false;
159
- let cursorPosition: CursorPosition | undefined;
160
- let cursorDirty = false;
161
- let previousCursorPosition: CursorPosition | undefined;
162
- let cursorWasShown = false;
163
-
164
- const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
165
- const hasChanges = (str: string, activeCursor: CursorPosition | undefined): boolean => {
166
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
167
- return str !== previousOutput || cursorChanged;
168
- };
169
-
170
- const render = (str: string) => {
171
- if (!showCursor && !hasHiddenCursor) {
172
- cliCursor.hide(stream);
173
- hasHiddenCursor = true;
174
- }
175
-
176
- // Only use cursor if setCursorPosition was called since last render.
177
- // This ensures stale positions don't persist after component unmount.
178
- const activeCursor = getActiveCursor();
179
- cursorDirty = false;
180
- const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
181
-
182
- if (!hasChanges(str, activeCursor)) {
183
- return false;
184
- }
185
-
186
- const nextLines = str.split("\n");
187
- const visibleCount = visibleLineCount(nextLines, str);
188
- const previousVisible = visibleLineCount(previousLines, previousOutput);
189
-
190
- if (str === previousOutput && cursorChanged) {
191
- stream.write(
192
- buildCursorOnlySequence({
193
- cursorWasShown,
194
- previousLineCount: previousLines.length,
195
- previousCursorPosition,
196
- cursorPosition: activeCursor,
197
- }),
198
- );
199
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
200
- cursorWasShown = activeCursor !== undefined;
201
- return true;
202
- }
203
-
204
- const returnPrefix = buildReturnToBottomPrefix(
205
- cursorWasShown,
206
- previousLines.length,
207
- previousCursorPosition,
208
- );
209
-
210
- if (str === "\n" || previousOutput.length === 0) {
211
- const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
212
- stream.write(
213
- returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix,
214
- );
215
- cursorWasShown = activeCursor !== undefined;
216
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
217
- previousOutput = str;
218
- previousLines = nextLines;
219
- return true;
220
- }
221
-
222
- // A frame that grew may need rows past the bottom margin. The
223
- // incremental walk moves over unchanged lines with cursorNextLine,
224
- // which clamps at the bottom margin instead of scrolling (only a
225
- // line feed scrolls), so a grown frame that reaches the bottom
226
- // desynchronizes the tracked cursor position from the terminal.
227
- // Rewrite the whole frame instead — every line is emitted with a
228
- // newline, which scrolls correctly at the bottom.
229
- if (visibleCount > previousVisible) {
230
- const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
231
- stream.write(
232
- returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix,
233
- );
234
- cursorWasShown = activeCursor !== undefined;
235
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
236
- previousOutput = str;
237
- previousLines = nextLines;
238
- return true;
239
- }
240
-
241
- const hasTrailingNewline = str.endsWith("\n");
242
-
243
- // We aggregate all chunks for incremental rendering into a buffer, and then write them to stdout at the end.
244
- const buffer: string[] = [];
245
-
246
- buffer.push(returnPrefix);
247
-
248
- // Clear extra lines if the current content's line count is lower than the previous.
249
- if (visibleCount < previousVisible) {
250
- const previousHadTrailingNewline = previousOutput.endsWith("\n");
251
- const extraSlot = previousHadTrailingNewline ? 1 : 0;
252
- buffer.push(
253
- ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot),
254
- ansiEscapes.cursorUp(visibleCount),
255
- );
256
- } else {
257
- buffer.push(ansiEscapes.cursorUp(previousLines.length - 1));
258
- }
259
-
260
- for (let i = 0; i < visibleCount; i++) {
261
- const isLastLine = i === visibleCount - 1;
262
-
263
- // We do not write lines if the contents are the same. This prevents flickering during renders.
264
- if (nextLines[i] === previousLines[i]) {
265
- // Don't move past the last line when there's no trailing newline,
266
- // otherwise the cursor overshoots the rendered block.
267
- if (!isLastLine || hasTrailingNewline) {
268
- buffer.push(ansiEscapes.cursorNextLine);
269
- }
270
-
271
- continue;
272
- }
273
-
274
- buffer.push(
275
- ansiEscapes.cursorTo(0) +
276
- nextLines[i] +
277
- ansiEscapes.eraseEndLine +
278
- // Don't append newline after the last line when the input
279
- // has no trailing newline (fullscreen mode).
280
- (isLastLine && !hasTrailingNewline ? "" : "\n"),
281
- );
282
- }
283
-
284
- const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
285
- buffer.push(cursorSuffix);
286
-
287
- stream.write(buffer.join(""));
288
-
289
- cursorWasShown = activeCursor !== undefined;
290
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
291
- previousOutput = str;
292
- previousLines = nextLines;
293
- return true;
294
- };
295
-
296
- render.clear = () => {
297
- const prefix = buildReturnToBottomPrefix(
298
- cursorWasShown,
299
- previousLines.length,
300
- previousCursorPosition,
301
- );
302
- stream.write(prefix + ansiEscapes.eraseLines(previousLines.length));
303
- previousOutput = "";
304
- previousLines = [];
305
- previousCursorPosition = undefined;
306
- cursorWasShown = false;
307
- };
308
-
309
- render.done = () => {
310
- previousOutput = "";
311
- previousLines = [];
312
- previousCursorPosition = undefined;
313
- cursorWasShown = false;
314
-
315
- if (!showCursor) {
316
- cliCursor.show(stream);
317
- hasHiddenCursor = false;
318
- }
319
- };
320
-
321
- render.reset = () => {
322
- previousOutput = "";
323
- previousLines = [];
324
- previousCursorPosition = undefined;
325
- cursorWasShown = false;
326
- };
327
-
328
- render.sync = (str: string) => {
329
- const activeCursor = cursorDirty ? cursorPosition : undefined;
330
- cursorDirty = false;
331
-
332
- const lines = str.split("\n");
333
- previousOutput = str;
334
- previousLines = lines;
335
-
336
- if (!activeCursor && cursorWasShown) {
337
- stream.write(hideCursorEscape);
338
- }
339
-
340
- if (activeCursor) {
341
- stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
342
- }
343
-
344
- previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
345
- cursorWasShown = activeCursor !== undefined;
346
- };
347
-
348
- render.setCursorPosition = (position: CursorPosition | undefined) => {
349
- cursorPosition = position;
350
- cursorDirty = true;
351
- };
352
-
353
- render.isCursorDirty = () => cursorDirty;
354
- render.willRender = (str: string) => hasChanges(str, getActiveCursor());
355
-
356
- return render;
357
- };
358
-
359
- const create = (
360
- stream: NodeJS.WritableStream,
361
- { showCursor = false, incremental = false } = {},
362
- ): LogUpdate => {
363
- if (incremental) {
364
- return createIncremental(stream, { showCursor });
365
- }
366
-
367
- return createStandard(stream, { showCursor });
368
- };
369
-
370
- export const logUpdate = { create };
package/src/output.ts DELETED
@@ -1,308 +0,0 @@
1
- import { sliceAnsi } from "./ansi/slice.ts";
2
- import { stringWidth } from "./ansi/string-width.ts";
3
- import {
4
- type StyledChar,
5
- styledCharsFromTokens,
6
- styledCharsToString,
7
- tokenize,
8
- } from "./ansi/tokenize.ts";
9
- import { type OutputTransformer } from "./render-node-to-output.ts";
10
-
11
- /**
12
- "Virtual" output class
13
-
14
- Handles the positioning and saving of the output of each node in the tree. Also responsible for applying transformations to each character of the output.
15
-
16
- Used to generate the final output of all nodes before writing it to actual output stream (e.g. stdout)
17
- */
18
-
19
- type Options = {
20
- width: number;
21
- height: number;
22
- };
23
-
24
- type Operation = WriteOperation | ClipOperation | UnclipOperation;
25
-
26
- type WriteOperation = {
27
- type: "write";
28
- x: number;
29
- y: number;
30
- text: string;
31
- transformers: OutputTransformer[];
32
- };
33
-
34
- type ClipOperation = {
35
- type: "clip";
36
- clip: Clip;
37
- };
38
-
39
- type Clip = {
40
- x1: number | undefined;
41
- x2: number | undefined;
42
- y1: number | undefined;
43
- y2: number | undefined;
44
- };
45
-
46
- type UnclipOperation = {
47
- type: "unclip";
48
- };
49
-
50
- class OutputCaches {
51
- widths = new Map<string, number>();
52
- blockWidths = new Map<string, number>();
53
- styledChars = new Map<string, StyledChar[]>();
54
-
55
- getStyledChars(line: string): StyledChar[] {
56
- let cached = this.styledChars.get(line);
57
- if (cached === undefined) {
58
- cached = styledCharsFromTokens(tokenize(line));
59
- this.styledChars.set(line, cached);
60
- }
61
-
62
- return cached;
63
- }
64
-
65
- getStringWidth(text: string): number {
66
- let cached = this.widths.get(text);
67
- if (cached === undefined) {
68
- cached = stringWidth(text);
69
- this.widths.set(text, cached);
70
- }
71
-
72
- return cached;
73
- }
74
-
75
- getWidestLine(text: string): number {
76
- let cached = this.blockWidths.get(text);
77
- if (cached === undefined) {
78
- let lineWidth = 0;
79
- for (const line of text.split("\n")) {
80
- lineWidth = Math.max(lineWidth, this.getStringWidth(line));
81
- }
82
-
83
- cached = lineWidth;
84
- this.blockWidths.set(text, cached);
85
- }
86
-
87
- return cached;
88
- }
89
- }
90
-
91
- export class Output {
92
- width: number;
93
- height: number;
94
-
95
- private readonly operations: Operation[] = [];
96
- private readonly caches: OutputCaches = new OutputCaches();
97
-
98
- constructor(options: Options) {
99
- const { width, height } = options;
100
-
101
- this.width = width;
102
- this.height = height;
103
- }
104
-
105
- write(x: number, y: number, text: string, options: { transformers: OutputTransformer[] }): void {
106
- const { transformers } = options;
107
-
108
- if (!text) {
109
- return;
110
- }
111
-
112
- this.operations.push({
113
- type: "write",
114
- x,
115
- y,
116
- text,
117
- transformers,
118
- });
119
- }
120
-
121
- clip(clip: Clip) {
122
- this.operations.push({
123
- type: "clip",
124
- clip,
125
- });
126
- }
127
-
128
- unclip() {
129
- this.operations.push({
130
- type: "unclip",
131
- });
132
- }
133
-
134
- get(): { output: string; height: number } {
135
- // Initialize output array with a specific set of rows, so that margin/padding at the bottom is preserved
136
- const output: StyledChar[][] = [];
137
-
138
- for (let y = 0; y < this.height; y++) {
139
- const row: StyledChar[] = [];
140
-
141
- for (let x = 0; x < this.width; x++) {
142
- row.push({
143
- type: "char",
144
- value: " ",
145
- fullWidth: false,
146
- styles: [],
147
- });
148
- }
149
-
150
- output.push(row);
151
- }
152
-
153
- const clips: Clip[] = [];
154
-
155
- for (const operation of this.operations) {
156
- if (operation.type === "clip") {
157
- clips.push(operation.clip);
158
- }
159
-
160
- if (operation.type === "unclip") {
161
- clips.pop();
162
- }
163
-
164
- if (operation.type === "write") {
165
- const { text, transformers } = operation;
166
- let { x, y } = operation;
167
- let lines = text.split("\n");
168
-
169
- const clip = clips.at(-1);
170
-
171
- if (clip) {
172
- const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
173
-
174
- const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
175
-
176
- // If text is positioned outside of clipping area altogether,
177
- // skip to the next operation to avoid unnecessary calculations
178
- if (clipHorizontally) {
179
- const width = this.caches.getWidestLine(text);
180
-
181
- if (x + width < clip.x1! || x > clip.x2!) {
182
- continue;
183
- }
184
- }
185
-
186
- if (clipVertically) {
187
- const height = lines.length;
188
-
189
- if (y + height < clip.y1! || y > clip.y2!) {
190
- continue;
191
- }
192
- }
193
-
194
- if (clipHorizontally) {
195
- lines = lines.map((line) => {
196
- const from = x < clip.x1! ? clip.x1! - x : 0;
197
- const width = this.caches.getStringWidth(line);
198
- const to = x + width > clip.x2! ? clip.x2! - x : width;
199
-
200
- return sliceAnsi(line, from, to);
201
- });
202
-
203
- if (x < clip.x1!) {
204
- x = clip.x1!;
205
- }
206
- }
207
-
208
- if (clipVertically) {
209
- const from = y < clip.y1! ? clip.y1! - y : 0;
210
- const height = lines.length;
211
- const to = y + height > clip.y2! ? clip.y2! - y : height;
212
-
213
- lines = lines.slice(from, to);
214
-
215
- if (y < clip.y1!) {
216
- y = clip.y1!;
217
- }
218
- }
219
- }
220
-
221
- let offsetY = 0;
222
-
223
- for (let [index, line] of lines.entries()) {
224
- const currentLine = output[y + offsetY];
225
-
226
- // Line can be missing if `text` is taller than height of pre-initialized `this.output`
227
- if (!currentLine) {
228
- continue;
229
- }
230
-
231
- for (const transformer of transformers) {
232
- line = transformer(line, index);
233
- }
234
-
235
- const characters = this.caches.getStyledChars(line);
236
- let offsetX = x;
237
-
238
- // Nothing to write (e.g. line was clipped away).
239
- if (characters.length === 0) {
240
- offsetY++;
241
- continue;
242
- }
243
-
244
- const spaceCell: StyledChar = {
245
- type: "char",
246
- value: " ",
247
- fullWidth: false,
248
- styles: [],
249
- };
250
-
251
- // Wide characters (e.g. CJK) occupy two cells: a leading
252
- // cell with the character and a trailing placeholder with
253
- // value ''. When an overlapping write lands in the middle
254
- // of a wide character, the boundary cells need cleanup so
255
- // the terminal never renders a half-visible wide character.
256
- if (
257
- currentLine[offsetX]?.value === "" &&
258
- offsetX > 0 &&
259
- this.caches.getStringWidth(currentLine[offsetX - 1]?.value ?? "") > 1
260
- ) {
261
- currentLine[offsetX - 1] = spaceCell;
262
- }
263
-
264
- for (const character of characters) {
265
- currentLine[offsetX] = character;
266
-
267
- // Determine printed width using string-width to align with measurement
268
- const characterWidth = Math.max(1, this.caches.getStringWidth(character.value));
269
-
270
- // For multi-column characters, clear following cells to avoid stray spaces/artifacts
271
- if (characterWidth > 1) {
272
- for (let columnOffset = 1; columnOffset < characterWidth; columnOffset++) {
273
- currentLine[offsetX + columnOffset] = {
274
- type: "char",
275
- value: "",
276
- fullWidth: false,
277
- styles: character.styles,
278
- };
279
- }
280
- }
281
-
282
- offsetX += characterWidth;
283
- }
284
-
285
- if (currentLine[offsetX]?.value === "") {
286
- currentLine[offsetX] = spaceCell;
287
- }
288
-
289
- offsetY++;
290
- }
291
- }
292
- }
293
-
294
- const generatedOutput = output
295
- .map((line) => {
296
- // See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
297
- const lineWithoutEmptyItems = line.filter((item) => item !== undefined);
298
-
299
- return styledCharsToString(lineWithoutEmptyItems).trimEnd();
300
- })
301
- .join("\n");
302
-
303
- return {
304
- output: generatedOutput,
305
- height: output.length,
306
- };
307
- }
308
- }