@rlabs-inc/tui 0.2.1 → 0.2.3
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.
package/package.json
CHANGED
package/src/api/history.ts
CHANGED
|
@@ -405,6 +405,10 @@ export function createRenderToHistory(
|
|
|
405
405
|
// Without batch, the ReactiveSet triggers updates when we allocate/release indices,
|
|
406
406
|
// causing the render effect to run mid-operation and duplicate content.
|
|
407
407
|
batch(() => {
|
|
408
|
+
// STEP 1: Erase the active area BEFORE doing anything else
|
|
409
|
+
// This clears the screen so history can be written where the active area was
|
|
410
|
+
appendRegionRenderer.eraseActive()
|
|
411
|
+
|
|
408
412
|
// Save current allocated indices BEFORE creating history components
|
|
409
413
|
const beforeIndices = new Set(getAllocatedIndices())
|
|
410
414
|
|
|
@@ -26,6 +26,10 @@ import * as ansi from './ansi'
|
|
|
26
26
|
* Erases previous active content and renders fresh.
|
|
27
27
|
*
|
|
28
28
|
* Like InlineRenderer but only erases the active area (preserves history).
|
|
29
|
+
*
|
|
30
|
+
* OVERFLOW HANDLING:
|
|
31
|
+
* - If active area fits in viewport: erase only active lines (preserves history)
|
|
32
|
+
* - If active area exceeds viewport: clear everything (can't partially erase scrollback)
|
|
29
33
|
*/
|
|
30
34
|
export class AppendRegionRenderer {
|
|
31
35
|
// Cell rendering state (for ANSI optimization)
|
|
@@ -38,19 +42,38 @@ export class AppendRegionRenderer {
|
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
44
|
* Render frame buffer as active content.
|
|
41
|
-
*
|
|
45
|
+
* Uses sync mode for flicker-free rendering.
|
|
46
|
+
*
|
|
47
|
+
* SMART ERASE STRATEGY:
|
|
48
|
+
* - If previous height <= viewport: erase only active lines (O(1) for history)
|
|
49
|
+
* - If previous height > viewport: clear terminal (history is in scrollback anyway)
|
|
42
50
|
*/
|
|
43
51
|
render(buffer: FrameBuffer): void {
|
|
44
52
|
const output = this.buildOutput(buffer)
|
|
53
|
+
const viewportHeight = process.stdout.rows || 24
|
|
54
|
+
|
|
55
|
+
// Begin synchronized output for flicker-free rendering
|
|
56
|
+
process.stdout.write(ansi.beginSync)
|
|
45
57
|
|
|
46
|
-
//
|
|
58
|
+
// Decide erase strategy based on whether content exceeds viewport
|
|
47
59
|
if (this.previousHeight > 0) {
|
|
48
|
-
|
|
60
|
+
if (this.previousHeight <= viewportHeight) {
|
|
61
|
+
// Active area fits in viewport - erase only active lines (preserves history)
|
|
62
|
+
process.stdout.write(ansi.cursorUp(this.previousHeight))
|
|
63
|
+
process.stdout.write(ansi.eraseDown)
|
|
64
|
+
} else {
|
|
65
|
+
// Active area exceeded viewport - clear terminal
|
|
66
|
+
// History that scrolled up is already in scrollback, so this is fine
|
|
67
|
+
process.stdout.write(ansi.clearTerminal)
|
|
68
|
+
}
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
// Render active content
|
|
52
72
|
process.stdout.write(output)
|
|
53
73
|
|
|
74
|
+
// End synchronized output
|
|
75
|
+
process.stdout.write(ansi.endSync)
|
|
76
|
+
|
|
54
77
|
// Track height for next render
|
|
55
78
|
// +1 because buildOutput adds trailing newline which moves cursor down one line
|
|
56
79
|
this.previousHeight = buffer.height + 1
|
|
@@ -62,7 +85,16 @@ export class AppendRegionRenderer {
|
|
|
62
85
|
*/
|
|
63
86
|
eraseActive(): void {
|
|
64
87
|
if (this.previousHeight > 0) {
|
|
65
|
-
process.stdout.
|
|
88
|
+
const viewportHeight = process.stdout.rows || 24
|
|
89
|
+
|
|
90
|
+
if (this.previousHeight <= viewportHeight) {
|
|
91
|
+
// Active area fits in viewport - erase only active lines
|
|
92
|
+
process.stdout.write(ansi.cursorUp(this.previousHeight))
|
|
93
|
+
process.stdout.write(ansi.eraseDown)
|
|
94
|
+
} else {
|
|
95
|
+
// Active area exceeded viewport - clear terminal
|
|
96
|
+
process.stdout.write(ansi.clearTerminal)
|
|
97
|
+
}
|
|
66
98
|
this.previousHeight = 0
|
|
67
99
|
}
|
|
68
100
|
}
|
package/src/renderer/output.ts
CHANGED
|
@@ -191,8 +191,8 @@ export class DiffRenderer {
|
|
|
191
191
|
const prev = this.previousBuffer
|
|
192
192
|
let hasChanges = false
|
|
193
193
|
|
|
194
|
-
//
|
|
195
|
-
|
|
194
|
+
// Begin synchronized output for flicker-free rendering
|
|
195
|
+
this.output.write(ansi.beginSync)
|
|
196
196
|
|
|
197
197
|
// Reset cell renderer state at start of frame
|
|
198
198
|
this.cellRenderer.reset()
|
|
@@ -341,8 +341,10 @@ export class InlineRenderer {
|
|
|
341
341
|
return
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
//
|
|
344
|
+
// Synchronized output: wrap in begin/end sync for flicker-free rendering
|
|
345
|
+
this.output.write(ansi.beginSync)
|
|
345
346
|
this.output.write(ansi.clearTerminal + output)
|
|
347
|
+
this.output.write(ansi.endSync)
|
|
346
348
|
this.output.flushSync()
|
|
347
349
|
|
|
348
350
|
this.previousOutput = output
|