@greppleai/grip-tui 0.0.0-stage → 0.0.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 (158) hide show
  1. package/README.md +818 -2
  2. package/dist/autocomplete.d.ts +48 -0
  3. package/dist/autocomplete.d.ts.map +1 -0
  4. package/dist/autocomplete.js +566 -0
  5. package/dist/autocomplete.js.map +1 -0
  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/box.d.ts +22 -0
  11. package/dist/components/box.d.ts.map +1 -0
  12. package/dist/components/box.js +104 -0
  13. package/dist/components/box.js.map +1 -0
  14. package/dist/components/cancellable-loader.d.ts +22 -0
  15. package/dist/components/cancellable-loader.d.ts.map +1 -0
  16. package/dist/components/cancellable-loader.js +35 -0
  17. package/dist/components/cancellable-loader.js.map +1 -0
  18. package/dist/components/editor.d.ts +292 -0
  19. package/dist/components/editor.d.ts.map +1 -0
  20. package/dist/components/editor.js +1887 -0
  21. package/dist/components/editor.js.map +1 -0
  22. package/dist/components/h-stack.d.ts +7 -0
  23. package/dist/components/h-stack.d.ts.map +1 -0
  24. package/dist/components/h-stack.js +43 -0
  25. package/dist/components/h-stack.js.map +1 -0
  26. package/dist/components/image.d.ts +32 -0
  27. package/dist/components/image.d.ts.map +1 -0
  28. package/dist/components/image.js +76 -0
  29. package/dist/components/image.js.map +1 -0
  30. package/dist/components/input.d.ts +46 -0
  31. package/dist/components/input.d.ts.map +1 -0
  32. package/dist/components/input.js +352 -0
  33. package/dist/components/input.js.map +1 -0
  34. package/dist/components/loader.d.ts +32 -0
  35. package/dist/components/loader.d.ts.map +1 -0
  36. package/dist/components/loader.js +80 -0
  37. package/dist/components/loader.js.map +1 -0
  38. package/dist/components/markdown.d.ts +132 -0
  39. package/dist/components/markdown.d.ts.map +1 -0
  40. package/dist/components/markdown.js +717 -0
  41. package/dist/components/markdown.js.map +1 -0
  42. package/dist/components/scroll-view.d.ts +53 -0
  43. package/dist/components/scroll-view.d.ts.map +1 -0
  44. package/dist/components/scroll-view.js +173 -0
  45. package/dist/components/scroll-view.js.map +1 -0
  46. package/dist/components/select-list.d.ts +50 -0
  47. package/dist/components/select-list.d.ts.map +1 -0
  48. package/dist/components/select-list.js +159 -0
  49. package/dist/components/select-list.js.map +1 -0
  50. package/dist/components/settings-list.d.ts +52 -0
  51. package/dist/components/settings-list.d.ts.map +1 -0
  52. package/dist/components/settings-list.js +187 -0
  53. package/dist/components/settings-list.js.map +1 -0
  54. package/dist/components/spacer.d.ts +12 -0
  55. package/dist/components/spacer.d.ts.map +1 -0
  56. package/dist/components/spacer.js +23 -0
  57. package/dist/components/spacer.js.map +1 -0
  58. package/dist/components/stack.d.ts +32 -0
  59. package/dist/components/stack.d.ts.map +1 -0
  60. package/dist/components/stack.js +122 -0
  61. package/dist/components/stack.js.map +1 -0
  62. package/dist/components/text.d.ts +19 -0
  63. package/dist/components/text.d.ts.map +1 -0
  64. package/dist/components/text.js +89 -0
  65. package/dist/components/text.js.map +1 -0
  66. package/dist/components/truncated-text.d.ts +13 -0
  67. package/dist/components/truncated-text.d.ts.map +1 -0
  68. package/dist/components/truncated-text.js +51 -0
  69. package/dist/components/truncated-text.js.map +1 -0
  70. package/dist/components/v-stack.d.ts +8 -0
  71. package/dist/components/v-stack.d.ts.map +1 -0
  72. package/dist/components/v-stack.js +27 -0
  73. package/dist/components/v-stack.js.map +1 -0
  74. package/dist/editor-component.d.ts +39 -0
  75. package/dist/editor-component.d.ts.map +1 -0
  76. package/dist/editor-component.js +2 -0
  77. package/dist/editor-component.js.map +1 -0
  78. package/dist/fuzzy.d.ts +16 -0
  79. package/dist/fuzzy.d.ts.map +1 -0
  80. package/dist/fuzzy.js +101 -0
  81. package/dist/fuzzy.js.map +1 -0
  82. package/dist/index.d.ts +30 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +41 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/keybindings.d.ts +243 -0
  87. package/dist/keybindings.d.ts.map +1 -0
  88. package/dist/keybindings.js +209 -0
  89. package/dist/keybindings.js.map +1 -0
  90. package/dist/keys.d.ts +184 -0
  91. package/dist/keys.d.ts.map +1 -0
  92. package/dist/keys.js +1053 -0
  93. package/dist/keys.js.map +1 -0
  94. package/dist/kill-ring.d.ts +30 -0
  95. package/dist/kill-ring.d.ts.map +1 -0
  96. package/dist/kill-ring.js +37 -0
  97. package/dist/kill-ring.js.map +1 -0
  98. package/dist/latex.d.ts +10 -0
  99. package/dist/latex.d.ts.map +1 -0
  100. package/dist/latex.js +1406 -0
  101. package/dist/latex.js.map +1 -0
  102. package/dist/layout-node.d.ts +43 -0
  103. package/dist/layout-node.d.ts.map +1 -0
  104. package/dist/layout-node.js +6 -0
  105. package/dist/layout-node.js.map +1 -0
  106. package/dist/layout.d.ts +40 -0
  107. package/dist/layout.d.ts.map +1 -0
  108. package/dist/layout.js +386 -0
  109. package/dist/layout.js.map +1 -0
  110. package/dist/native-modifiers.d.ts +3 -0
  111. package/dist/native-modifiers.d.ts.map +1 -0
  112. package/dist/native-modifiers.js +53 -0
  113. package/dist/native-modifiers.js.map +1 -0
  114. package/dist/stdin-buffer.d.ts +58 -0
  115. package/dist/stdin-buffer.d.ts.map +1 -0
  116. package/dist/stdin-buffer.js +318 -0
  117. package/dist/stdin-buffer.js.map +1 -0
  118. package/dist/terminal-control.d.ts +5 -0
  119. package/dist/terminal-control.d.ts.map +1 -0
  120. package/dist/terminal-control.js +26 -0
  121. package/dist/terminal-control.js.map +1 -0
  122. package/dist/terminal-image.d.ts +112 -0
  123. package/dist/terminal-image.d.ts.map +1 -0
  124. package/dist/terminal-image.js +508 -0
  125. package/dist/terminal-image.js.map +1 -0
  126. package/dist/terminal.d.ts +117 -0
  127. package/dist/terminal.d.ts.map +1 -0
  128. package/dist/terminal.js +490 -0
  129. package/dist/terminal.js.map +1 -0
  130. package/dist/tui-alt-screen.d.ts +114 -0
  131. package/dist/tui-alt-screen.d.ts.map +1 -0
  132. package/dist/tui-alt-screen.js +950 -0
  133. package/dist/tui-alt-screen.js.map +1 -0
  134. package/dist/tui-main-screen.d.ts +52 -0
  135. package/dist/tui-main-screen.d.ts.map +1 -0
  136. package/dist/tui-main-screen.js +511 -0
  137. package/dist/tui-main-screen.js.map +1 -0
  138. package/dist/tui.d.ts +295 -0
  139. package/dist/tui.d.ts.map +1 -0
  140. package/dist/tui.js +868 -0
  141. package/dist/tui.js.map +1 -0
  142. package/dist/undo-stack.d.ts +17 -0
  143. package/dist/undo-stack.d.ts.map +1 -0
  144. package/dist/undo-stack.js +25 -0
  145. package/dist/undo-stack.js.map +1 -0
  146. package/dist/utils.d.ts +96 -0
  147. package/dist/utils.d.ts.map +1 -0
  148. package/dist/utils.js +1106 -0
  149. package/dist/utils.js.map +1 -0
  150. package/dist/word-navigation.d.ts +25 -0
  151. package/dist/word-navigation.d.ts.map +1 -0
  152. package/dist/word-navigation.js +94 -0
  153. package/dist/word-navigation.js.map +1 -0
  154. package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  155. package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  156. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  157. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  158. package/package.json +47 -5
@@ -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.GRIP_DEBUG_REDRAW !== "1")
174
+ return;
175
+ const logPath = path.join(this.logDirectory, "grip-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, "grip-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.GRIP_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.GRIP_DEBUG_REDRAW !== \"1\") return;\n\t\tconst logPath = path.join(this.logDirectory, \"grip-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, \"grip-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.GRIP_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"]}