@kingkoo1985/ink 6.6.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 (225) hide show
  1. package/build/colorize.d.ts +4 -0
  2. package/build/colorize.js +59 -0
  3. package/build/colorize.js.map +1 -0
  4. package/build/components/AccessibilityContext.d.ts +3 -0
  5. package/build/components/AccessibilityContext.js +5 -0
  6. package/build/components/AccessibilityContext.js.map +1 -0
  7. package/build/components/App.d.ts +68 -0
  8. package/build/components/App.js +290 -0
  9. package/build/components/App.js.map +1 -0
  10. package/build/components/AppContext.d.ts +52 -0
  11. package/build/components/AppContext.js +16 -0
  12. package/build/components/AppContext.js.map +1 -0
  13. package/build/components/BackgroundContext.d.ts +4 -0
  14. package/build/components/BackgroundContext.js +3 -0
  15. package/build/components/BackgroundContext.js.map +1 -0
  16. package/build/components/Box.d.ts +171 -0
  17. package/build/components/Box.js +40 -0
  18. package/build/components/Box.js.map +1 -0
  19. package/build/components/ErrorOverview.d.ts +6 -0
  20. package/build/components/ErrorOverview.js +84 -0
  21. package/build/components/ErrorOverview.js.map +1 -0
  22. package/build/components/FocusContext.d.ts +16 -0
  23. package/build/components/FocusContext.js +16 -0
  24. package/build/components/FocusContext.js.map +1 -0
  25. package/build/components/Newline.d.ts +13 -0
  26. package/build/components/Newline.js +8 -0
  27. package/build/components/Newline.js.map +1 -0
  28. package/build/components/Spacer.d.ts +7 -0
  29. package/build/components/Spacer.js +11 -0
  30. package/build/components/Spacer.js.map +1 -0
  31. package/build/components/Static.d.ts +24 -0
  32. package/build/components/Static.js +29 -0
  33. package/build/components/Static.js.map +1 -0
  34. package/build/components/StaticRender.d.ts +8 -0
  35. package/build/components/StaticRender.js +19 -0
  36. package/build/components/StaticRender.js.map +1 -0
  37. package/build/components/StderrContext.d.ts +15 -0
  38. package/build/components/StderrContext.js +12 -0
  39. package/build/components/StderrContext.js.map +1 -0
  40. package/build/components/StdinContext.d.ts +22 -0
  41. package/build/components/StdinContext.js +16 -0
  42. package/build/components/StdinContext.js.map +1 -0
  43. package/build/components/StdoutContext.d.ts +15 -0
  44. package/build/components/StdoutContext.js +12 -0
  45. package/build/components/StdoutContext.js.map +1 -0
  46. package/build/components/Text.d.ts +63 -0
  47. package/build/components/Text.js +50 -0
  48. package/build/components/Text.js.map +1 -0
  49. package/build/components/Transform.d.ts +16 -0
  50. package/build/components/Transform.js +15 -0
  51. package/build/components/Transform.js.map +1 -0
  52. package/build/data-limited-lru-map.d.ts +20 -0
  53. package/build/data-limited-lru-map.js +65 -0
  54. package/build/data-limited-lru-map.js.map +1 -0
  55. package/build/debug-log.d.ts +2 -0
  56. package/build/debug-log.js +44 -0
  57. package/build/debug-log.js.map +1 -0
  58. package/build/devtools-window-polyfill.d.ts +1 -0
  59. package/build/devtools-window-polyfill.js +65 -0
  60. package/build/devtools-window-polyfill.js.map +1 -0
  61. package/build/devtools.d.ts +1 -0
  62. package/build/devtools.js +8 -0
  63. package/build/devtools.js.map +1 -0
  64. package/build/dom.d.ts +114 -0
  65. package/build/dom.js +169 -0
  66. package/build/dom.js.map +1 -0
  67. package/build/get-max-width.d.ts +3 -0
  68. package/build/get-max-width.js +10 -0
  69. package/build/get-max-width.js.map +1 -0
  70. package/build/hooks/use-app.d.ts +5 -0
  71. package/build/hooks/use-app.js +8 -0
  72. package/build/hooks/use-app.js.map +1 -0
  73. package/build/hooks/use-focus-manager.d.ts +28 -0
  74. package/build/hooks/use-focus-manager.js +17 -0
  75. package/build/hooks/use-focus-manager.js.map +1 -0
  76. package/build/hooks/use-focus.d.ts +29 -0
  77. package/build/hooks/use-focus.js +42 -0
  78. package/build/hooks/use-focus.js.map +1 -0
  79. package/build/hooks/use-input.d.ts +93 -0
  80. package/build/hooks/use-input.js +92 -0
  81. package/build/hooks/use-input.js.map +1 -0
  82. package/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  83. package/build/hooks/use-is-screen-reader-enabled.js +11 -0
  84. package/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  85. package/build/hooks/use-stderr.d.ts +5 -0
  86. package/build/hooks/use-stderr.js +8 -0
  87. package/build/hooks/use-stderr.js.map +1 -0
  88. package/build/hooks/use-stdin.d.ts +5 -0
  89. package/build/hooks/use-stdin.js +8 -0
  90. package/build/hooks/use-stdin.js.map +1 -0
  91. package/build/hooks/use-stdout.d.ts +5 -0
  92. package/build/hooks/use-stdout.js +8 -0
  93. package/build/hooks/use-stdout.js.map +1 -0
  94. package/build/index.d.ts +38 -0
  95. package/build/index.js +27 -0
  96. package/build/index.js.map +1 -0
  97. package/build/ink.d.ts +110 -0
  98. package/build/ink.js +576 -0
  99. package/build/ink.js.map +1 -0
  100. package/build/instances.d.ts +3 -0
  101. package/build/instances.js +8 -0
  102. package/build/instances.js.map +1 -0
  103. package/build/layout.d.ts +18 -0
  104. package/build/layout.js +54 -0
  105. package/build/layout.js.map +1 -0
  106. package/build/log-update.d.ts +28 -0
  107. package/build/log-update.js +529 -0
  108. package/build/log-update.js.map +1 -0
  109. package/build/measure-element.d.ts +119 -0
  110. package/build/measure-element.js +825 -0
  111. package/build/measure-element.js.map +1 -0
  112. package/build/measure-text.d.ts +50 -0
  113. package/build/measure-text.js +237 -0
  114. package/build/measure-text.js.map +1 -0
  115. package/build/output.d.ts +242 -0
  116. package/build/output.js +607 -0
  117. package/build/output.js.map +1 -0
  118. package/build/parse-keypress.d.ts +14 -0
  119. package/build/parse-keypress.js +225 -0
  120. package/build/parse-keypress.js.map +1 -0
  121. package/build/reconciler.d.ts +4 -0
  122. package/build/reconciler.js +326 -0
  123. package/build/reconciler.js.map +1 -0
  124. package/build/render-background.d.ts +4 -0
  125. package/build/render-background.js +37 -0
  126. package/build/render-background.js.map +1 -0
  127. package/build/render-border.d.ts +4 -0
  128. package/build/render-border.js +81 -0
  129. package/build/render-border.js.map +1 -0
  130. package/build/render-cached.d.ts +18 -0
  131. package/build/render-cached.js +66 -0
  132. package/build/render-cached.js.map +1 -0
  133. package/build/render-container.d.ts +27 -0
  134. package/build/render-container.js +169 -0
  135. package/build/render-container.js.map +1 -0
  136. package/build/render-node-to-output.d.ts +32 -0
  137. package/build/render-node-to-output.js +177 -0
  138. package/build/render-node-to-output.js.map +1 -0
  139. package/build/render-screen-reader.d.ts +5 -0
  140. package/build/render-screen-reader.js +54 -0
  141. package/build/render-screen-reader.js.map +1 -0
  142. package/build/render-scrollbar.d.ts +23 -0
  143. package/build/render-scrollbar.js +70 -0
  144. package/build/render-scrollbar.js.map +1 -0
  145. package/build/render-sticky.d.ts +53 -0
  146. package/build/render-sticky.js +317 -0
  147. package/build/render-sticky.js.map +1 -0
  148. package/build/render-text-node.d.ts +20 -0
  149. package/build/render-text-node.js +155 -0
  150. package/build/render-text-node.js.map +1 -0
  151. package/build/render.d.ts +165 -0
  152. package/build/render.js +60 -0
  153. package/build/render.js.map +1 -0
  154. package/build/renderer.d.ts +24 -0
  155. package/build/renderer.js +292 -0
  156. package/build/renderer.js.map +1 -0
  157. package/build/replay.d.ts +59 -0
  158. package/build/replay.js +128 -0
  159. package/build/replay.js.map +1 -0
  160. package/build/resize-observer.d.ts +24 -0
  161. package/build/resize-observer.js +102 -0
  162. package/build/resize-observer.js.map +1 -0
  163. package/build/scroll.d.ts +11 -0
  164. package/build/scroll.js +123 -0
  165. package/build/scroll.js.map +1 -0
  166. package/build/selection.d.ts +52 -0
  167. package/build/selection.js +359 -0
  168. package/build/selection.js.map +1 -0
  169. package/build/serialization.d.ts +25 -0
  170. package/build/serialization.js +224 -0
  171. package/build/serialization.js.map +1 -0
  172. package/build/squash-text-nodes.d.ts +16 -0
  173. package/build/squash-text-nodes.js +58 -0
  174. package/build/squash-text-nodes.js.map +1 -0
  175. package/build/styled-line.d.ts +58 -0
  176. package/build/styled-line.js +629 -0
  177. package/build/styled-line.js.map +1 -0
  178. package/build/styles.d.ts +286 -0
  179. package/build/styles.js +257 -0
  180. package/build/styles.js.map +1 -0
  181. package/build/terminal-buffer.d.ts +57 -0
  182. package/build/terminal-buffer.js +507 -0
  183. package/build/terminal-buffer.js.map +1 -0
  184. package/build/text-wrap.d.ts +12 -0
  185. package/build/text-wrap.js +154 -0
  186. package/build/text-wrap.js.map +1 -0
  187. package/build/tokenize.d.ts +47 -0
  188. package/build/tokenize.js +419 -0
  189. package/build/tokenize.js.map +1 -0
  190. package/build/vertical-gap.d.ts +17 -0
  191. package/build/vertical-gap.js +20 -0
  192. package/build/vertical-gap.js.map +1 -0
  193. package/build/worker/animation-controller.d.ts +72 -0
  194. package/build/worker/animation-controller.js +128 -0
  195. package/build/worker/animation-controller.js.map +1 -0
  196. package/build/worker/ansi-utils.d.ts +16 -0
  197. package/build/worker/ansi-utils.js +40 -0
  198. package/build/worker/ansi-utils.js.map +1 -0
  199. package/build/worker/canvas.d.ts +49 -0
  200. package/build/worker/canvas.js +90 -0
  201. package/build/worker/canvas.js.map +1 -0
  202. package/build/worker/compositor.d.ts +33 -0
  203. package/build/worker/compositor.js +308 -0
  204. package/build/worker/compositor.js.map +1 -0
  205. package/build/worker/platform.d.ts +15 -0
  206. package/build/worker/platform.js +19 -0
  207. package/build/worker/platform.js.map +1 -0
  208. package/build/worker/render-worker.d.ts +112 -0
  209. package/build/worker/render-worker.js +944 -0
  210. package/build/worker/render-worker.js.map +1 -0
  211. package/build/worker/scene-manager.d.ts +26 -0
  212. package/build/worker/scene-manager.js +109 -0
  213. package/build/worker/scene-manager.js.map +1 -0
  214. package/build/worker/scroll-optimizer.d.ts +32 -0
  215. package/build/worker/scroll-optimizer.js +110 -0
  216. package/build/worker/scroll-optimizer.js.map +1 -0
  217. package/build/worker/terminal-writer.d.ts +116 -0
  218. package/build/worker/terminal-writer.js +708 -0
  219. package/build/worker/terminal-writer.js.map +1 -0
  220. package/build/worker/worker-entry.d.ts +6 -0
  221. package/build/worker/worker-entry.js +130 -0
  222. package/build/worker/worker-entry.js.map +1 -0
  223. package/license +9 -0
  224. package/package.json +208 -0
  225. package/readme.md +2353 -0
