@mariozechner/pi-tui 0.5.47 → 0.6.2

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 (65) hide show
  1. package/README.md +166 -475
  2. package/dist/autocomplete.d.ts.map +1 -1
  3. package/dist/autocomplete.js +2 -0
  4. package/dist/autocomplete.js.map +1 -1
  5. package/dist/components/{text-editor.d.ts → editor.d.ts} +9 -5
  6. package/dist/components/editor.d.ts.map +1 -0
  7. package/dist/components/{text-editor.js → editor.js} +125 -70
  8. package/dist/components/editor.js.map +1 -0
  9. package/dist/components/input.d.ts +14 -0
  10. package/dist/components/input.d.ts.map +1 -0
  11. package/dist/components/input.js +120 -0
  12. package/dist/components/input.js.map +1 -0
  13. package/dist/components/{loading-animation.d.ts → loader.d.ts} +5 -5
  14. package/dist/components/loader.d.ts.map +1 -0
  15. package/dist/components/{loading-animation.js → loader.js} +13 -10
  16. package/dist/components/loader.js.map +1 -0
  17. package/dist/components/markdown.d.ts +46 -0
  18. package/dist/components/markdown.d.ts.map +1 -0
  19. package/dist/components/markdown.js +499 -0
  20. package/dist/components/markdown.js.map +1 -0
  21. package/dist/components/select-list.d.ts +3 -3
  22. package/dist/components/select-list.d.ts.map +1 -1
  23. package/dist/components/select-list.js +24 -16
  24. package/dist/components/select-list.js.map +1 -1
  25. package/dist/components/spacer.d.ts +11 -0
  26. package/dist/components/spacer.d.ts.map +1 -0
  27. package/dist/components/spacer.js +20 -0
  28. package/dist/components/spacer.js.map +1 -0
  29. package/dist/components/text.d.ts +26 -0
  30. package/dist/components/text.d.ts.map +1 -0
  31. package/dist/components/text.js +141 -0
  32. package/dist/components/text.js.map +1 -0
  33. package/dist/index.d.ts +8 -6
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +10 -12
  36. package/dist/index.js.map +1 -1
  37. package/dist/terminal.d.ts +12 -0
  38. package/dist/terminal.d.ts.map +1 -1
  39. package/dist/terminal.js +33 -3
  40. package/dist/terminal.js.map +1 -1
  41. package/dist/tui.d.ts +30 -52
  42. package/dist/tui.d.ts.map +1 -1
  43. package/dist/tui.js +131 -337
  44. package/dist/tui.js.map +1 -1
  45. package/dist/utils.d.ts +10 -0
  46. package/dist/utils.d.ts.map +1 -0
  47. package/dist/utils.js +15 -0
  48. package/dist/utils.js.map +1 -0
  49. package/package.json +6 -5
  50. package/dist/components/loading-animation.d.ts.map +0 -1
  51. package/dist/components/loading-animation.js.map +0 -1
  52. package/dist/components/markdown-component.d.ts +0 -15
  53. package/dist/components/markdown-component.d.ts.map +0 -1
  54. package/dist/components/markdown-component.js +0 -247
  55. package/dist/components/markdown-component.js.map +0 -1
  56. package/dist/components/text-component.d.ts +0 -14
  57. package/dist/components/text-component.d.ts.map +0 -1
  58. package/dist/components/text-component.js +0 -90
  59. package/dist/components/text-component.js.map +0 -1
  60. package/dist/components/text-editor.d.ts.map +0 -1
  61. package/dist/components/text-editor.js.map +0 -1
  62. package/dist/components/whitespace-component.d.ts +0 -13
  63. package/dist/components/whitespace-component.d.ts.map +0 -1
  64. package/dist/components/whitespace-component.js +0 -22
  65. package/dist/components/whitespace-component.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,389 +1,183 @@
1
- import process from "process";
2
- import { ProcessTerminal } from "./terminal.js";
3
- // Global component ID counter
4
- let nextComponentId = 1;
5
- // Helper to get next component ID
6
- export function getNextComponentId() {
7
- return nextComponentId++;
8
- }
9
1
  /**
10
- * Container for managing child components
2
+ * Minimal TUI implementation with differential rendering
3
+ */
4
+ import { visibleWidth } from "./utils.js";
5
+ export { visibleWidth };
6
+ /**
7
+ * Container - a component that contains other components
11
8
  */
