@fleetagent/pi-tui 0.1.9 → 0.2.1

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 (109) hide show
  1. package/README.md +47 -7
  2. package/dist/autocomplete.d.ts +17 -23
  3. package/dist/autocomplete.d.ts.map +1 -1
  4. package/dist/autocomplete.js +129 -195
  5. package/dist/autocomplete.js.map +1 -1
  6. package/dist/components/alt-screen-flash.d.ts +13 -0
  7. package/dist/components/alt-screen-flash.d.ts.map +1 -0
  8. package/dist/components/alt-screen-flash.js +37 -0
  9. package/dist/components/alt-screen-flash.js.map +1 -0
  10. package/dist/components/editor.d.ts +47 -4
  11. package/dist/components/editor.d.ts.map +1 -1
  12. package/dist/components/editor.js +615 -585
  13. package/dist/components/editor.js.map +1 -1
  14. package/dist/components/h-stack.d.ts +7 -0
  15. package/dist/components/h-stack.d.ts.map +1 -0
  16. package/dist/components/h-stack.js +43 -0
  17. package/dist/components/h-stack.js.map +1 -0
  18. package/dist/components/image.d.ts +4 -0
  19. package/dist/components/image.d.ts.map +1 -1
  20. package/dist/components/image.js +38 -51
  21. package/dist/components/image.js.map +1 -1
  22. package/dist/components/input.d.ts +9 -0
  23. package/dist/components/input.d.ts.map +1 -1
  24. package/dist/components/input.js +121 -147
  25. package/dist/components/input.js.map +1 -1
  26. package/dist/components/markdown.d.ts +34 -0
  27. package/dist/components/markdown.d.ts.map +1 -1
  28. package/dist/components/markdown.js +491 -429
  29. package/dist/components/markdown.js.map +1 -1
  30. package/dist/components/scroll-view.d.ts +53 -0
  31. package/dist/components/scroll-view.d.ts.map +1 -0
  32. package/dist/components/scroll-view.js +173 -0
  33. package/dist/components/scroll-view.js.map +1 -0
  34. package/dist/components/select-list.d.ts.map +1 -1
  35. package/dist/components/select-list.js.map +1 -1
  36. package/dist/components/settings-list.d.ts +2 -0
  37. package/dist/components/settings-list.d.ts.map +1 -1
  38. package/dist/components/settings-list.js +31 -29
  39. package/dist/components/settings-list.js.map +1 -1
  40. package/dist/components/stack.d.ts +32 -0
  41. package/dist/components/stack.d.ts.map +1 -0
  42. package/dist/components/stack.js +122 -0
  43. package/dist/components/stack.js.map +1 -0
  44. package/dist/components/v-stack.d.ts +8 -0
  45. package/dist/components/v-stack.d.ts.map +1 -0
  46. package/dist/components/v-stack.js +27 -0
  47. package/dist/components/v-stack.js.map +1 -0
  48. package/dist/fuzzy.d.ts.map +1 -1
  49. package/dist/fuzzy.js +37 -46
  50. package/dist/fuzzy.js.map +1 -1
  51. package/dist/index.d.ts +10 -3
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +11 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/keybindings.d.ts +54 -4
  56. package/dist/keybindings.d.ts.map +1 -1
  57. package/dist/keybindings.js +39 -4
  58. package/dist/keybindings.js.map +1 -1
  59. package/dist/keys.d.ts.map +1 -1
  60. package/dist/keys.js +359 -479
  61. package/dist/keys.js.map +1 -1
  62. package/dist/kill-ring.d.ts +6 -4
  63. package/dist/kill-ring.d.ts.map +1 -1
  64. package/dist/kill-ring.js +0 -7
  65. package/dist/kill-ring.js.map +1 -1
  66. package/dist/latex.d.ts +10 -0
  67. package/dist/latex.d.ts.map +1 -0
  68. package/dist/latex.js +1406 -0
  69. package/dist/latex.js.map +1 -0
  70. package/dist/layout-node.d.ts +43 -0
  71. package/dist/layout-node.d.ts.map +1 -0
  72. package/dist/layout-node.js +6 -0
  73. package/dist/layout-node.js.map +1 -0
  74. package/dist/layout.d.ts +40 -0
  75. package/dist/layout.d.ts.map +1 -0
  76. package/dist/layout.js +386 -0
  77. package/dist/layout.js.map +1 -0
  78. package/dist/stdin-buffer.d.ts +8 -0
  79. package/dist/stdin-buffer.d.ts.map +1 -1
  80. package/dist/stdin-buffer.js +116 -159
  81. package/dist/stdin-buffer.js.map +1 -1
  82. package/dist/terminal-image.d.ts +41 -19
  83. package/dist/terminal-image.d.ts.map +1 -1
  84. package/dist/terminal-image.js +171 -32
  85. package/dist/terminal-image.js.map +1 -1
  86. package/dist/terminal.d.ts +3 -0
  87. package/dist/terminal.d.ts.map +1 -1
  88. package/dist/terminal.js +27 -23
  89. package/dist/terminal.js.map +1 -1
  90. package/dist/tui-alt-screen.d.ts +114 -0
  91. package/dist/tui-alt-screen.d.ts.map +1 -0
  92. package/dist/tui-alt-screen.js +950 -0
  93. package/dist/tui-alt-screen.js.map +1 -0
  94. package/dist/tui-main-screen.d.ts +52 -0
  95. package/dist/tui-main-screen.d.ts.map +1 -0
  96. package/dist/tui-main-screen.js +511 -0
  97. package/dist/tui-main-screen.js.map +1 -0
  98. package/dist/tui.d.ts +112 -44
  99. package/dist/tui.d.ts.map +1 -1
  100. package/dist/tui.js +489 -699
  101. package/dist/tui.js.map +1 -1
  102. package/dist/utils.d.ts +29 -17
  103. package/dist/utils.d.ts.map +1 -1
  104. package/dist/utils.js +605 -528
  105. package/dist/utils.js.map +1 -1
  106. package/dist/word-navigation.d.ts.map +1 -1
  107. package/dist/word-navigation.js +37 -39
  108. package/dist/word-navigation.js.map +1 -1
  109. package/package.json +1 -1