@@ -0,0 +1,708 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import process from 'node:process';
7
+ import ansiEscapes from 'ansi-escapes';
8
+ import { styledLineToString } from '../tokenize.js';
9
+ import { StyledLine } from '../styled-line.js';
10
+ import { debugLog } from '../debug-log.js';
11
+ import colorize from '../colorize.js';
12
+ import { debugWorker } from './render-worker.js';
13
+ import { enterSynchronizedOutput, exitSynchronizedOutput, resetScrollRegion, ris, clearScrollbackStandard, homeEraseDown, getMoveCursorDownCode, getMoveCursorUpCode, getDeleteLinesCode, getInsertLinesCode, getSetScrollRegionCode, } from './ansi-utils.js';
14
+ import { platform } from './platform.js';
15
+ const synchronizeOutput = true;
16
+ export const rainbowColors = [
17
+ 'ansi256(17)',
18
+ 'ansi256(18)',
19
+ 'ansi256(19)',
20
+ 'ansi256(20)',
21
+ 'ansi256(21)',
22
+ 'ansi256(25)',
23
+ 'ansi256(26)',
24
+ 'ansi256(27)',
25
+ 'ansi256(31)',
26
+ 'ansi256(32)',
27
+ 'ansi256(33)',
28
+ 'ansi256(39)',
29
+ 'ansi256(63)',
30
+ 'ansi256(69)',
31
+ 'ansi256(75)',
32
+ ];
33
+ export function linesEqual(lineA, lineB) {
34
+ if (lineA === lineB)
35
+ return true;
36
+ if (!lineA || !lineB)
37
+ return false;
38
+ return lineA.equals(lineB);
39
+ }
40
+ /**
41
+ * Low level terminal renderer.
42
+ *
43
+ * This class makes it robust and simple to perform efficient incremental updates to the
44
+ * terminal, scroll regions, and inject content into the backbuffer.
45
+ * It handles caching what content was previously rendered and operations
46
+ * such as syncing individual lines without generating flicker, adding
47
+ * lines to the backbuffer, and scrolling content onto the backbuffer.
48
+ */
49
+ export class TerminalWriter {
50
+ columns;
51
+ rows;
52
+ stdout;
53
+ isTainted = false;
54
+ debugRainbowColor;
55
+ backbufferDirty = false;
56
+ backbufferScrolledIncorrectly = false;
57
+ backbufferDirtyCurrentFrame = false;
58
+ fullRenderTimeout;
59
+ maxScrollbackLength = 1000;
60
+ forceScrollToBottomOnBackbufferRefresh = false;
61
+ linesUpdated = 0;
62
+ screen = [];
63
+ backbuffer = [];
64
+ cursorX = -1;
65
+ cursorY = -1;
66
+ targetCursorX = -1;
67
+ targetCursorY = -1;
68
+ scrollRegionTop = -1;
69
+ scrollRegionBottom = -1;
70
+ firstRender = true;
71
+ enableSynchronizedOutput = synchronizeOutput;
72
+ cancelSlowFlush;
73
+ isDone = false;
74
+ outputBuffer = [];
75
+ currentChunkBuffer = [];
76
+ constructor(columns, rows, stdout) {
77
+ this.columns = columns;
78
+ this.rows = rows;
79
+ this.stdout = stdout;
80
+ }
81
+ getLinesUpdated() {
82
+ return this.linesUpdated;
83
+ }
84
+ resetLinesUpdated() {
85
+ this.linesUpdated = 0;
86
+ }
87
+ unkownCursorLocation() {
88
+ this.cursorX = -1;
89
+ this.cursorY = -1;
90
+ }
91
+ writeRaw(text) {
92
+ this.writeHelper(text);
93
+ }
94
+ taintScreen() {
95
+ for (const line of this.screen) {
96
+ if (line) {
97
+ line.tainted = true;
98
+ }
99
+ }
100
+ }
101
+ getBackbufferLength() {
102
+ return this.backbuffer.length;
103
+ }
104
+ getBackbufferEntry(index) {
105
+ return this.backbuffer[index];
106
+ }
107
+ getScreenLine(y) {
108
+ return this.screen[y];
109
+ }
110
+ setBackbuffer(lines) {
111
+ this.backbuffer = lines;
112
+ }
113
+ get isFirstRender() {
114
+ return this.firstRender;
115
+ }
116
+ appendLinesBackbuffer(lines) {
117
+ this.startSynchronizedOutput();
118
+ try {
119
+ for (const line of lines) {
120
+ // 1. Replace the top line with the clean version
121
+ this.syncLine(line, 0);
122
+ // 2. Scroll the terminal up, which pushes row 0 (the clean line) to history
123
+ this.applyScrollUpBackbuffer(0, this.rows);
124
+ }
125
+ }
126
+ finally {
127
+ this.endSynchronizedOutput();
128
+ }
129
+ }
130
+ updateBackbuffer(start, deleteCount, newLines) {
131
+ const backbufferLength = this.backbuffer.length;
132
+ const screenStart = Math.max(0, backbufferLength - this.rows);
133
+ // Case 1: Append at the very end
134
+ if (start === backbufferLength && deleteCount === 0) {
135
+ this.appendLinesBackbuffer(newLines);
136
+ return;
137
+ }
138
+ // Case 2: Within the screen
139
+ if (start >= screenStart) {
140
+ this.backbuffer.splice(start, deleteCount, ...newLines);
141
+ return;
142
+ }
143
+ // Case 3: Other cases (outside screen, not append)
144
+ this.isTainted = true;
145
+ }
146
+ syncLines(lines) {
147
+ const backBufferLength = Math.max(0, lines.length - this.rows);
148
+ for (const [i, line] of lines.entries()) {
149
+ if (i < backBufferLength) {
150
+ const clampedLine = this.clampLine(line.styledChars, this.columns);
151
+ this.backbuffer.push(clampedLine);
152
+ if (this.backbuffer.length > this.maxScrollbackLength) {
153
+ this.backbuffer.shift();
154
+ }
155
+ }
156
+ else {
157
+ const screenRow = i - backBufferLength;
158
+ this.syncLine(line, screenRow);
159
+ }
160
+ }
161
+ this.firstRender = false;
162
+ }
163
+ writeLines(lines) {
164
+ if (this.backbuffer.length > 0 || this.screen.length > 0) {
165
+ throw new Error(`writeLines can only be called on an empty terminal. Sizes = ${this.backbuffer.length}, ${this.screen.length}`);
166
+ }
167
+ const backBufferLength = Math.max(0, lines.length - this.rows);
168
+ for (const [i, line] of lines.entries()) {
169
+ const clampedLine = this.clampLine(line.styledChars, this.columns);
170
+ let textToWrite = clampedLine.text;
171
+ if (this.debugRainbowColor) {
172
+ textToWrite = colorize(textToWrite, this.debugRainbowColor, 'background');
173
+ }
174
+ this.writeHelper(textToWrite);
175
+ this.linesUpdated++;
176
+ if (i >= backBufferLength &&
177
+ i < backBufferLength + this.rows &&
178
+ this.isFirstRender &&
179
+ clampedLine.length < this.columns) {
180
+ // Need to clear any text we might be rendering on top of.
181
+ this.writeHelper(ansiEscapes.eraseEndLine);
182
+ }
183
+ if (i + 1 < lines.length) {
184
+ this.writeHelper('\n');
185
+ }
186
+ if (i < backBufferLength) {
187
+ this.backbuffer.push(clampedLine);
188
+ if (this.backbuffer.length > this.maxScrollbackLength) {
189
+ this.backbuffer.shift();
190
+ }
191
+ }
192
+ else {
193
+ this.screen.push(clampedLine);
194
+ }
195
+ }
196
+ if (this.isFirstRender) {
197
+ /// Clean up lines at the bottom of the screen if we
198
+ // rendered at less than the terminal height.
199
+ for (let row = lines.length; row < this.rows; row++) {
200
+ this.writeHelper('\n' + ansiEscapes.eraseEndLine);
201
+ }
202
+ }
203
+ this.cursorX = -1;
204
+ this.cursorY = -1;
205
+ this.firstRender = false;
206
+ this.finishChunkAndUpdateCursor();
207
+ }
208
+ setTargetCursorPosition(row, col) {
209
+ if (this.targetCursorY === row && this.targetCursorX === col) {
210
+ return;
211
+ }
212
+ this.targetCursorY = row;
213
+ this.targetCursorX = col;
214
+ }
215
+ getExpectedState() {
216
+ return {
217
+ backbuffer: [...this.backbuffer],
218
+ screen: [...this.screen],
219
+ cursorX: this.cursorX,
220
+ cursorY: this.cursorY,
221
+ };
222
+ }
223
+ finish() {
224
+ this.finishChunkAndUpdateCursor();
225
+ this.targetCursorY = -1;
226
+ this.targetCursorX = -1;
227
+ }
228
+ done() {
229
+ this.finishChunkAndUpdateCursor();
230
+ this.unkownCursorLocation();
231
+ this.resetScrollRegion();
232
+ if (this.screen.length > 0) {
233
+ this.moveCursor(this.rows - 1, 0);
234
+ this.writeHelper('\n');
235
+ this.cursorX = 0;
236
+ this.cursorY = this.rows;
237
+ }
238
+ this.finishChunkAndUpdateCursor();
239
+ this.flush();
240
+ this.isDone = true;
241
+ }
242
+ moveCursor(x, y) {
243
+ if (x === this.cursorY && y === this.cursorX) {
244
+ return;
245
+ }
246
+ const diff = x - this.cursorY;
247
+ if (this.cursorY < 0 ||
248
+ this.cursorX < 0 ||
249
+ x !== this.cursorY ||
250
+ y !== this.cursorX) {
251
+ this.writeHelper(ansiEscapes.cursorTo(y, x));
252
+ this.cursorY = x;
253
+ this.cursorX = y;
254
+ return;
255
+ }
256
+ if (diff > 0) {
257
+ this.writeHelper(getMoveCursorDownCode(diff));
258
+ }
259
+ else if (diff < 0) {
260
+ this.writeHelper(getMoveCursorUpCode(-diff));
261
+ }
262
+ this.cursorY = x;
263
+ if (y !== this.cursorX) {
264
+ if (y === 0) {
265
+ this.writeHelper(ansiEscapes.cursorLeft);
266
+ }
267
+ else {
268
+ this.writeHelper(ansiEscapes.cursorTo(y));
269
+ }
270
+ this.cursorX = y;
271
+ }
272
+ }
273
+ clampLine(line, width) {
274
+ if (width <= 0 || !line) {
275
+ return {
276
+ styledChars: new StyledLine(),
277
+ text: '',
278
+ length: 0,
279
+ tainted: false,
280
+ };
281
+ }
282
+ let i = line.length - 1;
283
+ while (i >= 0 && line.getValue(i) === ' ' && !line.hasStyles(i)) {
284
+ i--;
285
+ }
286
+ const trimmedLength = i + 1;
287
+ let visualWidth = 0;
288
+ for (let k = 0; k < trimmedLength; k++) {
289
+ const val = line.getValue(k);
290
+ if (val === '')
291
+ continue;
292
+ visualWidth += line.getFullWidth(k) ? 2 : 1;
293
+ }
294
+ if (visualWidth <= width) {
295
+ const styledChars = trimmedLength === line.length ? line : line.slice(0, trimmedLength);
296
+ return {
297
+ styledChars,
298
+ text: styledLineToString(styledChars),
299
+ length: visualWidth,
300
+ tainted: false,
301
+ };
302
+ }
303
+ // Truncate logic
304
+ const lastVal = line.getValue(i);
305
+ const lastFullWidth = line.getFullWidth(i);
306
+ const hasBoxChar = lastVal === '╮' || lastVal === '│' || lastVal === '╯';
307
+ let targetVisualWidth = width;
308
+ if (hasBoxChar) {
309
+ targetVisualWidth -= lastFullWidth ? 2 : 1;
310
+ }
311
+ let currentWidth = 0;
312
+ let sliceIndex = 0;
313
+ for (let k = 0; k < trimmedLength; k++) {
314
+ const charWidth = line.getFullWidth(k) ? 2 : 1;
315
+ if (currentWidth + charWidth > targetVisualWidth) {
316
+ break;
317
+ }
318
+ currentWidth += charWidth;
319
+ sliceIndex++;
320
+ }
321
+ if (hasBoxChar) {
322
+ const boxWidth = lastFullWidth ? 2 : 1;
323
+ const lastCharLine = new StyledLine();
324
+ lastCharLine.pushChar(lastVal, line.getFormatFlags(i), line.getFgColor(i), line.getBgColor(i), line.getLink(i));
325
+ const styledChars = line.slice(0, sliceIndex).combine(lastCharLine);
326
+ return {
327
+ styledChars,
328
+ text: styledLineToString(styledChars),
329
+ length: currentWidth + boxWidth,
330
+ tainted: false,
331
+ };
332
+ }
333
+ const styledChars = line.slice(0, sliceIndex);
334
+ return {
335
+ styledChars,
336
+ text: styledLineToString(styledChars),
337
+ length: currentWidth,
338
+ tainted: false,
339
+ };
340
+ }
341
+ syncLine(line, y) {
342
+ if (y < 0 || y >= this.rows) {
343
+ return;
344
+ }
345
+ const clampedLine = this.clampLine(line.styledChars, this.columns);
346
+ const currentLine = this.screen[y];
347
+ if (currentLine &&
348
+ !currentLine.tainted &&
349
+ currentLine.text === clampedLine.text) {
350
+ // Content matches, no update needed
351
+ return;
352
+ }
353
+ this.moveCursor(y, 0);
354
+ this.linesUpdated++;
355
+ let textToWrite = clampedLine.text;
356
+ if (this.debugRainbowColor) {
357
+ textToWrite = colorize(textToWrite, this.debugRainbowColor, 'background');
358
+ }
359
+ this.writeHelper(textToWrite);
360
+ if (clampedLine.length < this.columns) {
361
+ this.writeHelper(ansiEscapes.eraseEndLine);
362
+ }
363
+ if (y !== this.rows - 1 && y !== this.scrollRegionBottom - 1) {
364
+ this.writeHelper('\n');
365
+ this.cursorY = y + 1;
366
+ this.cursorX = -1;
367
+ }
368
+ else {
369
+ this.cursorY = y;
370
+ this.cursorX = clampedLine.length;
371
+ }
372
+ clampedLine.tainted = false;
373
+ this.screen[y] = clampedLine;
374
+ }
375
+ scrollLines(options) {
376
+ try {
377
+ this.performScroll(options);
378
+ }
379
+ finally {
380
+ this.resetScrollRegion();
381
+ }
382
+ }
383
+ resize(columns, rows) {
384
+ if (this.columns === columns && this.rows === rows) {
385
+ return;
386
+ }
387
+ this.columns = columns;
388
+ this.rows = rows;
389
+ const startIndex = Math.max(0, this.backbuffer.length - this.rows);
390
+ for (let i = startIndex; i < this.backbuffer.length; i++) {
391
+ const line = this.backbuffer[i];
392
+ if (line && line.length >= this.columns) {
393
+ line.tainted = true;
394
+ }
395
+ }
396
+ for (const line of this.screen) {
397
+ if (line) {
398
+ line.tainted = true;
399
+ }
400
+ }
401
+ }
402
+ clear(_options) {
403
+ if (process.env['TERM_PROGRAM'] === 'vscode' &&
404
+ this.forceScrollToBottomOnBackbufferRefresh) {
405
+ this.writeHelper(ris);
406
+ }
407
+ else if (process.env['TERM_PROGRAM'] === 'iTerm.app') {
408
+ this.writeHelper(ansiEscapes.clearTerminal);
409
+ }
410
+ else {
411
+ this.writeHelper(clearScrollbackStandard);
412
+ this.writeHelper(homeEraseDown);
413
+ }
414
+ // Tmux does not reset the scroll region reliably on clear so we
415
+ // reset it manually.
416
+ this.writeHelper(resetScrollRegion);
417
+ this.scrollRegionTop = -1;
418
+ this.scrollRegionBottom = -1;
419
+ this.screen = [];
420
+ this.backbuffer = [];
421
+ this.firstRender = true;
422
+ this.backbufferDirty = false;
423
+ this.backbufferDirtyCurrentFrame = false;
424
+ if (this.fullRenderTimeout) {
425
+ clearTimeout(this.fullRenderTimeout);
426
+ this.fullRenderTimeout = undefined;
427
+ }
428
+ // Set the cursor to an unknown location as tmux
429
+ // Does not appear to always reset it to 0,0 on clear
430
+ // While in mouse mode.
431
+ this.cursorX = -1;
432
+ this.cursorY = -1;
433
+ }
434
+ startSynchronizedOutput() {
435
+ this.writeHelper(enterSynchronizedOutput);
436
+ }
437
+ endSynchronizedOutput() {
438
+ this.writeHelper(exitSynchronizedOutput);
439
+ this.finishChunkAndUpdateCursor();
440
+ }
441
+ flush() {
442
+ if (this.isDone)
443
+ return;
444
+ if (this.cancelSlowFlush) {
445
+ this.cancelSlowFlush();
446
+ }
447
+ this.finishChunkAndUpdateCursor();
448
+ if (this.outputBuffer.length > 0) {
449
+ this.synchronizedWrite(this.outputBuffer.join(''));
450
+ }
451
+ this.firstRender = false;
452
+ this.outputBuffer = [];
453
+ }
454
+ /**
455
+ * Testing only method that flushes content slowly to simplify debugging
456
+ * hard to diagnose issues.
457
+ *
458
+ * If there are bugs in terminal-writer a good way to debug is to call
459
+ * slowFlush instead of flush() so you can see the incremental states the
460
+ * terminal goes through applying the changes.
461
+ */
462
+ async slowFlush() {
463
+ if (this.isDone)
464
+ return;
465
+ if (this.cancelSlowFlush) {
466
+ this.cancelSlowFlush();
467
+ }
468
+ this.finishChunkAndUpdateCursor();
469
+ if (this.outputBuffer.length === 0) {
470
+ return;
471
+ }
472
+ this.firstRender = false;
473
+ while (this.outputBuffer.length > 0) {
474
+ const chunk = this.outputBuffer.shift();
475
+ if (chunk) {
476
+ this.synchronizedWrite(chunk);
477
+ }
478
+ // eslint-disable-next-line no-await-in-loop
479
+ await new Promise(resolve => {
480
+ let finished = false;
481
+ const timer = setTimeout(() => {
482
+ finished = true;
483
+ this.cancelSlowFlush = undefined;
484
+ resolve();
485
+ }, 50);
486
+ this.cancelSlowFlush = () => {
487
+ if (!finished) {
488
+ clearTimeout(timer);
489
+ finished = true;
490
+ if (this.outputBuffer.length > 0) {
491
+ this.synchronizedWrite(this.outputBuffer.join(''));
492
+ this.outputBuffer = [];
493
+ }
494
+ this.cancelSlowFlush = undefined;
495
+ resolve();
496
+ }
497
+ };
498
+ });
499
+ }
500
+ }
501
+ validateLinesConsistent(lines) {
502
+ if (this.isTainted) {
503
+ return;
504
+ }
505
+ for (let r = 0; r < this.rows; r++) {
506
+ const index = lines.length + r - this.rows;
507
+ if (index < 0) {
508
+ continue;
509
+ }
510
+ if (!linesEqual(this.screen[r]?.styledChars, lines[index]?.styledChars) &&
511
+ debugWorker) {
512
+ debugLog(`Line ${r} on screen inconsistent between terminalWriter and ground truth. Expected "${styledLineToString(lines[index]?.styledChars ?? new StyledLine())}", got "${styledLineToString(this.screen[r]?.styledChars ?? new StyledLine())}"`);
513
+ }
514
+ }
515
+ // Validated the backbuffer matches for lines 0 -> this.lines.length - this.rows
516
+ const backbufferLimit = lines.length - this.rows;
517
+ for (let i = 0; i < backbufferLimit; i++) {
518
+ if (!linesEqual(this.backbuffer[i]?.styledChars, lines[i]?.styledChars) &&
519
+ debugWorker) {
520
+ debugLog(`Line ${i} in backbuffer inconsistent. Expected "${styledLineToString(lines[i]?.styledChars ?? new StyledLine())}", got "${styledLineToString(this.backbuffer[i]?.styledChars ?? new StyledLine())}"`);
521
+ }
522
+ }
523
+ }
524
+ shiftScreenUp(start, bottom) {
525
+ for (let i = start; i < bottom - 1; i++) {
526
+ this.screen[i] = this.screen[i + 1];
527
+ }
528
+ this.screen[bottom - 1] = {
529
+ styledChars: new StyledLine(),
530
+ text: '',
531
+ length: 0,
532
+ tainted: false,
533
+ };
534
+ }
535
+ /**
536
+ * Trigger a scroll up of content into the backbuffer.
537
+ */
538
+ applyScrollUpBackbuffer(start, bottom) {
539
+ // Simulate the effect of adding a linebreak at the bottom of the scroll region.
540
+ this.moveCursor(bottom - 1, 0);
541
+ this.writeHelper('\n');
542
+ this.cursorX = -1;
543
+ this.cursorY = bottom - 1;
544
+ if (start === 0) {
545
+ this.backbuffer.push(this.screen[0]);
546
+ if (this.backbuffer.length > this.maxScrollbackLength) {
547
+ this.backbuffer.shift();
548
+ }
549
+ }
550
+ this.shiftScreenUp(start, bottom);
551
+ }
552
+ applyScrollUp(start, bottom) {
553
+ if (start === 0 && platform.isAppleTerminal() && bottom > 1) {
554
+ // Terminal.app doesn't respect scroll regions starting at line 0.
555
+ // Workaround: set scroll region to exclude line 0, do hardware scroll
556
+ // on lines 1+, then manually rewrite line 0.
557
+ const line0Content = this.screen[1];
558
+ // Set scroll region to lines 1 through bottom (excluding line 0)
559
+ this.writeHelper(getSetScrollRegionCode(1, bottom)); // 1-indexed: line 2 = index 1
560
+ this.scrollRegionTop = 1;
561
+ this.scrollRegionBottom = bottom;
562
+ // Now delete a line at line 1 (this will scroll within the region)
563
+ this.moveCursor(1, 0);
564
+ this.writeHelper(getDeleteLinesCode(1));
565
+ // Update screen state for lines 1 through bottom-1
566
+ this.shiftScreenUp(1, bottom);
567
+ // Mark line 0 as tainted so it gets redrawn with what was line 1
568
+ if (line0Content) {
569
+ this.screen[0] = { ...line0Content, tainted: true };
570
+ }
571
+ else {
572
+ this.screen[0] = {
573
+ styledChars: new StyledLine(),
574
+ text: '',
575
+ length: 0,
576
+ tainted: true,
577
+ };
578
+ }
579
+ return;
580
+ }
581
+ this.moveCursor(start, 0);
582
+ this.writeHelper(getDeleteLinesCode(1));
583
+ // Simulate the effect of the ansi escape for scroll up
584
+ this.shiftScreenUp(start, bottom);
585
+ }
586
+ applyScrollDown(start, bottom) {
587
+ this.moveCursor(start, 0);
588
+ this.writeHelper(getInsertLinesCode(1));
589
+ // Simulate the effect of the ansi escape for scroll up
590
+ for (let i = bottom - 1; i > start; i--) {
591
+ this.screen[i] = this.screen[i - 1];
592
+ }
593
+ this.screen[start] = {
594
+ styledChars: new StyledLine(),
595
+ text: '',
596
+ length: 0,
597
+ tainted: false,
598
+ };
599
+ }
600
+ performScroll(options) {
601
+ const { start, end, linesToScroll, lines, direction, scrollToBackbuffer } = options;
602
+ if (debugWorker) {
603
+ debugLog(`[terminal-writer] SCROLLING LINES ${start}-${end} by ${linesToScroll} ${direction}`);
604
+ }
605
+ this.setScrollRegion(start, end);
606
+ const scrollAreaHeight = end - start;
607
+ if (lines.length !== end - start + linesToScroll) {
608
+ throw new Error(`Mismatch in scrollLines: expected ${end - start + linesToScroll} lines, got ${lines.length}`);
609
+ }
610
+ if (scrollToBackbuffer && direction !== 'up') {
611
+ throw new Error(`scrollToBackbuffer is only supported for direction "up"`);
612
+ }
613
+ // Make sure the content on screen before scrolling really matches what is in lines.
614
+ // For 'up', existing content is at the start of 'lines'.
615
+ // For 'down', existing content is at the end of 'lines'.
616
+ const existingContentOffset = direction === 'up' ? 0 : linesToScroll;
617
+ for (let i = start; i < end; i++) {
618
+ this.syncLine(lines[existingContentOffset + i - start], i);
619
+ }
620
+ if (direction === 'up') {
621
+ for (let i = 0; i < linesToScroll; i++) {
622
+ if (scrollToBackbuffer) {
623
+ if (start > 0) {
624
+ // Case: Scrolling a region that doesn't start at row 0.
625
+ // To push row 0 to history, we MUST scroll the whole screen.
626
+ this.setScrollRegion(0, this.rows);
627
+ const savedHeader = this.screen.slice(0, start);
628
+ const savedFooter = this.screen.slice(end, this.rows);
629
+ this.syncLine(lines[i], 0);
630
+ this.applyScrollUpBackbuffer(0, this.rows);
631
+ for (const [k, element] of savedHeader.entries()) {
632
+ this.syncLine(element, k);
633
+ }
634
+ for (const [k, element] of savedFooter.entries()) {
635
+ this.syncLine(element, end + k);
636
+ }
637
+ }
638
+ else {
639
+ // Case: start === 0.
640
+ // We can push to history by scrolling the region from 0 to 'end'.
641
+ // This preserves everything below 'end' (e.g. the footer).
642
+ this.setScrollRegion(0, end);
643
+ // 1. Use the provided 'clean' line to replace the top row
644
+ this.syncLine(lines[i], 0);
645
+ this.applyScrollUpBackbuffer(0, end);
646
+ }
647
+ }
648
+ else {
649
+ this.applyScrollUp(start, end);
650
+ }
651
+ // Add the new line at the end after scrolling up the other lines
652
+ this.unkownCursorLocation();
653
+ this.syncLine(lines[i + scrollAreaHeight], end - 1);
654
+ }
655
+ this.finishChunkAndUpdateCursor();
656
+ }
657
+ else if (direction === 'down') {
658
+ for (let i = 0; i < linesToScroll; i++) {
659
+ const line = lines[linesToScroll - 1 - i];
660
+ this.applyScrollDown(start, end);
661
+ // Add the new line at the end after scrolling up the other lines
662
+ this.unkownCursorLocation();
663
+ this.syncLine(line, start);
664
+ }
665
+ this.finishChunkAndUpdateCursor();
666
+ }
667
+ }
668
+ synchronizedWrite(text) {
669
+ if (this.enableSynchronizedOutput) {
670
+ this.stdout.write(enterSynchronizedOutput + text + exitSynchronizedOutput);
671
+ }
672
+ else {
673
+ this.stdout.write(text);
674
+ }
675
+ }
676
+ resetScrollRegion() {
677
+ if (this.scrollRegionTop !== -1 || this.scrollRegionBottom !== -1) {
678
+ this.writeHelper(resetScrollRegion);
679
+ this.unkownCursorLocation();
680
+ this.scrollRegionTop = -1;
681
+ this.scrollRegionBottom = -1;
682
+ }
683
+ }
684
+ setScrollRegion(top, bottom) {
685
+ if (this.scrollRegionTop !== top || this.scrollRegionBottom !== bottom) {
686
+ this.writeHelper(getSetScrollRegionCode(top, bottom));
687
+ this.unkownCursorLocation();
688
+ this.scrollRegionTop = top;
689
+ this.scrollRegionBottom = bottom;
690
+ }
691
+ }
692
+ writeHelper(text) {
693
+ if (this.isDone) {
694
+ return;
695
+ }
696
+ this.currentChunkBuffer.push(text);
697
+ }
698
+ finishChunkAndUpdateCursor() {
699
+ if (this.targetCursorY >= 0 && this.targetCursorX >= 0) {
700
+ this.moveCursor(this.targetCursorY, this.targetCursorX);
701
+ }
702
+ if (this.currentChunkBuffer.length > 0) {
703
+ this.outputBuffer.push(this.currentChunkBuffer.join(''));
704
+ this.currentChunkBuffer = [];
705
+ }
706
+ }
707
+ }
708
+ //# sourceMappingURL=terminal-writer.js.map