12
9
  export class Container {
13
- constructor() {
14
- this.children = [];
15
- this.previousChildCount = 0;
16
- this.id = getNextComponentId();
17
- }
18
- setTui(tui) {
19
- this.tui = tui;
20
- for (const child of this.children) {
21
- if (child instanceof Container) {
22
- child.setTui(tui);
23
- }
24
- }
25
- }
10
+ children = [];
26
11
  addChild(component) {
27
12
  this.children.push(component);
28
- if (component instanceof Container) {
29
- component.setTui(this.tui);
30
- }
31
- this.tui?.requestRender();
32
13
  }
33
14
  removeChild(component) {
34
15
  const index = this.children.indexOf(component);
35
- if (index >= 0) {
36
- this.children.splice(index, 1);
37
- if (component instanceof Container) {
38
- component.setTui(undefined);
39
- }
40
- this.tui?.requestRender();
41
- }
42
- }
43
- removeChildAt(index) {
44
- if (index >= 0 && index < this.children.length) {
45
- const component = this.children[index];
16
+ if (index !== -1) {
46
17
  this.children.splice(index, 1);
47
- if (component instanceof Container) {
48
- component.setTui(undefined);
49
- }
50
- this.tui?.requestRender();
51
18
  }
52
19
  }
53
20
  clear() {
54
- for (const child of this.children) {
55
- if (child instanceof Container) {
56
- child.setTui(undefined);
57
- }
58
- }
59
21
  this.children = [];
60
- this.tui?.requestRender();
61
- }
62
- getChild(index) {
63
- return this.children[index];
64
- }
65
- getChildCount() {
66
- return this.children.length;
67
22
  }
68
23
  render(width) {
69
24
  const lines = [];
70
- let changed = false;
71
- // Check if the number of children changed (important for detecting clears)
72
- if (this.children.length !== this.previousChildCount) {
73
- changed = true;
74
- this.previousChildCount = this.children.length;
75
- }
76
25
  for (const child of this.children) {
77
- const result = child.render(width);
78
- lines.push(...result.lines);
79
- if (result.changed) {
80
- changed = true;
81
- }
26
+ lines.push(...child.render(width));
82
27
  }
83
- return { lines, changed };
28
+ return lines;
84
29
  }
85
30
  }
86
31
  /**
87
- * TUI - Smart differential rendering TUI implementation.
32
+ * TUI - Main class for managing terminal UI with differential rendering
88
33
  */
89
34
  export class TUI extends Container {
90
- getLinesRedrawn() {
91
- return this.totalLinesRedrawn;
92
- }
93
- getAverageLinesRedrawn() {
94
- return this.renderCount > 0 ? this.totalLinesRedrawn / this.renderCount : 0;
95
- }
35
+ terminal;
36
+ previousLines = [];
37
+ previousWidth = 0;
38
+ focusedComponent = null;
39
+ renderRequested = false;
40
+ cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)
96
41
  constructor(terminal) {
97
42
  super();
98
- this.focusedComponent = null;
99
- this.needsRender = false;
100
- this.isFirstRender = true;
101
- this.isStarted = false;
102
- // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used in renderToScreen method on lines 260 and 276
103
- this.previousRenderCommands = [];
104
- this.previousLines = []; // What we rendered last time
105
- // Performance metrics
106
- this.totalLinesRedrawn = 0;
107
- this.renderCount = 0;
108
- this.setTui(this);
109
- this.handleResize = this.handleResize.bind(this);
110
- this.handleKeypress = this.handleKeypress.bind(this);
111
- // Use provided terminal or default to ProcessTerminal
112
- this.terminal = terminal || new ProcessTerminal();
43
+ this.terminal = terminal;
113
44
  }
114
45
  setFocus(component) {
115
- if (this.findComponent(component)) {
116
- this.focusedComponent = component;
117
- }
118
- }
119
- findComponent(component) {
120
- if (this.children.includes(component)) {
121
- return true;
122
- }
123
- for (const child of this.children) {
124
- if (child instanceof Container) {
125
- if (this.findInContainer(child, component)) {
126
- return true;
127
- }
128
- }
129
- }
130
- return false;
131
- }
132
- findInContainer(container, component) {
133
- const childCount = container.getChildCount();
134
- for (let i = 0; i < childCount; i++) {
135
- const child = container.getChild(i);
136
- if (child === component) {
137
- return true;
138
- }
139
- if (child instanceof Container) {
140
- if (this.findInContainer(child, component)) {
141
- return true;
142
- }
143
- }
144
- }
145
- return false;
146
- }
147
- requestRender() {
148
- if (!this.isStarted)
149
- return;
150
- // Only queue a render if we haven't already
151
- if (!this.needsRender) {
152
- this.needsRender = true;
153
- process.nextTick(() => {
154
- if (this.needsRender) {
155
- this.renderToScreen();
156
- this.needsRender = false;
157
- }
158
- });
159
- }
46
+ this.focusedComponent = component;
160
47
  }
161
48
  start() {
162
- this.isStarted = true;
163
- // Hide cursor
164
- this.terminal.write("\x1b[?25l");
165
- // Start terminal with handlers
166
- try {
167
- this.terminal.start(this.handleKeypress, this.handleResize);
168
- }
169
- catch (error) {
170
- console.error("Error starting terminal:", error);
171
- }
172
- // Trigger initial render if we have components
173
- if (this.children.length > 0) {
174
- this.requestRender();
175
- }
49
+ this.terminal.start((data) => this.handleInput(data), () => this.requestRender());
50
+ this.terminal.hideCursor();
51
+ this.requestRender();
176
52
  }
177
53
  stop() {
178
- // Show cursor
179
- this.terminal.write("\x1b[?25h");
180
- // Stop terminal
54
+ this.terminal.showCursor();
181
55
  this.terminal.stop();
182
- this.isStarted = false;
183
- }
184
- renderToScreen(resize = false) {
185
- const termWidth = this.terminal.columns;
186
- const termHeight = this.terminal.rows;
187
- if (resize) {
188
- this.isFirstRender = true;
189
- this.previousRenderCommands = [];
190
- this.previousLines = [];
191
- }
192
- // Collect all render commands
193
- const currentRenderCommands = [];
194
- this.collectRenderCommands(this, termWidth, currentRenderCommands);
195
- if (this.isFirstRender) {
196
- this.renderInitial(currentRenderCommands);
197
- this.isFirstRender = false;
198
- }
199
- else {
200
- this.renderLineBased(currentRenderCommands, termHeight);
201
- }
202
- // Save for next render
203
- this.previousRenderCommands = currentRenderCommands;
204
- this.renderCount++;
205
56
  }
206
- collectRenderCommands(container, width, commands) {
207
- const childCount = container.getChildCount();
208
- for (let i = 0; i < childCount; i++) {
209
- const child = container.getChild(i);
210
- if (!child)
211
- continue;
212
- const result = child.render(width);
213
- commands.push({
214
- id: child.id,
215
- lines: result.lines,
216
- changed: result.changed,
217
- });
218
- }
219
- }
220
- renderInitial(commands) {
221
- let output = "";
222
- const lines = [];
223
- for (const command of commands) {
224
- lines.push(...command.lines);
225
- }
226
- // Output all lines
227
- for (let i = 0; i < lines.length; i++) {
228
- if (i > 0)
229
- output += "\r\n";
230
- output += lines[i];
57
+ requestRender() {
58
+ if (this.renderRequested)
59
+ return;
60
+ this.renderRequested = true;
61
+ process.nextTick(() => {
62
+ this.renderRequested = false;
63
+ this.doRender();
64
+ });
65
+ }
66
+ handleInput(data) {
67
+ // Pass input to focused component (including Ctrl+C)
68
+ // The focused component can decide how to handle Ctrl+C
69
+ if (this.focusedComponent?.handleInput) {
70
+ this.focusedComponent.handleInput(data);
71
+ this.requestRender();
231
72
  }
232
- // Add final newline to position cursor below content
233
- if (lines.length > 0)
234
- output += "\r\n";
235
- this.terminal.write(output);
236
- // Save what we rendered
237
- this.previousLines = lines;
238
- this.totalLinesRedrawn += lines.length;
239
73
  }
240
- renderLineBased(currentCommands, termHeight) {
241
- const viewportHeight = termHeight - 1; // Leave one line for cursor
242
- // Build the new lines array
243
- const newLines = [];
244
- for (const command of currentCommands) {
245
- newLines.push(...command.lines);
246
- }
247
- const totalNewLines = newLines.length;
248
- const totalOldLines = this.previousLines.length;
249
- // Find first changed line by comparing old and new
250
- let firstChangedLine = -1;
251
- const minLines = Math.min(totalOldLines, totalNewLines);
252
- for (let i = 0; i < minLines; i++) {
253
- if (this.previousLines[i] !== newLines[i]) {
254
- firstChangedLine = i;
255
- break;
74
+ doRender() {
75
+ const width = this.terminal.columns;
76
+ const height = this.terminal.rows;
77
+ // Render all components to get new lines
78
+ const newLines = this.render(width);
79
+ // Width changed - need full re-render
80
+ const widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;
81
+ // First render - just output everything without clearing
82
+ if (this.previousLines.length === 0) {
83
+ let buffer = "\x1b[?2026h"; // Begin synchronized output
84
+ for (let i = 0; i < newLines.length; i++) {
85
+ if (i > 0)
86
+ buffer += "\r\n";
87
+ buffer += newLines[i];
256
88
  }
257
- }
258
- // If all common lines are the same, check if we have different lengths
259
- if (firstChangedLine === -1 && totalOldLines !== totalNewLines) {
260
- firstChangedLine = minLines;
261
- }
262
- // No changes at all
263
- if (firstChangedLine === -1) {
89
+ buffer += "\x1b[?2026l"; // End synchronized output
90
+ this.terminal.write(buffer);
91
+ // After rendering N lines, cursor is at end of last line (line N-1)
92
+ this.cursorRow = newLines.length - 1;
264
93
  this.previousLines = newLines;
94
+ this.previousWidth = width;
265
95
  return;
266
96
  }
267
- // Calculate viewport boundaries
268
- const oldViewportStart = Math.max(0, totalOldLines - viewportHeight);
269
- const cursorPosition = totalOldLines; // Cursor is one line below last content
270
- let output = "";
271
- let linesRedrawn = 0;
272
- // Check if change is in scrollback (unreachable by cursor)
273
- if (firstChangedLine < oldViewportStart) {
274
- // Must do full clear and re-render
275
- output = "\x1b[3J\x1b[H"; // Clear scrollback and screen, home cursor
97
+ // Width changed - full re-render
98
+ if (widthChanged) {
99
+ let buffer = "\x1b[?2026h"; // Begin synchronized output
100
+ buffer += "\x1b[3J\x1b[2J\x1b[H"; // Clear scrollback, screen, and home
276
101
  for (let i = 0; i < newLines.length; i++) {
277
102
  if (i > 0)
278
- output += "\r\n";
279
- output += newLines[i];
103
+ buffer += "\r\n";
104
+ buffer += newLines[i];
280
105
  }
281
- if (newLines.length > 0)
282
- output += "\r\n";
283
- linesRedrawn = newLines.length;
106
+ buffer += "\x1b[?2026l"; // End synchronized output
107
+ this.terminal.write(buffer);
108
+ this.cursorRow = newLines.length - 1;
109
+ this.previousLines = newLines;
110
+ this.previousWidth = width;
111
+ return;
284
112
  }
285
- else {
286
- // Change is in viewport - we can reach it with cursor movements
287
- // Calculate viewport position of the change
288
- const viewportChangePosition = firstChangedLine - oldViewportStart;
289
- // Move cursor to the change position
290
- const linesToMoveUp = cursorPosition - oldViewportStart - viewportChangePosition;
291
- if (linesToMoveUp > 0) {
292
- output += `\x1b[${linesToMoveUp}A`;
293
- }
294
- // Now do surgical updates or partial clear based on what's more efficient
295
- let currentLine = firstChangedLine;
296
- const currentViewportLine = viewportChangePosition;
297
- // If we have significant structural changes, just clear and re-render from here
298
- const hasSignificantChanges = totalNewLines !== totalOldLines || totalNewLines - firstChangedLine > 10; // Arbitrary threshold
299
- if (hasSignificantChanges) {
300
- // Clear from cursor to end of screen and render all remaining lines
301
- output += "\r\x1b[0J";
302
- for (let i = firstChangedLine; i < newLines.length; i++) {
303
- if (i > firstChangedLine)
304
- output += "\r\n";
305
- output += newLines[i];
306
- linesRedrawn++;
307
- }
308
- if (newLines.length > firstChangedLine)
309
- output += "\r\n";
310
- }
311
- else {
312
- // Do surgical line-by-line updates
313
- for (let i = firstChangedLine; i < minLines; i++) {
314
- if (this.previousLines[i] !== newLines[i]) {
315
- // Move to this line if needed
316
- const moveLines = i - currentLine;
317
- if (moveLines > 0) {
318
- output += `\x1b[${moveLines}B`;
319
- }
320
- // Clear and rewrite the line
321
- output += "\r\x1b[2K" + newLines[i];
322
- currentLine = i;
323
- linesRedrawn++;
324
- }
325
- }
326
- // Handle added/removed lines at the end
327
- if (totalNewLines > totalOldLines) {
328
- // Move to end of old content and add new lines
329
- const moveToEnd = totalOldLines - 1 - currentLine;
330
- if (moveToEnd > 0) {
331
- output += `\x1b[${moveToEnd}B`;
332
- }
333
- output += "\r\n";
334
- for (let i = totalOldLines; i < totalNewLines; i++) {
335
- if (i > totalOldLines)
336
- output += "\r\n";
337
- output += newLines[i];
338
- linesRedrawn++;
339
- }
340
- output += "\r\n";
341
- }
342
- else if (totalNewLines < totalOldLines) {
343
- // Move to end of new content and clear rest
344
- const moveToEnd = totalNewLines - 1 - currentLine;
345
- if (moveToEnd > 0) {
346
- output += `\x1b[${moveToEnd}B`;
347
- }
348
- else if (moveToEnd < 0) {
349
- output += `\x1b[${-moveToEnd}A`;
350
- }
351
- output += "\r\n\x1b[0J";
352
- }
353
- else {
354
- // Same length, just position cursor at end
355
- const moveToEnd = totalNewLines - 1 - currentLine;
356
- if (moveToEnd > 0) {
357
- output += `\x1b[${moveToEnd}B`;
358
- }
359
- else if (moveToEnd < 0) {
360
- output += `\x1b[${-moveToEnd}A`;
361
- }
362
- output += "\r\n";
113
+ // Find first and last changed lines
114
+ let firstChanged = -1;
115
+ let lastChanged = -1;
116
+ const maxLines = Math.max(newLines.length, this.previousLines.length);
117
+ for (let i = 0; i < maxLines; i++) {
118
+ const oldLine = i < this.previousLines.length ? this.previousLines[i] : "";
119
+ const newLine = i < newLines.length ? newLines[i] : "";
120
+ if (oldLine !== newLine) {
121
+ if (firstChanged === -1) {
122
+ firstChanged = i;
363
123
  }
124
+ lastChanged = i;
364
125
  }
365
126
  }
366
- this.terminal.write(output);
367
- this.previousLines = newLines;
368
- this.totalLinesRedrawn += linesRedrawn;
369
- }
370
- handleResize() {
371
- // Clear screen and reset
372
- this.terminal.write("\x1b[2J\x1b[H\x1b[?25l");
373
- this.renderToScreen(true);
374
- }
375
- handleKeypress(data) {
376
- if (this.onGlobalKeyPress) {
377
- const shouldForward = this.onGlobalKeyPress(data);
378
- if (!shouldForward) {
379
- this.requestRender();
380
- return;
127
+ // No changes
128
+ if (firstChanged === -1) {
129
+ return;
130
+ }
131
+ // Check if firstChanged is outside the viewport
132
+ // cursorRow is the line where cursor is (0-indexed)
133
+ // Viewport shows lines from (cursorRow - height + 1) to cursorRow
134
+ // If firstChanged < viewportTop, we need full re-render
135
+ const viewportTop = this.cursorRow - height + 1;
136
+ if (firstChanged < viewportTop) {
137
+ // First change is above viewport - need full re-render
138
+ let buffer = "\x1b[?2026h"; // Begin synchronized output
139
+ buffer += "\x1b[3J\x1b[2J\x1b[H"; // Clear scrollback, screen, and home
140
+ for (let i = 0; i < newLines.length; i++) {
141
+ if (i > 0)
142
+ buffer += "\r\n";
143
+ buffer += newLines[i];
381
144
  }
145
+ buffer += "\x1b[?2026l"; // End synchronized output
146
+ this.terminal.write(buffer);
147
+ this.cursorRow = newLines.length - 1;
148
+ this.previousLines = newLines;
149
+ this.previousWidth = width;
150
+ return;
382
151
  }
383
- if (this.focusedComponent?.handleInput) {
384
- this.focusedComponent.handleInput(data);
385
- this.requestRender();
152
+ // Render from first changed line to end
153
+ // Build buffer with all updates wrapped in synchronized output
154
+ let buffer = "\x1b[?2026h"; // Begin synchronized output
155
+ // Move cursor to first changed line
156
+ const lineDiff = firstChanged - this.cursorRow;
157
+ if (lineDiff > 0) {
158
+ buffer += `\x1b[${lineDiff}B`; // Move down
159
+ }
160
+ else if (lineDiff < 0) {
161
+ buffer += `\x1b[${-lineDiff}A`; // Move up
162
+ }
163
+ buffer += "\r"; // Move to column 0
164
+ buffer += "\x1b[J"; // Clear from cursor to end of screen
165
+ // Render from first changed line to end
166
+ for (let i = firstChanged; i < newLines.length; i++) {
167
+ if (i > firstChanged)
168
+ buffer += "\r\n";
169
+ if (visibleWidth(newLines[i]) > width) {
170
+ throw new Error(`Rendered line ${i} exceeds terminal width\n\n${newLines[i]}`);
171
+ }
172
+ buffer += newLines[i];
386
173
  }
174
+ buffer += "\x1b[?2026l"; // End synchronized output
175
+ // Write entire buffer at once
176
+ this.terminal.write(buffer);
177
+ // Cursor is now at end of last line
178
+ this.cursorRow = newLines.length - 1;
179
+ this.previousLines = newLines;
180
+ this.previousWidth = width;
387
181
  }
388
182
  }
389
183
  //# sourceMappingURL=tui.js.map
package/dist/tui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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","sourcesContent":["import process from \"process\";\nimport { ProcessTerminal, type Terminal } from \"./terminal.js\";\n\n/**\n * Result of rendering a component\n */\nexport interface ComponentRenderResult {\n\tlines: string[];\n\tchanged: boolean;\n}\n\n/**\n * Component interface\n */\nexport interface Component {\n\treadonly id: number;\n\trender(width: number): ComponentRenderResult;\n\thandleInput?(keyData: string): void;\n}\n\n// Global component ID counter\nlet nextComponentId = 1;\n\n// Helper to get next component ID\nexport function getNextComponentId(): number {\n\treturn nextComponentId++;\n}\n\n// Padding type for components\nexport interface Padding {\n\ttop?: number;\n\tbottom?: number;\n\tleft?: number;\n\tright?: number;\n}\n\n/**\n * Container for managing child components\n */\nexport class Container implements Component {\n\treadonly id: number;\n\tpublic children: (Component | Container)[] = [];\n\tprivate tui?: TUI;\n\tprivate previousChildCount: number = 0;\n\n\tconstructor() {\n\t\tthis.id = getNextComponentId();\n\t}\n\n\tsetTui(tui: TUI | undefined): void {\n\t\tthis.tui = tui;\n\t\tfor (const child of this.children) {\n\t\t\tif (child instanceof Container) {\n\t\t\t\tchild.setTui(tui);\n\t\t\t}\n\t\t}\n\t}\n\n\taddChild(component: Component | Container): void {\n\t\tthis.children.push(component);\n\t\tif (component instanceof Container) {\n\t\t\tcomponent.setTui(this.tui);\n\t\t}\n\t\tthis.tui?.requestRender();\n\t}\n\n\tremoveChild(component: Component | Container): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index >= 0) {\n\t\t\tthis.children.splice(index, 1);\n\t\t\tif (component instanceof Container) {\n\t\t\t\tcomponent.setTui(undefined);\n\t\t\t}\n\t\t\tthis.tui?.requestRender();\n\t\t}\n\t}\n\n\tremoveChildAt(index: number): void {\n\t\tif (index >= 0 && index < this.children.length) {\n\t\t\tconst component = this.children[index];\n\t\t\tthis.children.splice(index, 1);\n\t\t\tif (component instanceof Container) {\n\t\t\t\tcomponent.setTui(undefined);\n\t\t\t}\n\t\t\tthis.tui?.requestRender();\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tfor (const child of this.children) {\n\t\t\tif (child instanceof Container) {\n\t\t\t\tchild.setTui(undefined);\n\t\t\t}\n\t\t}\n\t\tthis.children = [];\n\t\tthis.tui?.requestRender();\n\t}\n\n\tgetChild(index: number): (Component | Container) | undefined {\n\t\treturn this.children[index];\n\t}\n\n\tgetChildCount(): number {\n\t\treturn this.children.length;\n\t}\n\n\trender(width: number): ComponentRenderResult {\n\t\tconst lines: string[] = [];\n\t\tlet changed = false;\n\n\t\t// Check if the number of children changed (important for detecting clears)\n\t\tif (this.children.length !== this.previousChildCount) {\n\t\t\tchanged = true;\n\t\t\tthis.previousChildCount = this.children.length;\n\t\t}\n\n\t\tfor (const child of this.children) {\n\t\t\tconst result = child.render(width);\n\t\t\tlines.push(...result.lines);\n\t\t\tif (result.changed) {\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t}\n\n\t\treturn { lines, changed };\n\t}\n}\n\n/**\n * Render command for tracking component output\n */\ninterface RenderCommand {\n\tid: number;\n\tlines: string[];\n\tchanged: boolean;\n}\n\n/**\n * TUI - Smart differential rendering TUI implementation.\n */\nexport class TUI extends Container {\n\tprivate focusedComponent: Component | null = null;\n\tprivate needsRender = false;\n\tprivate isFirstRender = true;\n\tprivate isStarted = false;\n\tpublic onGlobalKeyPress?: (data: string) => boolean;\n\tprivate terminal: Terminal;\n\t// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used in renderToScreen method on lines 260 and 276\n\tprivate previousRenderCommands: RenderCommand[] = [];\n\tprivate previousLines: string[] = []; // What we rendered last time\n\n\t// Performance metrics\n\tprivate totalLinesRedrawn = 0;\n\tprivate renderCount = 0;\n\tpublic getLinesRedrawn(): number {\n\t\treturn this.totalLinesRedrawn;\n\t}\n\tpublic getAverageLinesRedrawn(): number {\n\t\treturn this.renderCount > 0 ? this.totalLinesRedrawn / this.renderCount : 0;\n\t}\n\n\tconstructor(terminal?: Terminal) {\n\t\tsuper();\n\t\tthis.setTui(this);\n\t\tthis.handleResize = this.handleResize.bind(this);\n\t\tthis.handleKeypress = this.handleKeypress.bind(this);\n\n\t\t// Use provided terminal or default to ProcessTerminal\n\t\tthis.terminal = terminal || new ProcessTerminal();\n\t}\n\n\tsetFocus(component: Component): void {\n\t\tif (this.findComponent(component)) {\n\t\t\tthis.focusedComponent = component;\n\t\t}\n\t}\n\n\tprivate findComponent(component: Component): boolean {\n\t\tif (this.children.includes(component)) {\n\t\t\treturn true;\n\t\t}\n\n\t\tfor (const child of this.children) {\n\t\t\tif (child instanceof Container) {\n\t\t\t\tif (this.findInContainer(child, component)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate findInContainer(container: Container, component: Component): boolean {\n\t\tconst childCount = container.getChildCount();\n\n\t\tfor (let i = 0; i < childCount; i++) {\n\t\t\tconst child = container.getChild(i);\n\t\t\tif (child === component) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (child instanceof Container) {\n\t\t\t\tif (this.findInContainer(child, component)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\trequestRender(): void {\n\t\tif (!this.isStarted) return;\n\n\t\t// Only queue a render if we haven't already\n\t\tif (!this.needsRender) {\n\t\t\tthis.needsRender = true;\n\t\t\tprocess.nextTick(() => {\n\t\t\t\tif (this.needsRender) {\n\t\t\t\t\tthis.renderToScreen();\n\t\t\t\t\tthis.needsRender = false;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tstart(): void {\n\t\tthis.isStarted = true;\n\n\t\t// Hide cursor\n\t\tthis.terminal.write(\"\\x1b[?25l\");\n\n\t\t// Start terminal with handlers\n\t\ttry {\n\t\t\tthis.terminal.start(this.handleKeypress, this.handleResize);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error starting terminal:\", error);\n\t\t}\n\n\t\t// Trigger initial render if we have components\n\t\tif (this.children.length > 0) {\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tstop(): void {\n\t\t// Show cursor\n\t\tthis.terminal.write(\"\\x1b[?25h\");\n\n\t\t// Stop terminal\n\t\tthis.terminal.stop();\n\n\t\tthis.isStarted = false;\n\t}\n\n\tprivate renderToScreen(resize = false): void {\n\t\tconst termWidth = this.terminal.columns;\n\t\tconst termHeight = this.terminal.rows;\n\n\t\tif (resize) {\n\t\t\tthis.isFirstRender = true;\n\t\t\tthis.previousRenderCommands = [];\n\t\t\tthis.previousLines = [];\n\t\t}\n\n\t\t// Collect all render commands\n\t\tconst currentRenderCommands: RenderCommand[] = [];\n\t\tthis.collectRenderCommands(this, termWidth, currentRenderCommands);\n\n\t\tif (this.isFirstRender) {\n\t\t\tthis.renderInitial(currentRenderCommands);\n\t\t\tthis.isFirstRender = false;\n\t\t} else {\n\t\t\tthis.renderLineBased(currentRenderCommands, termHeight);\n\t\t}\n\n\t\t// Save for next render\n\t\tthis.previousRenderCommands = currentRenderCommands;\n\t\tthis.renderCount++;\n\t}\n\n\tprivate collectRenderCommands(container: Container, width: number, commands: RenderCommand[]): void {\n\t\tconst childCount = container.getChildCount();\n\n\t\tfor (let i = 0; i < childCount; i++) {\n\t\t\tconst child = container.getChild(i);\n\t\t\tif (!child) continue;\n\n\t\t\tconst result = child.render(width);\n\t\t\tcommands.push({\n\t\t\t\tid: child.id,\n\t\t\t\tlines: result.lines,\n\t\t\t\tchanged: result.changed,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate renderInitial(commands: RenderCommand[]): void {\n\t\tlet output = \"\";\n\t\tconst lines: string[] = [];\n\n\t\tfor (const command of commands) {\n\t\t\tlines.push(...command.lines);\n\t\t}\n\n\t\t// Output all lines\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tif (i > 0) output += \"\\r\\n\";\n\t\t\toutput += lines[i];\n\t\t}\n\n\t\t// Add final newline to position cursor below content\n\t\tif (lines.length > 0) output += \"\\r\\n\";\n\n\t\tthis.terminal.write(output);\n\n\t\t// Save what we rendered\n\t\tthis.previousLines = lines;\n\t\tthis.totalLinesRedrawn += lines.length;\n\t}\n\n\tprivate renderLineBased(currentCommands: RenderCommand[], termHeight: number): void {\n\t\tconst viewportHeight = termHeight - 1; // Leave one line for cursor\n\n\t\t// Build the new lines array\n\t\tconst newLines: string[] = [];\n\t\tfor (const command of currentCommands) {\n\t\t\tnewLines.push(...command.lines);\n\t\t}\n\n\t\tconst totalNewLines = newLines.length;\n\t\tconst totalOldLines = this.previousLines.length;\n\n\t\t// Find first changed line by comparing old and new\n\t\tlet firstChangedLine = -1;\n\t\tconst minLines = Math.min(totalOldLines, totalNewLines);\n\n\t\tfor (let i = 0; i < minLines; i++) {\n\t\t\tif (this.previousLines[i] !== newLines[i]) {\n\t\t\t\tfirstChangedLine = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// If all common lines are the same, check if we have different lengths\n\t\tif (firstChangedLine === -1 && totalOldLines !== totalNewLines) {\n\t\t\tfirstChangedLine = minLines;\n\t\t}\n\n\t\t// No changes at all\n\t\tif (firstChangedLine === -1) {\n\t\t\tthis.previousLines = newLines;\n\t\t\treturn;\n\t\t}\n\n\t\t// Calculate viewport boundaries\n\t\tconst oldViewportStart = Math.max(0, totalOldLines - viewportHeight);\n\t\tconst cursorPosition = totalOldLines; // Cursor is one line below last content\n\n\t\tlet output = \"\";\n\t\tlet linesRedrawn = 0;\n\n\t\t// Check if change is in scrollback (unreachable by cursor)\n\t\tif (firstChangedLine < oldViewportStart) {\n\t\t\t// Must do full clear and re-render\n\t\t\toutput = \"\\x1b[3J\\x1b[H\"; // Clear scrollback and screen, home cursor\n\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) output += \"\\r\\n\";\n\t\t\t\toutput += newLines[i];\n\t\t\t}\n\n\t\t\tif (newLines.length > 0) output += \"\\r\\n\";\n\t\t\tlinesRedrawn = newLines.length;\n\t\t} else {\n\t\t\t// Change is in viewport - we can reach it with cursor movements\n\t\t\t// Calculate viewport position of the change\n\t\t\tconst viewportChangePosition = firstChangedLine - oldViewportStart;\n\n\t\t\t// Move cursor to the change position\n\t\t\tconst linesToMoveUp = cursorPosition - oldViewportStart - viewportChangePosition;\n\t\t\tif (linesToMoveUp > 0) {\n\t\t\t\toutput += `\\x1b[${linesToMoveUp}A`;\n\t\t\t}\n\n\t\t\t// Now do surgical updates or partial clear based on what's more efficient\n\t\t\tlet currentLine = firstChangedLine;\n\t\t\tconst currentViewportLine = viewportChangePosition;\n\n\t\t\t// If we have significant structural changes, just clear and re-render from here\n\t\t\tconst hasSignificantChanges = totalNewLines !== totalOldLines || totalNewLines - firstChangedLine > 10; // Arbitrary threshold\n\n\t\t\tif (hasSignificantChanges) {\n\t\t\t\t// Clear from cursor to end of screen and render all remaining lines\n\t\t\t\toutput += \"\\r\\x1b[0J\";\n\n\t\t\t\tfor (let i = firstChangedLine; i < newLines.length; i++) {\n\t\t\t\t\tif (i > firstChangedLine) output += \"\\r\\n\";\n\t\t\t\t\toutput += newLines[i];\n\t\t\t\t\tlinesRedrawn++;\n\t\t\t\t}\n\n\t\t\t\tif (newLines.length > firstChangedLine) output += \"\\r\\n\";\n\t\t\t} else {\n\t\t\t\t// Do surgical line-by-line updates\n\t\t\t\tfor (let i = firstChangedLine; i < minLines; i++) {\n\t\t\t\t\tif (this.previousLines[i] !== newLines[i]) {\n\t\t\t\t\t\t// Move to this line if needed\n\t\t\t\t\t\tconst moveLines = i - currentLine;\n\t\t\t\t\t\tif (moveLines > 0) {\n\t\t\t\t\t\t\toutput += `\\x1b[${moveLines}B`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Clear and rewrite the line\n\t\t\t\t\t\toutput += \"\\r\\x1b[2K\" + newLines[i];\n\t\t\t\t\t\tcurrentLine = i;\n\t\t\t\t\t\tlinesRedrawn++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Handle added/removed lines at the end\n\t\t\t\tif (totalNewLines > totalOldLines) {\n\t\t\t\t\t// Move to end of old content and add new lines\n\t\t\t\t\tconst moveToEnd = totalOldLines - 1 - currentLine;\n\t\t\t\t\tif (moveToEnd > 0) {\n\t\t\t\t\t\toutput += `\\x1b[${moveToEnd}B`;\n\t\t\t\t\t}\n\t\t\t\t\toutput += \"\\r\\n\";\n\n\t\t\t\t\tfor (let i = totalOldLines; i < totalNewLines; i++) {\n\t\t\t\t\t\tif (i > totalOldLines) output += \"\\r\\n\";\n\t\t\t\t\t\toutput += newLines[i];\n\t\t\t\t\t\tlinesRedrawn++;\n\t\t\t\t\t}\n\t\t\t\t\toutput += \"\\r\\n\";\n\t\t\t\t} else if (totalNewLines < totalOldLines) {\n\t\t\t\t\t// Move to end of new content and clear rest\n\t\t\t\t\tconst moveToEnd = totalNewLines - 1 - currentLine;\n\t\t\t\t\tif (moveToEnd > 0) {\n\t\t\t\t\t\toutput += `\\x1b[${moveToEnd}B`;\n\t\t\t\t\t} else if (moveToEnd < 0) {\n\t\t\t\t\t\toutput += `\\x1b[${-moveToEnd}A`;\n\t\t\t\t\t}\n\t\t\t\t\toutput += \"\\r\\n\\x1b[0J\";\n\t\t\t\t} else {\n\t\t\t\t\t// Same length, just position cursor at end\n\t\t\t\t\tconst moveToEnd = totalNewLines - 1 - currentLine;\n\t\t\t\t\tif (moveToEnd > 0) {\n\t\t\t\t\t\toutput += `\\x1b[${moveToEnd}B`;\n\t\t\t\t\t} else if (moveToEnd < 0) {\n\t\t\t\t\t\toutput += `\\x1b[${-moveToEnd}A`;\n\t\t\t\t\t}\n\t\t\t\t\toutput += \"\\r\\n\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.terminal.write(output);\n\t\tthis.previousLines = newLines;\n\t\tthis.totalLinesRedrawn += linesRedrawn;\n\t}\n\n\tprivate handleResize(): void {\n\t\t// Clear screen and reset\n\t\tthis.terminal.write(\"\\x1b[2J\\x1b[H\\x1b[?25l\");\n\t\tthis.renderToScreen(true);\n\t}\n\n\tprivate handleKeypress(data: string): void {\n\t\tif (this.onGlobalKeyPress) {\n\t\t\tconst shouldForward = this.onGlobalKeyPress(data);\n\t\t\tif (!shouldForward) {\n\t\t\t\tthis.requestRender();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal TUI implementation with differential rendering\n */\n\nimport type { Terminal } from \"./terminal.js\";\nimport { visibleWidth } from \"./utils.js\";\n\n/**\n * Component interface - all components must implement this\n */\nexport interface Component {\n\t/**\n\t * Render the component to lines for the given viewport width\n\t * @param width - Current viewport width\n\t * @returns Array of strings, each representing a line\n\t */\n\trender(width: number): string[];\n\n\t/**\n\t * Optional handler for keyboard input when component has focus\n\t */\n\thandleInput?(data: string): void;\n}\n\nexport { visibleWidth };\n\n/**\n * Container - a component that contains other components\n */\nexport class Container implements Component {\n\tchildren: Component[] = [];\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tlines.push(...child.render(width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/**\n * TUI - Main class for managing terminal UI with differential rendering\n */\nexport class TUI extends Container {\n\tprivate terminal: Terminal;\n\tprivate previousLines: string[] = [];\n\tprivate previousWidth = 0;\n\tprivate focusedComponent: Component | null = null;\n\tprivate renderRequested = false;\n\tprivate cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)\n\n\tconstructor(terminal: Terminal) {\n\t\tsuper();\n\t\tthis.terminal = terminal;\n\t}\n\n\tsetFocus(component: Component | null): void {\n\t\tthis.focusedComponent = component;\n\t}\n\n\tstart(): void {\n\t\tthis.terminal.start(\n\t\t\t(data) => this.handleInput(data),\n\t\t\t() => this.requestRender(),\n\t\t);\n\t\tthis.terminal.hideCursor();\n\t\tthis.requestRender();\n\t}\n\n\tstop(): void {\n\t\tthis.terminal.showCursor();\n\t\tthis.terminal.stop();\n\t}\n\n\trequestRender(): void {\n\t\tif (this.renderRequested) return;\n\t\tthis.renderRequested = true;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.renderRequested = false;\n\t\t\tthis.doRender();\n\t\t});\n\t}\n\n\tprivate handleInput(data: string): void {\n\t\t// Pass input to focused component (including Ctrl+C)\n\t\t// The focused component can decide how to handle Ctrl+C\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tprivate doRender(): void {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\n\t\t// Render all components to get new lines\n\t\tconst newLines = this.render(width);\n\n\t\t// Width changed - need full re-render\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\n\t\t// First render - just output everything without clearing\n\t\tif (this.previousLines.length === 0) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\t// After rendering N lines, cursor is at end of last line (line N-1)\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Width changed - full re-render\n\t\tif (widthChanged) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find first and last changed lines\n\t\tlet firstChanged = -1;\n\t\tlet lastChanged = -1;\n\n\t\tconst maxLines = Math.max(newLines.length, this.previousLines.length);\n\t\tfor (let i = 0; i < maxLines; i++) {\n\t\t\tconst oldLine = i < this.previousLines.length ? this.previousLines[i] : \"\";\n\t\t\tconst newLine = i < newLines.length ? newLines[i] : \"\";\n\n\t\t\tif (oldLine !== newLine) {\n\t\t\t\tif (firstChanged === -1) {\n\t\t\t\t\tfirstChanged = i;\n\t\t\t\t}\n\t\t\t\tlastChanged = i;\n\t\t\t}\n\t\t}\n\n\t\t// No changes\n\t\tif (firstChanged === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if firstChanged is outside the viewport\n\t\t// cursorRow is the line where cursor is (0-indexed)\n\t\t// Viewport shows lines from (cursorRow - height + 1) to cursorRow\n\t\t// If firstChanged < viewportTop, we need full re-render\n\t\tconst viewportTop = this.cursorRow - height + 1;\n\t\tif (firstChanged < viewportTop) {\n\t\t\t// First change is above viewport - need full re-render\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Render from first changed line to end\n\t\t// Build buffer with all updates wrapped in synchronized output\n\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\n\t\t// Move cursor to first changed line\n\t\tconst lineDiff = firstChanged - this.cursorRow;\n\t\tif (lineDiff > 0) {\n\t\t\tbuffer += `\\x1b[${lineDiff}B`; // Move down\n\t\t} else if (lineDiff < 0) {\n\t\t\tbuffer += `\\x1b[${-lineDiff}A`; // Move up\n\t\t}\n\n\t\tbuffer += \"\\r\"; // Move to column 0\n\t\tbuffer += \"\\x1b[J\"; // Clear from cursor to end of screen\n\n\t\t// Render from first changed line to end\n\t\tfor (let i = firstChanged; i < newLines.length; i++) {\n\t\t\tif (i > firstChanged) buffer += \"\\r\\n\";\n\t\t\tif (visibleWidth(newLines[i]) > width) {\n\t\t\t\tthrow new Error(`Rendered line ${i} exceeds terminal width\\n\\n${newLines[i]}`);\n\t\t\t}\n\t\t\tbuffer += newLines[i];\n\t\t}\n\n\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\n\t\t// Write entire buffer at once\n\t\tthis.terminal.write(buffer);\n\n\t\t// Cursor is now at end of last line\n\t\tthis.cursorRow = newLines.length - 1;\n\n\t\tthis.previousLines = newLines;\n\t\tthis.previousWidth = width;\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Calculate the visible width of a string in terminal columns.
3
+ * This correctly handles:
4
+ * - ANSI escape codes (ignored)
5
+ * - Emojis and wide characters (counted as 2 columns)
6
+ * - Combining characters (counted correctly)
7
+ * - Tabs (replaced with 3 spaces for consistent width)
8
+ */
9
+ export declare function visibleWidth(str: string): number;
10
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD","sourcesContent":["import stringWidth from \"string-width\";\n\n/**\n * Calculate the visible width of a string in terminal columns.\n * This correctly handles:\n * - ANSI escape codes (ignored)\n * - Emojis and wide characters (counted as 2 columns)\n * - Combining characters (counted correctly)\n * - Tabs (replaced with 3 spaces for consistent width)\n */\nexport function visibleWidth(str: string): number {\n\t// Replace tabs with 3 spaces before measuring\n\tconst normalized = str.replace(/\\t/g, \" \");\n\treturn stringWidth(normalized);\n}\n"]}
package/dist/utils.js ADDED
@@ -0,0 +1,15 @@
1
+ import stringWidth from "string-width";
2
+ /**
3
+ * Calculate the visible width of a string in terminal columns.
4
+ * This correctly handles:
5
+ * - ANSI escape codes (ignored)
6
+ * - Emojis and wide characters (counted as 2 columns)
7
+ * - Combining characters (counted correctly)
8
+ * - Tabs (replaced with 3 spaces for consistent width)
9
+ */
10
+ export function visibleWidth(str) {
11
+ // Replace tabs with 3 spaces before measuring
12
+ const normalized = str.replace(/\t/g, " ");
13
+ return stringWidth(normalized);
14
+ }
15
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,cAAc,CAAC;AAEvC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW,EAAU;IACjD,8CAA8C;IAC9C,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC7C,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;AAAA,CAC/B","sourcesContent":["import stringWidth from \"string-width\";\n\n/**\n * Calculate the visible width of a string in terminal columns.\n * This correctly handles:\n * - ANSI escape codes (ignored)\n * - Emojis and wide characters (counted as 2 columns)\n * - Combining characters (counted correctly)\n * - Tabs (replaced with 3 spaces for consistent width)\n */\nexport function visibleWidth(str: string): number {\n\t// Replace tabs with 3 spaces before measuring\n\tconst normalized = str.replace(/\\t/g, \" \");\n\treturn stringWidth(normalized);\n}\n"]}