@@ -0,0 +1,511 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { deleteKittyImage, isImageLine } from "./terminal-image.js";
4
+ import { TuiBase } from "./tui.js";
5
+ import { visibleWidth } from "./utils.js";
6
+ const KITTY_SEQUENCE_PREFIX = "\x1b_G";
7
+ function parseKittyImageHeader(line) {
8
+ const sequenceStart = line.indexOf(KITTY_SEQUENCE_PREFIX);
9
+ if (sequenceStart === -1)
10
+ return undefined;
11
+ const paramsStart = sequenceStart + KITTY_SEQUENCE_PREFIX.length;
12
+ const paramsEnd = line.indexOf(";", paramsStart);
13
+ if (paramsEnd === -1)
14
+ return undefined;
15
+ const ids = [];
16
+ let rows = 1;
17
+ for (const param of line.slice(paramsStart, paramsEnd).split(",")) {
18
+ const [key, value] = param.split("=", 2);
19
+ if (value === undefined)
20
+ continue;
21
+ const numberValue = Number(value);
22
+ if (!Number.isInteger(numberValue) || numberValue <= 0 || numberValue > 0xffffffff)
23
+ continue;
24
+ if (key === "i")
25
+ ids.push(numberValue);
26
+ else if (key === "r")
27
+ rows = numberValue;
28
+ }
29
+ return { ids, rows };
30
+ }
31
+ function extractKittyImageIds(line) {
32
+ return parseKittyImageHeader(line)?.ids ?? [];
33
+ }
34
+ function extractKittyImageRows(line) {
35
+ return parseKittyImageHeader(line)?.rows ?? 1;
36
+ }
37
+ function isTermuxSession() {
38
+ return Boolean(process.env.TERMUX_VERSION);
39
+ }
40
+ /** TUI implementation that renders into the terminal's main screen and scrollback. */
41
+ export class TuiMainScreen extends TuiBase {
42
+ mode = "regular";
43
+ previousLines = [];
44
+ previousKittyImageIds = new Set();
45
+ previousWidth = 0;
46
+ previousHeight = 0;
47
+ cursorRow = 0;
48
+ hardwareCursorRow = 0;
49
+ maxLinesRendered = 0;
50
+ previousViewportTop = 0;
51
+ captureRenderState() {
52
+ return {
53
+ previousLines: [...this.previousLines],
54
+ previousWidth: this.previousWidth,
55
+ previousHeight: this.previousHeight,
56
+ cursorRow: this.cursorRow,
57
+ hardwareCursorRow: this.hardwareCursorRow,
58
+ maxLinesRendered: this.maxLinesRendered,
59
+ previousViewportTop: this.previousViewportTop,
60
+ };
61
+ }
62
+ restoreRenderState(state) {
63
+ this.previousLines = state.previousLines.map((line) => (isImageLine(line) ? "" : line));
64
+ this.previousKittyImageIds = new Set();
65
+ this.previousWidth = state.previousWidth;
66
+ this.previousHeight = state.previousHeight;
67
+ this.cursorRow = state.cursorRow;
68
+ this.hardwareCursorRow = state.hardwareCursorRow;
69
+ this.maxLinesRendered = state.maxLinesRendered;
70
+ this.previousViewportTop = state.previousViewportTop;
71
+ }
72
+ resetRenderState() {
73
+ this.previousLines = [];
74
+ this.previousWidth = -1;
75
+ this.previousHeight = -1;
76
+ this.cursorRow = 0;
77
+ this.hardwareCursorRow = 0;
78
+ this.maxLinesRendered = 0;
79
+ this.previousViewportTop = 0;
80
+ }
81
+ beforeTerminalStop(options) {
82
+ if (options?.preserveScreen || this.previousLines.length === 0)
83
+ return;
84
+ this.terminal.write(" ");
85
+ const targetRow = this.previousLines.length;
86
+ const lineDiff = targetRow - this.hardwareCursorRow;
87
+ if (lineDiff > 0)
88
+ this.terminal.write(`\x1b[${lineDiff}B`);
89
+ else if (lineDiff < 0)
90
+ this.terminal.write(`\x1b[${-lineDiff}A`);
91
+ this.terminal.write("\r\n");
92
+ }
93
+ collectKittyImageIds(lines) {
94
+ const ids = new Set();
95
+ for (const line of lines) {
96
+ for (const id of extractKittyImageIds(line)) {
97
+ ids.add(id);
98
+ }
99
+ }
100
+ return ids;
101
+ }
102
+ deleteKittyImages(ids) {
103
+ let buffer = "";
104
+ for (const id of ids) {
105
+ buffer += deleteKittyImage(id);
106
+ }
107
+ return buffer;
108
+ }
109
+ getKittyImageReservedRows(lines, index, maxIndex = lines.length - 1) {
110
+ const rows = extractKittyImageRows(lines[index] ?? "");
111
+ if (rows <= 1)
112
+ return 1;
113
+ const maxRows = Math.min(rows, maxIndex - index + 1, lines.length - index);
114
+ let reservedRows = 1;
115
+ while (reservedRows < maxRows) {
116
+ const line = lines[index + reservedRows] ?? "";
117
+ if (isImageLine(line) || visibleWidth(line) > 0)
118
+ break;
119
+ reservedRows++;
120
+ }
121
+ return reservedRows;
122
+ }
123
+ expandChangedRangeForKittyImages(firstChanged, lastChanged, newLines) {
124
+ let expandedFirstChanged = firstChanged;
125
+ let expandedLastChanged = lastChanged;
126
+ const expandForLines = (lines) => {
127
+ for (let i = 0; i < lines.length; i++) {
128
+ if (extractKittyImageIds(lines[i]).length === 0)
129
+ continue;
130
+ const blockEnd = i + this.getKittyImageReservedRows(lines, i) - 1;
131
+ if (i >= firstChanged || (i <= lastChanged && blockEnd >= firstChanged)) {
132
+ expandedFirstChanged = Math.min(expandedFirstChanged, i);
133
+ expandedLastChanged = Math.max(expandedLastChanged, blockEnd);
134
+ }
135
+ }
136
+ };
137
+ expandForLines(this.previousLines);
138
+ expandForLines(newLines);
139
+ return { firstChanged: expandedFirstChanged, lastChanged: expandedLastChanged };
140
+ }
141
+ deleteChangedKittyImages(firstChanged, lastChanged) {
142
+ if (firstChanged < 0 || lastChanged < firstChanged)
143
+ return "";
144
+ const ids = new Set();
145
+ const maxLine = Math.min(lastChanged, this.previousLines.length - 1);
146
+ for (let i = firstChanged; i <= maxLine; i++) {
147
+ for (const id of extractKittyImageIds(this.previousLines[i] ?? "")) {
148
+ ids.add(id);
149
+ }
150
+ }
151
+ return this.deleteKittyImages(ids);
152
+ }
153
+ prepareRenderFrame() {
154
+ const width = this.terminal.columns;
155
+ const height = this.terminal.rows;
156
+ const widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;
157
+ const heightChanged = this.previousHeight !== 0 && this.previousHeight !== height;
158
+ const previousBufferLength = this.previousHeight > 0 ? this.previousViewportTop + this.previousHeight : height;
159
+ const previousViewportTop = heightChanged ? Math.max(0, previousBufferLength - height) : this.previousViewportTop;
160
+ let newLines = this.render(width);
161
+ if (this.hasOverlayEntries)
162
+ newLines = this.compositeOverlays(newLines, width, height);
163
+ const cursorPosition = this.extractCursorPosition(newLines, height);
164
+ newLines = this.applyLineResets(newLines);
165
+ return { width, height, widthChanged, heightChanged, newLines, cursorPosition, previousViewportTop };
166
+ }
167
+ getLineDiff(targetRow, hardwareCursorRow, previousViewportTop, viewportTop) {
168
+ const currentScreenRow = hardwareCursorRow - previousViewportTop;
169
+ const targetScreenRow = targetRow - viewportTop;
170
+ return targetScreenRow - currentScreenRow;
171
+ }
172
+ logFullRedraw(frame, reason) {
173
+ if (process.env.PI_DEBUG_REDRAW !== "1")
174
+ return;
175
+ const logPath = path.join(this.logDirectory, "pi-debug.log");
176
+ const message = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.previousLines.length}, new=${frame.newLines.length}, height=${frame.height})\n`;
177
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
178
+ fs.appendFileSync(logPath, message);
179
+ }
180
+ renderFullFrame(frame, clear) {
181
+ this.fullRedrawCount += 1;
182
+ let buffer = "\x1b[?2026h";
183
+ if (clear) {
184
+ buffer += this.deleteKittyImages(this.previousKittyImageIds);
185
+ buffer += "\x1b[2J\x1b[H\x1b[3J";
186
+ }
187
+ for (let index = 0; index < frame.newLines.length; index++) {
188
+ if (index > 0)
189
+ buffer += "\r\n";
190
+ const line = frame.newLines[index];
191
+ const reservedRows = isImageLine(line) ? this.getKittyImageReservedRows(frame.newLines, index) : 1;
192
+ if (reservedRows > 1 && reservedRows <= frame.height) {
193
+ buffer += "\r\n".repeat(reservedRows - 1);
194
+ buffer += `\x1b[${reservedRows - 1}A${line}\x1b[${reservedRows - 1}B`;
195
+ index += reservedRows - 1;
196
+ continue;
197
+ }
198
+ buffer += line;
199
+ }
200
+ buffer += "\x1b[?2026l";
201
+ this.terminal.write(buffer);
202
+ this.cursorRow = Math.max(0, frame.newLines.length - 1);
203
+ this.hardwareCursorRow = this.cursorRow;
204
+ this.maxLinesRendered = clear ? frame.newLines.length : Math.max(this.maxLinesRendered, frame.newLines.length);
205
+ const bufferLength = Math.max(frame.height, frame.newLines.length);
206
+ this.previousViewportTop = Math.max(0, bufferLength - frame.height);
207
+ this.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);
208
+ this.previousLines = frame.newLines;
209
+ this.previousKittyImageIds = this.collectKittyImageIds(frame.newLines);
210
+ this.previousWidth = frame.width;
211
+ this.previousHeight = frame.height;
212
+ }
213
+ renderRequiredFullFrame(frame) {
214
+ if (this.previousLines.length === 0 && !frame.widthChanged && !frame.heightChanged) {
215
+ this.logFullRedraw(frame, "first render");
216
+ this.renderFullFrame(frame, false);
217
+ return true;
218
+ }
219
+ if (frame.widthChanged) {
220
+ this.logFullRedraw(frame, `terminal width changed (${this.previousWidth} -> ${frame.width})`);
221
+ this.renderFullFrame(frame, true);
222
+ return true;
223
+ }
224
+ if (frame.heightChanged && !isTermuxSession()) {
225
+ this.logFullRedraw(frame, `terminal height changed (${this.previousHeight} -> ${frame.height})`);
226
+ this.renderFullFrame(frame, true);
227
+ return true;
228
+ }
229
+ if (this.getClearOnShrink() && frame.newLines.length < this.maxLinesRendered && !this.hasOverlayEntries) {
230
+ this.logFullRedraw(frame, `clearOnShrink (maxLinesRendered=${this.maxLinesRendered})`);
231
+ this.renderFullFrame(frame, true);
232
+ return true;
233
+ }
234
+ return false;
235
+ }
236
+ findChangedContent(frame) {
237
+ let firstChanged = -1;
238
+ let lastChanged = -1;
239
+ const maxLines = Math.max(frame.newLines.length, this.previousLines.length);
240
+ for (let index = 0; index < maxLines; index++) {
241
+ const oldLine = index < this.previousLines.length ? this.previousLines[index] : "";
242
+ const newLine = index < frame.newLines.length ? frame.newLines[index] : "";
243
+ if (oldLine === newLine)
244
+ continue;
245
+ if (firstChanged === -1)
246
+ firstChanged = index;
247
+ lastChanged = index;
248
+ }
249
+ const appendedLines = frame.newLines.length > this.previousLines.length;
250
+ if (appendedLines) {
251
+ if (firstChanged === -1)
252
+ firstChanged = this.previousLines.length;
253
+ lastChanged = frame.newLines.length - 1;
254
+ }
255
+ if (firstChanged === -1)
256
+ return undefined;
257
+ const expanded = this.expandChangedRangeForKittyImages(firstChanged, lastChanged, frame.newLines);
258
+ return {
259
+ ...expanded,
260
+ appendStart: appendedLines && expanded.firstChanged === this.previousLines.length && expanded.firstChanged > 0,
261
+ };
262
+ }
263
+ commitFrameHistory(frame, viewportTop) {
264
+ this.previousLines = frame.newLines;
265
+ this.previousKittyImageIds = this.collectKittyImageIds(frame.newLines);
266
+ this.previousWidth = frame.width;
267
+ this.previousHeight = frame.height;
268
+ this.previousViewportTop = viewportTop;
269
+ }
270
+ handleUnchangedFrame(frame) {
271
+ this.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);
272
+ this.previousViewportTop = frame.previousViewportTop;
273
+ this.previousHeight = frame.height;
274
+ }
275
+ renderDeletedContent(frame, range) {
276
+ if (this.previousLines.length <= frame.newLines.length) {
277
+ this.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);
278
+ this.commitFrameHistory(frame, frame.previousViewportTop);
279
+ return;
280
+ }
281
+ let buffer = "\x1b[?2026h";
282
+ buffer += this.deleteChangedKittyImages(range.firstChanged, range.lastChanged);
283
+ const targetRow = Math.max(0, frame.newLines.length - 1);
284
+ if (targetRow < frame.previousViewportTop) {
285
+ this.logFullRedraw(frame, `deleted lines moved viewport up (${targetRow} < ${frame.previousViewportTop})`);
286
+ this.renderFullFrame(frame, true);
287
+ return;
288
+ }
289
+ const lineDiff = this.getLineDiff(targetRow, this.hardwareCursorRow, frame.previousViewportTop, frame.previousViewportTop);
290
+ if (lineDiff > 0)
291
+ buffer += `\x1b[${lineDiff}B`;
292
+ else if (lineDiff < 0)
293
+ buffer += `\x1b[${-lineDiff}A`;
294
+ buffer += "\r";
295
+ const extraLines = this.previousLines.length - frame.newLines.length;
296
+ if (extraLines > frame.height) {
297
+ this.logFullRedraw(frame, `extraLines > height (${extraLines} > ${frame.height})`);
298
+ this.renderFullFrame(frame, true);
299
+ return;
300
+ }
301
+ const clearStartOffset = frame.newLines.length === 0 ? 0 : 1;
302
+ if (extraLines > 0 && clearStartOffset > 0)
303
+ buffer += `\x1b[${clearStartOffset}B`;
304
+ for (let index = 0; index < extraLines; index++) {
305
+ buffer += "\r\x1b[2K";
306
+ if (index < extraLines - 1)
307
+ buffer += "\x1b[1B";
308
+ }
309
+ const moveBack = Math.max(0, extraLines - 1 + clearStartOffset);
310
+ if (moveBack > 0)
311
+ buffer += `\x1b[${moveBack}A`;
312
+ buffer += "\x1b[?2026l";
313
+ this.terminal.write(buffer);
314
+ this.cursorRow = targetRow;
315
+ this.hardwareCursorRow = targetRow;
316
+ this.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);
317
+ this.commitFrameHistory(frame, frame.previousViewportTop);
318
+ }
319
+ assertRenderedLineFits(line, index, frame) {
320
+ if (isImageLine(line) || visibleWidth(line) <= frame.width)
321
+ return;
322
+ const crashLogPath = path.join(this.logDirectory, "pi-crash.log");
323
+ const crashData = [
324
+ `Crash at ${new Date().toISOString()}`,
325
+ `Terminal width: ${frame.width}`,
326
+ `Line ${index} visible width: ${visibleWidth(line)}`,
327
+ "",
328
+ "=== All rendered lines ===",
329
+ ...frame.newLines.map((renderedLine, lineIndex) => `[${lineIndex}] (w=${visibleWidth(renderedLine)}) ${renderedLine}`),
330
+ "",
331
+ ].join("\n");
332
+ fs.mkdirSync(path.dirname(crashLogPath), { recursive: true });
333
+ fs.writeFileSync(crashLogPath, crashData);
334
+ this.stop();
335
+ throw new Error([
336
+ `Rendered line ${index} exceeds terminal width (${visibleWidth(line)} > ${frame.width}).`,
337
+ "",
338
+ "This is likely caused by a custom TUI component not truncating its output.",
339
+ "Use visibleWidth() to measure and truncateToWidth() to truncate lines.",
340
+ "",
341
+ `Debug log written to: ${crashLogPath}`,
342
+ ].join("\n"));
343
+ }
344
+ renderChangedLines(frame, range, viewportTop, initialBuffer) {
345
+ let buffer = initialBuffer;
346
+ const renderEnd = Math.min(range.lastChanged, frame.newLines.length - 1);
347
+ for (let index = range.firstChanged; index <= renderEnd; index++) {
348
+ if (index > range.firstChanged)
349
+ buffer += "\r\n";
350
+ const line = frame.newLines[index];
351
+ const reservedRows = isImageLine(line) ? this.getKittyImageReservedRows(frame.newLines, index, renderEnd) : 1;
352
+ if (reservedRows > 1) {
353
+ const imageStartScreenRow = index - viewportTop;
354
+ if (imageStartScreenRow < 0 || imageStartScreenRow + reservedRows > frame.height) {
355
+ this.logFullRedraw(frame, `kitty image pre-clear would scroll (${imageStartScreenRow} + ${reservedRows} > ${frame.height})`);
356
+ this.renderFullFrame(frame, true);
357
+ return undefined;
358
+ }
359
+ buffer += "\x1b[2K";
360
+ for (let row = 1; row < reservedRows; row++)
361
+ buffer += "\r\n\x1b[2K";
362
+ buffer += `\x1b[${reservedRows - 1}A${line}\x1b[${reservedRows - 1}B`;
363
+ index += reservedRows - 1;
364
+ continue;
365
+ }
366
+ buffer += "\x1b[2K";
367
+ this.assertRenderedLineFits(line, index, frame);
368
+ buffer += line;
369
+ }
370
+ return { buffer, renderEnd };
371
+ }
372
+ writeDifferentialDebugLog(frame, range, viewportTop, hardwareCursorRow, lineDiff, renderEnd, finalCursorRow, buffer) {
373
+ if (process.env.PI_TUI_DEBUG !== "1")
374
+ return;
375
+ const debugDir = "/tmp/tui";
376
+ fs.mkdirSync(debugDir, { recursive: true });
377
+ const debugPath = path.join(debugDir, `render-${Date.now()}-${Math.random().toString(36).slice(2)}.log`);
378
+ const debugData = [
379
+ `firstChanged: ${range.firstChanged}`,
380
+ `viewportTop: ${viewportTop}`,
381
+ `cursorRow: ${this.cursorRow}`,
382
+ `height: ${frame.height}`,
383
+ `lineDiff: ${lineDiff}`,
384
+ `hardwareCursorRow: ${hardwareCursorRow}`,
385
+ `renderEnd: ${renderEnd}`,
386
+ `finalCursorRow: ${finalCursorRow}`,
387
+ `cursorPos: ${JSON.stringify(frame.cursorPosition)}`,
388
+ `newLines.length: ${frame.newLines.length}`,
389
+ `previousLines.length: ${this.previousLines.length}`,
390
+ "",
391
+ "=== newLines ===",
392
+ JSON.stringify(frame.newLines, null, 2),
393
+ "",
394
+ "=== previousLines ===",
395
+ JSON.stringify(this.previousLines, null, 2),
396
+ "",
397
+ "=== buffer ===",
398
+ JSON.stringify(buffer),
399
+ ].join("\n");
400
+ fs.writeFileSync(debugPath, debugData);
401
+ }
402
+ renderDifferentialContent(frame, range) {
403
+ if (range.firstChanged < frame.previousViewportTop) {
404
+ this.logFullRedraw(frame, `firstChanged < viewportTop (${range.firstChanged} < ${frame.previousViewportTop})`);
405
+ this.renderFullFrame(frame, true);
406
+ return;
407
+ }
408
+ let previousViewportTop = frame.previousViewportTop;
409
+ let viewportTop = previousViewportTop;
410
+ let hardwareCursorRow = this.hardwareCursorRow;
411
+ let buffer = `\x1b[?2026h${this.deleteChangedKittyImages(range.firstChanged, range.lastChanged)}`;
412
+ const previousViewportBottom = previousViewportTop + frame.height - 1;
413
+ const moveTargetRow = range.appendStart ? range.firstChanged - 1 : range.firstChanged;
414
+ if (moveTargetRow > previousViewportBottom) {
415
+ const currentScreenRow = Math.max(0, Math.min(frame.height - 1, hardwareCursorRow - previousViewportTop));
416
+ const moveToBottom = frame.height - 1 - currentScreenRow;
417
+ if (moveToBottom > 0)
418
+ buffer += `\x1b[${moveToBottom}B`;
419
+ const scroll = moveTargetRow - previousViewportBottom;
420
+ buffer += "\r\n".repeat(scroll);
421
+ previousViewportTop += scroll;
422
+ viewportTop += scroll;
423
+ hardwareCursorRow = moveTargetRow;
424
+ }
425
+ const lineDiff = this.getLineDiff(moveTargetRow, hardwareCursorRow, previousViewportTop, viewportTop);
426
+ if (lineDiff > 0)
427
+ buffer += `\x1b[${lineDiff}B`;
428
+ else if (lineDiff < 0)
429
+ buffer += `\x1b[${-lineDiff}A`;
430
+ buffer += range.appendStart ? "\r\n" : "\r";
431
+ const changedLines = this.renderChangedLines(frame, range, viewportTop, buffer);
432
+ if (!changedLines)
433
+ return;
434
+ buffer = changedLines.buffer;
435
+ let finalCursorRow = changedLines.renderEnd;
436
+ if (this.previousLines.length > frame.newLines.length) {
437
+ if (changedLines.renderEnd < frame.newLines.length - 1) {
438
+ const moveDown = frame.newLines.length - 1 - changedLines.renderEnd;
439
+ buffer += `\x1b[${moveDown}B`;
440
+ finalCursorRow = frame.newLines.length - 1;
441
+ }
442
+ const extraLines = this.previousLines.length - frame.newLines.length;
443
+ for (let index = frame.newLines.length; index < this.previousLines.length; index++) {
444
+ buffer += "\r\n\x1b[2K";
445
+ }
446
+ buffer += `\x1b[${extraLines}A`;
447
+ }
448
+ buffer += "\x1b[?2026l";
449
+ this.writeDifferentialDebugLog(frame, range, viewportTop, hardwareCursorRow, lineDiff, changedLines.renderEnd, finalCursorRow, buffer);
450
+ this.terminal.write(buffer);
451
+ this.cursorRow = Math.max(0, frame.newLines.length - 1);
452
+ this.hardwareCursorRow = finalCursorRow;
453
+ this.maxLinesRendered = Math.max(this.maxLinesRendered, frame.newLines.length);
454
+ const committedViewportTop = Math.max(previousViewportTop, finalCursorRow - frame.height + 1);
455
+ this.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);
456
+ this.commitFrameHistory(frame, committedViewportTop);
457
+ }
458
+ doRender() {
459
+ if (this.stopped)
460
+ return;
461
+ const frame = this.prepareRenderFrame();
462
+ if (this.renderRequiredFullFrame(frame))
463
+ return;
464
+ const changedContent = this.findChangedContent(frame);
465
+ if (!changedContent) {
466
+ this.handleUnchangedFrame(frame);
467
+ return;
468
+ }
469
+ if (changedContent.firstChanged >= frame.newLines.length) {
470
+ this.renderDeletedContent(frame, changedContent);
471
+ return;
472
+ }
473
+ this.renderDifferentialContent(frame, changedContent);
474
+ }
475
+ /**
476
+ * Position the hardware cursor for IME candidate window.
477
+ * @param cursorPos The cursor position extracted from rendered output, or null
478
+ * @param totalLines Total number of rendered lines
479
+ */
480
+ positionHardwareCursor(cursorPos, totalLines) {
481
+ if (!cursorPos || totalLines <= 0) {
482
+ this.terminal.hideCursor();
483
+ return;
484
+ }
485
+ // Clamp cursor position to valid range
486
+ const targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1));
487
+ const targetCol = Math.max(0, cursorPos.col);
488
+ // Move cursor from current position to target
489
+ const rowDelta = targetRow - this.hardwareCursorRow;
490
+ let buffer = "";
491
+ if (rowDelta > 0) {
492
+ buffer += `\x1b[${rowDelta}B`; // Move down
493
+ }
494
+ else if (rowDelta < 0) {
495
+ buffer += `\x1b[${-rowDelta}A`; // Move up
496
+ }
497
+ // Move to absolute column (1-indexed)
498
+ buffer += `\x1b[${targetCol + 1}G`;
499
+ if (buffer) {
500
+ this.terminal.write(buffer);
501
+ }
502
+ this.hardwareCursorRow = targetRow;
503
+ if (this.getShowHardwareCursor()) {
504
+ this.terminal.showCursor();
505
+ }
506
+ else {
507
+ this.terminal.hideCursor();
508
+ }
509
+ }
510
+ }
511
+ //# sourceMappingURL=tui-main-screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-main-screen.js","sourceRoot":"","sources":["../src/tui-main-screen.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { deleteKittyImage, isImageLine } from \"./terminal-image.ts\";\nimport { type RenderedCursorPosition, type TUI, TuiBase, type TuiStopOptions } from \"./tui.ts\";\nimport { visibleWidth } from \"./utils.ts\";\n\nconst KITTY_SEQUENCE_PREFIX = \"\\x1b_G\";\n\ninterface KittyImageHeader {\n\tids: number[];\n\trows: number;\n}\n\ninterface ChangedLineRange {\n\tfirstChanged: number;\n\tlastChanged: number;\n}\n\ninterface ChangedContentRange extends ChangedLineRange {\n\tappendStart: boolean;\n}\n\ninterface MainScreenRenderFrame {\n\twidth: number;\n\theight: number;\n\twidthChanged: boolean;\n\theightChanged: boolean;\n\tnewLines: string[];\n\tcursorPosition: RenderedCursorPosition | null;\n\tpreviousViewportTop: number;\n}\n\ninterface ChangedLinesRenderResult {\n\tbuffer: string;\n\trenderEnd: number;\n}\n\nfunction parseKittyImageHeader(line: string): KittyImageHeader | undefined {\n\tconst sequenceStart = line.indexOf(KITTY_SEQUENCE_PREFIX);\n\tif (sequenceStart === -1) return undefined;\n\tconst paramsStart = sequenceStart + KITTY_SEQUENCE_PREFIX.length;\n\tconst paramsEnd = line.indexOf(\";\", paramsStart);\n\tif (paramsEnd === -1) return undefined;\n\n\tconst ids: number[] = [];\n\tlet rows = 1;\n\tfor (const param of line.slice(paramsStart, paramsEnd).split(\",\")) {\n\t\tconst [key, value] = param.split(\"=\", 2);\n\t\tif (value === undefined) continue;\n\t\tconst numberValue = Number(value);\n\t\tif (!Number.isInteger(numberValue) || numberValue <= 0 || numberValue > 0xffffffff) continue;\n\t\tif (key === \"i\") ids.push(numberValue);\n\t\telse if (key === \"r\") rows = numberValue;\n\t}\n\treturn { ids, rows };\n}\n\nfunction extractKittyImageIds(line: string): number[] {\n\treturn parseKittyImageHeader(line)?.ids ?? [];\n}\n\nfunction extractKittyImageRows(line: string): number {\n\treturn parseKittyImageHeader(line)?.rows ?? 1;\n}\n\nfunction isTermuxSession(): boolean {\n\treturn Boolean(process.env.TERMUX_VERSION);\n}\n\nexport interface TuiMainScreenRenderState {\n\tpreviousLines: string[];\n\tpreviousWidth: number;\n\tpreviousHeight: number;\n\tcursorRow: number;\n\thardwareCursorRow: number;\n\tmaxLinesRendered: number;\n\tpreviousViewportTop: number;\n}\n\n/** TUI implementation that renders into the terminal's main screen and scrollback. */\nexport class TuiMainScreen extends TuiBase implements TUI {\n\treadonly mode = \"regular\" as const;\n\tprivate previousLines: string[] = [];\n\tprivate previousKittyImageIds = new Set<number>();\n\tprivate previousWidth = 0;\n\tprivate previousHeight = 0;\n\tprivate cursorRow = 0;\n\tprivate hardwareCursorRow = 0;\n\tprivate maxLinesRendered = 0;\n\tprivate previousViewportTop = 0;\n\n\tcaptureRenderState(): TuiMainScreenRenderState {\n\t\treturn {\n\t\t\tpreviousLines: [...this.previousLines],\n\t\t\tpreviousWidth: this.previousWidth,\n\t\t\tpreviousHeight: this.previousHeight,\n\t\t\tcursorRow: this.cursorRow,\n\t\t\thardwareCursorRow: this.hardwareCursorRow,\n\t\t\tmaxLinesRendered: this.maxLinesRendered,\n\t\t\tpreviousViewportTop: this.previousViewportTop,\n\t\t};\n\t}\n\n\trestoreRenderState(state: TuiMainScreenRenderState): void {\n\t\tthis.previousLines = state.previousLines.map((line) => (isImageLine(line) ? \"\" : line));\n\t\tthis.previousKittyImageIds = new Set();\n\t\tthis.previousWidth = state.previousWidth;\n\t\tthis.previousHeight = state.previousHeight;\n\t\tthis.cursorRow = state.cursorRow;\n\t\tthis.hardwareCursorRow = state.hardwareCursorRow;\n\t\tthis.maxLinesRendered = state.maxLinesRendered;\n\t\tthis.previousViewportTop = state.previousViewportTop;\n\t}\n\n\tprotected override resetRenderState(): void {\n\t\tthis.previousLines = [];\n\t\tthis.previousWidth = -1;\n\t\tthis.previousHeight = -1;\n\t\tthis.cursorRow = 0;\n\t\tthis.hardwareCursorRow = 0;\n\t\tthis.maxLinesRendered = 0;\n\t\tthis.previousViewportTop = 0;\n\t}\n\n\tprotected override beforeTerminalStop(options?: TuiStopOptions): void {\n\t\tif (options?.preserveScreen || this.previousLines.length === 0) return;\n\t\tthis.terminal.write(\" \");\n\t\tconst targetRow = this.previousLines.length;\n\t\tconst lineDiff = targetRow - this.hardwareCursorRow;\n\t\tif (lineDiff > 0) this.terminal.write(`\\x1b[${lineDiff}B`);\n\t\telse if (lineDiff < 0) this.terminal.write(`\\x1b[${-lineDiff}A`);\n\t\tthis.terminal.write(\"\\r\\n\");\n\t}\n\n\tprivate collectKittyImageIds(lines: string[]): Set<number> {\n\t\tconst ids = new Set<number>();\n\t\tfor (const line of lines) {\n\t\t\tfor (const id of extractKittyImageIds(line)) {\n\t\t\t\tids.add(id);\n\t\t\t}\n\t\t}\n\t\treturn ids;\n\t}\n\n\tprivate deleteKittyImages(ids: Iterable<number>): string {\n\t\tlet buffer = \"\";\n\t\tfor (const id of ids) {\n\t\t\tbuffer += deleteKittyImage(id);\n\t\t}\n\t\treturn buffer;\n\t}\n\n\tprivate getKittyImageReservedRows(lines: string[], index: number, maxIndex = lines.length - 1): number {\n\t\tconst rows = extractKittyImageRows(lines[index] ?? \"\");\n\t\tif (rows <= 1) return 1;\n\n\t\tconst maxRows = Math.min(rows, maxIndex - index + 1, lines.length - index);\n\t\tlet reservedRows = 1;\n\t\twhile (reservedRows < maxRows) {\n\t\t\tconst line = lines[index + reservedRows] ?? \"\";\n\t\t\tif (isImageLine(line) || visibleWidth(line) > 0) break;\n\t\t\treservedRows++;\n\t\t}\n\t\treturn reservedRows;\n\t}\n\n\tprivate expandChangedRangeForKittyImages(\n\t\tfirstChanged: number,\n\t\tlastChanged: number,\n\t\tnewLines: string[],\n\t): ChangedLineRange {\n\t\tlet expandedFirstChanged = firstChanged;\n\t\tlet expandedLastChanged = lastChanged;\n\t\tconst expandForLines = (lines: string[]): void => {\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tif (extractKittyImageIds(lines[i]).length === 0) continue;\n\t\t\t\tconst blockEnd = i + this.getKittyImageReservedRows(lines, i) - 1;\n\t\t\t\tif (i >= firstChanged || (i <= lastChanged && blockEnd >= firstChanged)) {\n\t\t\t\t\texpandedFirstChanged = Math.min(expandedFirstChanged, i);\n\t\t\t\t\texpandedLastChanged = Math.max(expandedLastChanged, blockEnd);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\texpandForLines(this.previousLines);\n\t\texpandForLines(newLines);\n\t\treturn { firstChanged: expandedFirstChanged, lastChanged: expandedLastChanged };\n\t}\n\n\tprivate deleteChangedKittyImages(firstChanged: number, lastChanged: number): string {\n\t\tif (firstChanged < 0 || lastChanged < firstChanged) return \"\";\n\n\t\tconst ids = new Set<number>();\n\t\tconst maxLine = Math.min(lastChanged, this.previousLines.length - 1);\n\t\tfor (let i = firstChanged; i <= maxLine; i++) {\n\t\t\tfor (const id of extractKittyImageIds(this.previousLines[i] ?? \"\")) {\n\t\t\t\tids.add(id);\n\t\t\t}\n\t\t}\n\n\t\treturn this.deleteKittyImages(ids);\n\t}\n\n\tprivate prepareRenderFrame(): MainScreenRenderFrame {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\t\tconst heightChanged = this.previousHeight !== 0 && this.previousHeight !== height;\n\t\tconst previousBufferLength = this.previousHeight > 0 ? this.previousViewportTop + this.previousHeight : height;\n\t\tconst previousViewportTop = heightChanged ? Math.max(0, previousBufferLength - height) : this.previousViewportTop;\n\n\t\tlet newLines = this.render(width);\n\t\tif (this.hasOverlayEntries) newLines = this.compositeOverlays(newLines, width, height);\n\t\tconst cursorPosition = this.extractCursorPosition(newLines, height);\n\t\tnewLines = this.applyLineResets(newLines);\n\t\treturn { width, height, widthChanged, heightChanged, newLines, cursorPosition, previousViewportTop };\n\t}\n\n\tprivate getLineDiff(\n\t\ttargetRow: number,\n\t\thardwareCursorRow: number,\n\t\tpreviousViewportTop: number,\n\t\tviewportTop: number,\n\t): number {\n\t\tconst currentScreenRow = hardwareCursorRow - previousViewportTop;\n\t\tconst targetScreenRow = targetRow - viewportTop;\n\t\treturn targetScreenRow - currentScreenRow;\n\t}\n\n\tprivate logFullRedraw(frame: MainScreenRenderFrame, reason: string): void {\n\t\tif (process.env.PI_DEBUG_REDRAW !== \"1\") return;\n\t\tconst logPath = path.join(this.logDirectory, \"pi-debug.log\");\n\t\tconst message = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.previousLines.length}, new=${frame.newLines.length}, height=${frame.height})\\n`;\n\t\tfs.mkdirSync(path.dirname(logPath), { recursive: true });\n\t\tfs.appendFileSync(logPath, message);\n\t}\n\n\tprivate renderFullFrame(frame: MainScreenRenderFrame, clear: boolean): void {\n\t\tthis.fullRedrawCount += 1;\n\t\tlet buffer = \"\\x1b[?2026h\";\n\t\tif (clear) {\n\t\t\tbuffer += this.deleteKittyImages(this.previousKittyImageIds);\n\t\t\tbuffer += \"\\x1b[2J\\x1b[H\\x1b[3J\";\n\t\t}\n\t\tfor (let index = 0; index < frame.newLines.length; index++) {\n\t\t\tif (index > 0) buffer += \"\\r\\n\";\n\t\t\tconst line = frame.newLines[index];\n\t\t\tconst reservedRows = isImageLine(line) ? this.getKittyImageReservedRows(frame.newLines, index) : 1;\n\t\t\tif (reservedRows > 1 && reservedRows <= frame.height) {\n\t\t\t\tbuffer += \"\\r\\n\".repeat(reservedRows - 1);\n\t\t\t\tbuffer += `\\x1b[${reservedRows - 1}A${line}\\x1b[${reservedRows - 1}B`;\n\t\t\t\tindex += reservedRows - 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbuffer += line;\n\t\t}\n\t\tbuffer += \"\\x1b[?2026l\";\n\t\tthis.terminal.write(buffer);\n\t\tthis.cursorRow = Math.max(0, frame.newLines.length - 1);\n\t\tthis.hardwareCursorRow = this.cursorRow;\n\t\tthis.maxLinesRendered = clear ? frame.newLines.length : Math.max(this.maxLinesRendered, frame.newLines.length);\n\t\tconst bufferLength = Math.max(frame.height, frame.newLines.length);\n\t\tthis.previousViewportTop = Math.max(0, bufferLength - frame.height);\n\t\tthis.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);\n\t\tthis.previousLines = frame.newLines;\n\t\tthis.previousKittyImageIds = this.collectKittyImageIds(frame.newLines);\n\t\tthis.previousWidth = frame.width;\n\t\tthis.previousHeight = frame.height;\n\t}\n\n\tprivate renderRequiredFullFrame(frame: MainScreenRenderFrame): boolean {\n\t\tif (this.previousLines.length === 0 && !frame.widthChanged && !frame.heightChanged) {\n\t\t\tthis.logFullRedraw(frame, \"first render\");\n\t\t\tthis.renderFullFrame(frame, false);\n\t\t\treturn true;\n\t\t}\n\t\tif (frame.widthChanged) {\n\t\t\tthis.logFullRedraw(frame, `terminal width changed (${this.previousWidth} -> ${frame.width})`);\n\t\t\tthis.renderFullFrame(frame, true);\n\t\t\treturn true;\n\t\t}\n\t\tif (frame.heightChanged && !isTermuxSession()) {\n\t\t\tthis.logFullRedraw(frame, `terminal height changed (${this.previousHeight} -> ${frame.height})`);\n\t\t\tthis.renderFullFrame(frame, true);\n\t\t\treturn true;\n\t\t}\n\t\tif (this.getClearOnShrink() && frame.newLines.length < this.maxLinesRendered && !this.hasOverlayEntries) {\n\t\t\tthis.logFullRedraw(frame, `clearOnShrink (maxLinesRendered=${this.maxLinesRendered})`);\n\t\t\tthis.renderFullFrame(frame, true);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate findChangedContent(frame: MainScreenRenderFrame): ChangedContentRange | undefined {\n\t\tlet firstChanged = -1;\n\t\tlet lastChanged = -1;\n\t\tconst maxLines = Math.max(frame.newLines.length, this.previousLines.length);\n\t\tfor (let index = 0; index < maxLines; index++) {\n\t\t\tconst oldLine = index < this.previousLines.length ? this.previousLines[index] : \"\";\n\t\t\tconst newLine = index < frame.newLines.length ? frame.newLines[index] : \"\";\n\t\t\tif (oldLine === newLine) continue;\n\t\t\tif (firstChanged === -1) firstChanged = index;\n\t\t\tlastChanged = index;\n\t\t}\n\n\t\tconst appendedLines = frame.newLines.length > this.previousLines.length;\n\t\tif (appendedLines) {\n\t\t\tif (firstChanged === -1) firstChanged = this.previousLines.length;\n\t\t\tlastChanged = frame.newLines.length - 1;\n\t\t}\n\t\tif (firstChanged === -1) return undefined;\n\n\t\tconst expanded = this.expandChangedRangeForKittyImages(firstChanged, lastChanged, frame.newLines);\n\t\treturn {\n\t\t\t...expanded,\n\t\t\tappendStart: appendedLines && expanded.firstChanged === this.previousLines.length && expanded.firstChanged > 0,\n\t\t};\n\t}\n\n\tprivate commitFrameHistory(frame: MainScreenRenderFrame, viewportTop: number): void {\n\t\tthis.previousLines = frame.newLines;\n\t\tthis.previousKittyImageIds = this.collectKittyImageIds(frame.newLines);\n\t\tthis.previousWidth = frame.width;\n\t\tthis.previousHeight = frame.height;\n\t\tthis.previousViewportTop = viewportTop;\n\t}\n\n\tprivate handleUnchangedFrame(frame: MainScreenRenderFrame): void {\n\t\tthis.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);\n\t\tthis.previousViewportTop = frame.previousViewportTop;\n\t\tthis.previousHeight = frame.height;\n\t}\n\n\tprivate renderDeletedContent(frame: MainScreenRenderFrame, range: ChangedContentRange): void {\n\t\tif (this.previousLines.length <= frame.newLines.length) {\n\t\t\tthis.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);\n\t\t\tthis.commitFrameHistory(frame, frame.previousViewportTop);\n\t\t\treturn;\n\t\t}\n\n\t\tlet buffer = \"\\x1b[?2026h\";\n\t\tbuffer += this.deleteChangedKittyImages(range.firstChanged, range.lastChanged);\n\t\tconst targetRow = Math.max(0, frame.newLines.length - 1);\n\t\tif (targetRow < frame.previousViewportTop) {\n\t\t\tthis.logFullRedraw(frame, `deleted lines moved viewport up (${targetRow} < ${frame.previousViewportTop})`);\n\t\t\tthis.renderFullFrame(frame, true);\n\t\t\treturn;\n\t\t}\n\t\tconst lineDiff = this.getLineDiff(\n\t\t\ttargetRow,\n\t\t\tthis.hardwareCursorRow,\n\t\t\tframe.previousViewportTop,\n\t\t\tframe.previousViewportTop,\n\t\t);\n\t\tif (lineDiff > 0) buffer += `\\x1b[${lineDiff}B`;\n\t\telse if (lineDiff < 0) buffer += `\\x1b[${-lineDiff}A`;\n\t\tbuffer += \"\\r\";\n\t\tconst extraLines = this.previousLines.length - frame.newLines.length;\n\t\tif (extraLines > frame.height) {\n\t\t\tthis.logFullRedraw(frame, `extraLines > height (${extraLines} > ${frame.height})`);\n\t\t\tthis.renderFullFrame(frame, true);\n\t\t\treturn;\n\t\t}\n\t\tconst clearStartOffset = frame.newLines.length === 0 ? 0 : 1;\n\t\tif (extraLines > 0 && clearStartOffset > 0) buffer += `\\x1b[${clearStartOffset}B`;\n\t\tfor (let index = 0; index < extraLines; index++) {\n\t\t\tbuffer += \"\\r\\x1b[2K\";\n\t\t\tif (index < extraLines - 1) buffer += \"\\x1b[1B\";\n\t\t}\n\t\tconst moveBack = Math.max(0, extraLines - 1 + clearStartOffset);\n\t\tif (moveBack > 0) buffer += `\\x1b[${moveBack}A`;\n\t\tbuffer += \"\\x1b[?2026l\";\n\t\tthis.terminal.write(buffer);\n\t\tthis.cursorRow = targetRow;\n\t\tthis.hardwareCursorRow = targetRow;\n\t\tthis.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);\n\t\tthis.commitFrameHistory(frame, frame.previousViewportTop);\n\t}\n\n\tprivate assertRenderedLineFits(line: string, index: number, frame: MainScreenRenderFrame): void {\n\t\tif (isImageLine(line) || visibleWidth(line) <= frame.width) return;\n\t\tconst crashLogPath = path.join(this.logDirectory, \"pi-crash.log\");\n\t\tconst crashData = [\n\t\t\t`Crash at ${new Date().toISOString()}`,\n\t\t\t`Terminal width: ${frame.width}`,\n\t\t\t`Line ${index} visible width: ${visibleWidth(line)}`,\n\t\t\t\"\",\n\t\t\t\"=== All rendered lines ===\",\n\t\t\t...frame.newLines.map(\n\t\t\t\t(renderedLine, lineIndex) => `[${lineIndex}] (w=${visibleWidth(renderedLine)}) ${renderedLine}`,\n\t\t\t),\n\t\t\t\"\",\n\t\t].join(\"\\n\");\n\t\tfs.mkdirSync(path.dirname(crashLogPath), { recursive: true });\n\t\tfs.writeFileSync(crashLogPath, crashData);\n\t\tthis.stop();\n\t\tthrow new Error(\n\t\t\t[\n\t\t\t\t`Rendered line ${index} exceeds terminal width (${visibleWidth(line)} > ${frame.width}).`,\n\t\t\t\t\"\",\n\t\t\t\t\"This is likely caused by a custom TUI component not truncating its output.\",\n\t\t\t\t\"Use visibleWidth() to measure and truncateToWidth() to truncate lines.\",\n\t\t\t\t\"\",\n\t\t\t\t`Debug log written to: ${crashLogPath}`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tprivate renderChangedLines(\n\t\tframe: MainScreenRenderFrame,\n\t\trange: ChangedContentRange,\n\t\tviewportTop: number,\n\t\tinitialBuffer: string,\n\t): ChangedLinesRenderResult | undefined {\n\t\tlet buffer = initialBuffer;\n\t\tconst renderEnd = Math.min(range.lastChanged, frame.newLines.length - 1);\n\t\tfor (let index = range.firstChanged; index <= renderEnd; index++) {\n\t\t\tif (index > range.firstChanged) buffer += \"\\r\\n\";\n\t\t\tconst line = frame.newLines[index];\n\t\t\tconst reservedRows = isImageLine(line) ? this.getKittyImageReservedRows(frame.newLines, index, renderEnd) : 1;\n\t\t\tif (reservedRows > 1) {\n\t\t\t\tconst imageStartScreenRow = index - viewportTop;\n\t\t\t\tif (imageStartScreenRow < 0 || imageStartScreenRow + reservedRows > frame.height) {\n\t\t\t\t\tthis.logFullRedraw(\n\t\t\t\t\t\tframe,\n\t\t\t\t\t\t`kitty image pre-clear would scroll (${imageStartScreenRow} + ${reservedRows} > ${frame.height})`,\n\t\t\t\t\t);\n\t\t\t\t\tthis.renderFullFrame(frame, true);\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tbuffer += \"\\x1b[2K\";\n\t\t\t\tfor (let row = 1; row < reservedRows; row++) buffer += \"\\r\\n\\x1b[2K\";\n\t\t\t\tbuffer += `\\x1b[${reservedRows - 1}A${line}\\x1b[${reservedRows - 1}B`;\n\t\t\t\tindex += reservedRows - 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[2K\";\n\t\t\tthis.assertRenderedLineFits(line, index, frame);\n\t\t\tbuffer += line;\n\t\t}\n\t\treturn { buffer, renderEnd };\n\t}\n\n\tprivate writeDifferentialDebugLog(\n\t\tframe: MainScreenRenderFrame,\n\t\trange: ChangedContentRange,\n\t\tviewportTop: number,\n\t\thardwareCursorRow: number,\n\t\tlineDiff: number,\n\t\trenderEnd: number,\n\t\tfinalCursorRow: number,\n\t\tbuffer: string,\n\t): void {\n\t\tif (process.env.PI_TUI_DEBUG !== \"1\") return;\n\t\tconst debugDir = \"/tmp/tui\";\n\t\tfs.mkdirSync(debugDir, { recursive: true });\n\t\tconst debugPath = path.join(debugDir, `render-${Date.now()}-${Math.random().toString(36).slice(2)}.log`);\n\t\tconst debugData = [\n\t\t\t`firstChanged: ${range.firstChanged}`,\n\t\t\t`viewportTop: ${viewportTop}`,\n\t\t\t`cursorRow: ${this.cursorRow}`,\n\t\t\t`height: ${frame.height}`,\n\t\t\t`lineDiff: ${lineDiff}`,\n\t\t\t`hardwareCursorRow: ${hardwareCursorRow}`,\n\t\t\t`renderEnd: ${renderEnd}`,\n\t\t\t`finalCursorRow: ${finalCursorRow}`,\n\t\t\t`cursorPos: ${JSON.stringify(frame.cursorPosition)}`,\n\t\t\t`newLines.length: ${frame.newLines.length}`,\n\t\t\t`previousLines.length: ${this.previousLines.length}`,\n\t\t\t\"\",\n\t\t\t\"=== newLines ===\",\n\t\t\tJSON.stringify(frame.newLines, null, 2),\n\t\t\t\"\",\n\t\t\t\"=== previousLines ===\",\n\t\t\tJSON.stringify(this.previousLines, null, 2),\n\t\t\t\"\",\n\t\t\t\"=== buffer ===\",\n\t\t\tJSON.stringify(buffer),\n\t\t].join(\"\\n\");\n\t\tfs.writeFileSync(debugPath, debugData);\n\t}\n\n\tprivate renderDifferentialContent(frame: MainScreenRenderFrame, range: ChangedContentRange): void {\n\t\tif (range.firstChanged < frame.previousViewportTop) {\n\t\t\tthis.logFullRedraw(frame, `firstChanged < viewportTop (${range.firstChanged} < ${frame.previousViewportTop})`);\n\t\t\tthis.renderFullFrame(frame, true);\n\t\t\treturn;\n\t\t}\n\n\t\tlet previousViewportTop = frame.previousViewportTop;\n\t\tlet viewportTop = previousViewportTop;\n\t\tlet hardwareCursorRow = this.hardwareCursorRow;\n\t\tlet buffer = `\\x1b[?2026h${this.deleteChangedKittyImages(range.firstChanged, range.lastChanged)}`;\n\t\tconst previousViewportBottom = previousViewportTop + frame.height - 1;\n\t\tconst moveTargetRow = range.appendStart ? range.firstChanged - 1 : range.firstChanged;\n\t\tif (moveTargetRow > previousViewportBottom) {\n\t\t\tconst currentScreenRow = Math.max(0, Math.min(frame.height - 1, hardwareCursorRow - previousViewportTop));\n\t\t\tconst moveToBottom = frame.height - 1 - currentScreenRow;\n\t\t\tif (moveToBottom > 0) buffer += `\\x1b[${moveToBottom}B`;\n\t\t\tconst scroll = moveTargetRow - previousViewportBottom;\n\t\t\tbuffer += \"\\r\\n\".repeat(scroll);\n\t\t\tpreviousViewportTop += scroll;\n\t\t\tviewportTop += scroll;\n\t\t\thardwareCursorRow = moveTargetRow;\n\t\t}\n\n\t\tconst lineDiff = this.getLineDiff(moveTargetRow, hardwareCursorRow, previousViewportTop, viewportTop);\n\t\tif (lineDiff > 0) buffer += `\\x1b[${lineDiff}B`;\n\t\telse if (lineDiff < 0) buffer += `\\x1b[${-lineDiff}A`;\n\t\tbuffer += range.appendStart ? \"\\r\\n\" : \"\\r\";\n\n\t\tconst changedLines = this.renderChangedLines(frame, range, viewportTop, buffer);\n\t\tif (!changedLines) return;\n\t\tbuffer = changedLines.buffer;\n\t\tlet finalCursorRow = changedLines.renderEnd;\n\t\tif (this.previousLines.length > frame.newLines.length) {\n\t\t\tif (changedLines.renderEnd < frame.newLines.length - 1) {\n\t\t\t\tconst moveDown = frame.newLines.length - 1 - changedLines.renderEnd;\n\t\t\t\tbuffer += `\\x1b[${moveDown}B`;\n\t\t\t\tfinalCursorRow = frame.newLines.length - 1;\n\t\t\t}\n\t\t\tconst extraLines = this.previousLines.length - frame.newLines.length;\n\t\t\tfor (let index = frame.newLines.length; index < this.previousLines.length; index++) {\n\t\t\t\tbuffer += \"\\r\\n\\x1b[2K\";\n\t\t\t}\n\t\t\tbuffer += `\\x1b[${extraLines}A`;\n\t\t}\n\t\tbuffer += \"\\x1b[?2026l\";\n\n\t\tthis.writeDifferentialDebugLog(\n\t\t\tframe,\n\t\t\trange,\n\t\t\tviewportTop,\n\t\t\thardwareCursorRow,\n\t\t\tlineDiff,\n\t\t\tchangedLines.renderEnd,\n\t\t\tfinalCursorRow,\n\t\t\tbuffer,\n\t\t);\n\t\tthis.terminal.write(buffer);\n\t\tthis.cursorRow = Math.max(0, frame.newLines.length - 1);\n\t\tthis.hardwareCursorRow = finalCursorRow;\n\t\tthis.maxLinesRendered = Math.max(this.maxLinesRendered, frame.newLines.length);\n\t\tconst committedViewportTop = Math.max(previousViewportTop, finalCursorRow - frame.height + 1);\n\t\tthis.positionHardwareCursor(frame.cursorPosition, frame.newLines.length);\n\t\tthis.commitFrameHistory(frame, committedViewportTop);\n\t}\n\n\tprotected doRender(): void {\n\t\tif (this.stopped) return;\n\t\tconst frame = this.prepareRenderFrame();\n\t\tif (this.renderRequiredFullFrame(frame)) return;\n\t\tconst changedContent = this.findChangedContent(frame);\n\t\tif (!changedContent) {\n\t\t\tthis.handleUnchangedFrame(frame);\n\t\t\treturn;\n\t\t}\n\t\tif (changedContent.firstChanged >= frame.newLines.length) {\n\t\t\tthis.renderDeletedContent(frame, changedContent);\n\t\t\treturn;\n\t\t}\n\t\tthis.renderDifferentialContent(frame, changedContent);\n\t}\n\n\t/**\n\t * Position the hardware cursor for IME candidate window.\n\t * @param cursorPos The cursor position extracted from rendered output, or null\n\t * @param totalLines Total number of rendered lines\n\t */\n\tprivate positionHardwareCursor(cursorPos: RenderedCursorPosition | null, totalLines: number): void {\n\t\tif (!cursorPos || totalLines <= 0) {\n\t\t\tthis.terminal.hideCursor();\n\t\t\treturn;\n\t\t}\n\n\t\t// Clamp cursor position to valid range\n\t\tconst targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1));\n\t\tconst targetCol = Math.max(0, cursorPos.col);\n\n\t\t// Move cursor from current position to target\n\t\tconst rowDelta = targetRow - this.hardwareCursorRow;\n\t\tlet buffer = \"\";\n\t\tif (rowDelta > 0) {\n\t\t\tbuffer += `\\x1b[${rowDelta}B`; // Move down\n\t\t} else if (rowDelta < 0) {\n\t\t\tbuffer += `\\x1b[${-rowDelta}A`; // Move up\n\t\t}\n\t\t// Move to absolute column (1-indexed)\n\t\tbuffer += `\\x1b[${targetCol + 1}G`;\n\n\t\tif (buffer) {\n\t\t\tthis.terminal.write(buffer);\n\t\t}\n\n\t\tthis.hardwareCursorRow = targetRow;\n\t\tif (this.getShowHardwareCursor()) {\n\t\t\tthis.terminal.showCursor();\n\t\t} else {\n\t\t\tthis.terminal.hideCursor();\n\t\t}\n\t}\n}\n"]}