linecraft 0.1.0 → 0.2.0
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/LICENSE +1 -1
- package/README.md +313 -64
- package/lib/api/color.d.ts +6 -0
- package/lib/api/color.d.ts.map +1 -0
- package/lib/api/color.js +9 -0
- package/lib/api/color.js.map +1 -0
- package/lib/api/color.test.d.ts +2 -0
- package/lib/api/color.test.d.ts.map +1 -0
- package/lib/api/color.test.js +23 -0
- package/lib/api/color.test.js.map +1 -0
- package/lib/api/flex.d.ts +55 -0
- package/lib/api/flex.d.ts.map +1 -0
- package/lib/api/flex.js +86 -0
- package/lib/api/flex.js.map +1 -0
- package/lib/api/flex.test.d.ts +2 -0
- package/lib/api/flex.test.d.ts.map +1 -0
- package/lib/api/flex.test.js +82 -0
- package/lib/api/flex.test.js.map +1 -0
- package/lib/api/grid.d.ts +22 -0
- package/lib/api/grid.d.ts.map +1 -0
- package/lib/api/grid.js +65 -0
- package/lib/api/grid.js.map +1 -0
- package/lib/api/grid.test.d.ts +2 -0
- package/lib/api/grid.test.d.ts.map +1 -0
- package/lib/api/grid.test.js +48 -0
- package/lib/api/grid.test.js.map +1 -0
- package/lib/components/base.d.ts +53 -0
- package/lib/components/base.d.ts.map +1 -0
- package/lib/components/base.js +47 -0
- package/lib/components/base.js.map +1 -0
- package/lib/components/col.d.ts +35 -0
- package/lib/components/col.d.ts.map +1 -0
- package/lib/components/col.js +168 -0
- package/lib/components/col.js.map +1 -0
- package/lib/components/col.test.d.ts +2 -0
- package/lib/components/col.test.d.ts.map +1 -0
- package/lib/components/col.test.js +96 -0
- package/lib/components/col.test.js.map +1 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +5 -0
- package/lib/components/index.js.map +1 -0
- package/lib/components/progress-bar-grid.d.ts +20 -0
- package/lib/components/progress-bar-grid.d.ts.map +1 -0
- package/lib/components/progress-bar-grid.js +44 -0
- package/lib/components/progress-bar-grid.js.map +1 -0
- package/lib/components/progress-bar-grid.test.d.ts +2 -0
- package/lib/components/progress-bar-grid.test.d.ts.map +1 -0
- package/lib/components/progress-bar-grid.test.js +101 -0
- package/lib/components/progress-bar-grid.test.js.map +1 -0
- package/lib/components/progress-bar.d.ts +26 -3
- package/lib/components/progress-bar.d.ts.map +1 -1
- package/lib/components/progress-bar.js +62 -6
- package/lib/components/progress-bar.js.map +1 -1
- package/lib/components/progress-bar.test.d.ts +2 -0
- package/lib/components/progress-bar.test.d.ts.map +1 -0
- package/lib/components/progress-bar.test.js +153 -0
- package/lib/components/progress-bar.test.js.map +1 -0
- package/lib/components/prompt.d.ts +14 -0
- package/lib/components/prompt.d.ts.map +1 -0
- package/lib/components/prompt.js +77 -0
- package/lib/components/prompt.js.map +1 -0
- package/lib/components/renderable.d.ts +42 -0
- package/lib/components/renderable.d.ts.map +1 -0
- package/lib/components/renderable.js +225 -0
- package/lib/components/renderable.js.map +1 -0
- package/lib/components/spinner.d.ts +2 -2
- package/lib/components/spinner.d.ts.map +1 -1
- package/lib/components/spinner.js +3 -1
- package/lib/components/spinner.js.map +1 -1
- package/lib/components/spinner.test.d.ts +2 -0
- package/lib/components/spinner.test.d.ts.map +1 -0
- package/lib/components/spinner.test.js +168 -0
- package/lib/components/spinner.test.js.map +1 -0
- package/lib/components/style.d.ts +16 -0
- package/lib/components/style.d.ts.map +1 -0
- package/lib/components/style.js +72 -0
- package/lib/components/style.js.map +1 -0
- package/lib/components/style.test.d.ts +2 -0
- package/lib/components/style.test.d.ts.map +1 -0
- package/lib/components/style.test.js +135 -0
- package/lib/components/style.test.js.map +1 -0
- package/lib/components/text.d.ts +24 -0
- package/lib/components/text.d.ts.map +1 -0
- package/lib/components/text.js +65 -0
- package/lib/components/text.js.map +1 -0
- package/lib/drawing/boxes.d.ts +33 -0
- package/lib/drawing/boxes.d.ts.map +1 -0
- package/lib/drawing/boxes.js +76 -0
- package/lib/drawing/boxes.js.map +1 -0
- package/lib/index.d.ts +18 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +21 -10
- package/lib/index.js.map +1 -1
- package/lib/index.test.d.ts +2 -0
- package/lib/index.test.d.ts.map +1 -0
- package/lib/index.test.js +80 -0
- package/lib/index.test.js.map +1 -0
- package/lib/layout/flex.d.ts +39 -0
- package/lib/layout/flex.d.ts.map +1 -0
- package/lib/layout/flex.js +300 -0
- package/lib/layout/flex.js.map +1 -0
- package/lib/layout/flex.test.d.ts +2 -0
- package/lib/layout/flex.test.d.ts.map +1 -0
- package/lib/layout/flex.test.js +206 -0
- package/lib/layout/flex.test.js.map +1 -0
- package/lib/layout/grid.d.ts +53 -0
- package/lib/layout/grid.d.ts.map +1 -0
- package/lib/layout/grid.js +421 -0
- package/lib/layout/grid.js.map +1 -0
- package/lib/layout/grid.test.d.ts +2 -0
- package/lib/layout/grid.test.d.ts.map +1 -0
- package/lib/layout/grid.test.js +139 -0
- package/lib/layout/grid.test.js.map +1 -0
- package/lib/native/ansi.d.ts +104 -0
- package/lib/native/ansi.d.ts.map +1 -0
- package/lib/native/ansi.js +120 -0
- package/lib/native/ansi.js.map +1 -0
- package/lib/native/ansi.test.d.ts +2 -0
- package/lib/native/ansi.test.d.ts.map +1 -0
- package/lib/native/ansi.test.js +57 -0
- package/lib/native/ansi.test.js.map +1 -0
- package/lib/native/buffer.d.ts +32 -0
- package/lib/native/buffer.d.ts.map +1 -0
- package/lib/native/buffer.js +49 -0
- package/lib/native/buffer.js.map +1 -0
- package/lib/native/buffer.test.d.ts +2 -0
- package/lib/native/buffer.test.d.ts.map +1 -0
- package/lib/native/buffer.test.js +64 -0
- package/lib/native/buffer.test.js.map +1 -0
- package/lib/native/diff.d.ts +20 -0
- package/lib/native/diff.d.ts.map +1 -0
- package/lib/native/diff.js +33 -0
- package/lib/native/diff.js.map +1 -0
- package/lib/native/diff.test.d.ts +2 -0
- package/lib/native/diff.test.d.ts.map +1 -0
- package/lib/native/diff.test.js +106 -0
- package/lib/native/diff.test.js.map +1 -0
- package/lib/native/region-old.d.ts +117 -0
- package/lib/native/region-old.d.ts.map +1 -0
- package/lib/native/region-old.js +539 -0
- package/lib/native/region-old.js.map +1 -0
- package/lib/native/region-renderer.d.ts +167 -0
- package/lib/native/region-renderer.d.ts.map +1 -0
- package/lib/native/region-renderer.js +1238 -0
- package/lib/native/region-renderer.js.map +1 -0
- package/lib/native/region-simple.d.ts +44 -0
- package/lib/native/region-simple.d.ts.map +1 -0
- package/lib/native/region-simple.js +290 -0
- package/lib/native/region-simple.js.map +1 -0
- package/lib/native/region.d.ts +53 -0
- package/lib/native/region.d.ts.map +1 -0
- package/lib/native/region.js +426 -0
- package/lib/native/region.js.map +1 -0
- package/lib/native/region.test.d.ts +2 -0
- package/lib/native/region.test.d.ts.map +1 -0
- package/lib/native/region.test.js +248 -0
- package/lib/native/region.test.js.map +1 -0
- package/lib/native/throttle.d.ts +29 -0
- package/lib/native/throttle.d.ts.map +1 -0
- package/lib/native/throttle.js +57 -0
- package/lib/native/throttle.js.map +1 -0
- package/lib/native/throttle.test.d.ts +2 -0
- package/lib/native/throttle.test.d.ts.map +1 -0
- package/lib/native/throttle.test.js +86 -0
- package/lib/native/throttle.test.js.map +1 -0
- package/lib/native.d.ts +5 -11
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +8 -64
- package/lib/native.js.map +1 -1
- package/lib/region.d.ts +48 -5
- package/lib/region.d.ts.map +1 -1
- package/lib/region.js +474 -36
- package/lib/region.js.map +1 -1
- package/lib/region.test.d.ts +2 -0
- package/lib/region.test.d.ts.map +1 -0
- package/lib/region.test.js +227 -0
- package/lib/region.test.js.map +1 -0
- package/lib/region.visual.test.d.ts +2 -0
- package/lib/region.visual.test.d.ts.map +1 -0
- package/lib/region.visual.test.js +55 -0
- package/lib/region.visual.test.js.map +1 -0
- package/lib/test-helpers/capturable-terminal.d.ts +61 -0
- package/lib/test-helpers/capturable-terminal.d.ts.map +1 -0
- package/lib/test-helpers/capturable-terminal.js +113 -0
- package/lib/test-helpers/capturable-terminal.js.map +1 -0
- package/lib/test-helpers/capturable-terminal.test.d.ts +2 -0
- package/lib/test-helpers/capturable-terminal.test.d.ts.map +1 -0
- package/lib/test-helpers/capturable-terminal.test.js +45 -0
- package/lib/test-helpers/capturable-terminal.test.js.map +1 -0
- package/lib/test-helpers/mock-region.d.ts +21 -0
- package/lib/test-helpers/mock-region.d.ts.map +1 -0
- package/lib/test-helpers/mock-region.js +37 -0
- package/lib/test-helpers/mock-region.js.map +1 -0
- package/lib/test-helpers/virtual-terminal-diff-reflow.test.d.ts +2 -0
- package/lib/test-helpers/virtual-terminal-diff-reflow.test.d.ts.map +1 -0
- package/lib/test-helpers/virtual-terminal-diff-reflow.test.js +256 -0
- package/lib/test-helpers/virtual-terminal-diff-reflow.test.js.map +1 -0
- package/lib/test-helpers/virtual-terminal-flex-resize.test.d.ts +2 -0
- package/lib/test-helpers/virtual-terminal-flex-resize.test.d.ts.map +1 -0
- package/lib/test-helpers/virtual-terminal-flex-resize.test.js +438 -0
- package/lib/test-helpers/virtual-terminal-flex-resize.test.js.map +1 -0
- package/lib/test-helpers/virtual-terminal.d.ts +176 -0
- package/lib/test-helpers/virtual-terminal.d.ts.map +1 -0
- package/lib/test-helpers/virtual-terminal.js +492 -0
- package/lib/test-helpers/virtual-terminal.js.map +1 -0
- package/lib/test-helpers/virtual-terminal.test.d.ts +2 -0
- package/lib/test-helpers/virtual-terminal.test.d.ts.map +1 -0
- package/lib/test-helpers/virtual-terminal.test.js +219 -0
- package/lib/test-helpers/virtual-terminal.test.js.map +1 -0
- package/lib/ts/components/spinner.test.d.ts +2 -0
- package/lib/ts/components/spinner.test.d.ts.map +1 -0
- package/lib/ts/components/spinner.test.js +168 -0
- package/lib/ts/components/spinner.test.js.map +1 -0
- package/lib/ts/utils/colors.test.d.ts +2 -0
- package/lib/ts/utils/colors.test.d.ts.map +1 -0
- package/lib/ts/utils/colors.test.js +87 -0
- package/lib/ts/utils/colors.test.js.map +1 -0
- package/lib/types.d.ts +8 -2
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/colors-simple.d.ts +51 -0
- package/lib/utils/colors-simple.d.ts.map +1 -0
- package/lib/utils/colors-simple.js +59 -0
- package/lib/utils/colors-simple.js.map +1 -0
- package/lib/utils/colors.d.ts +1 -1
- package/lib/utils/colors.d.ts.map +1 -1
- package/lib/utils/colors.js.map +1 -1
- package/lib/utils/colors.test.d.ts +2 -0
- package/lib/utils/colors.test.d.ts.map +1 -0
- package/lib/utils/colors.test.js +87 -0
- package/lib/utils/colors.test.js.map +1 -0
- package/lib/utils/cursor-position.d.ts +22 -0
- package/lib/utils/cursor-position.d.ts.map +1 -0
- package/lib/utils/cursor-position.js +139 -0
- package/lib/utils/cursor-position.js.map +1 -0
- package/lib/utils/debug-log.d.ts +2 -0
- package/lib/utils/debug-log.d.ts.map +1 -0
- package/lib/utils/debug-log.js +36 -0
- package/lib/utils/debug-log.js.map +1 -0
- package/lib/utils/terminal.d.ts +27 -0
- package/lib/utils/terminal.d.ts.map +1 -0
- package/lib/utils/terminal.js +116 -0
- package/lib/utils/terminal.js.map +1 -0
- package/lib/utils/text.d.ts +21 -0
- package/lib/utils/text.d.ts.map +1 -0
- package/lib/utils/text.js +82 -0
- package/lib/utils/text.js.map +1 -0
- package/lib/utils/wait-for-spacebar.d.ts +10 -0
- package/lib/utils/wait-for-spacebar.d.ts.map +1 -0
- package/lib/utils/wait-for-spacebar.js +71 -0
- package/lib/utils/wait-for-spacebar.js.map +1 -0
- package/package.json +31 -28
- package/.cursor/plan.md +0 -952
- package/TESTING.md +0 -102
- package/build.zig +0 -100
- package/examples/basic-progress.ts +0 -21
- package/examples/multi-lane.ts +0 -29
- package/examples/spinner.ts +0 -20
- package/examples/test-basic.ts +0 -23
- package/src/ts/components/progress-bar.ts +0 -53
- package/src/ts/components/spinner.ts +0 -56
- package/src/ts/index.ts +0 -37
- package/src/ts/native.ts +0 -86
- package/src/ts/region.ts +0 -89
- package/src/ts/types/ffi-napi.d.ts +0 -11
- package/src/ts/types/ref-napi.d.ts +0 -5
- package/src/ts/types.ts +0 -53
- package/src/ts/utils/colors.ts +0 -72
- package/src/zig/ansi.zig +0 -21
- package/src/zig/buffer.zig +0 -37
- package/src/zig/diff.zig +0 -43
- package/src/zig/region.zig +0 -292
- package/src/zig/renderer.zig +0 -92
- package/src/zig/test_ansi.zig +0 -66
- package/src/zig/test_buffer.zig +0 -82
- package/src/zig/test_diff.zig +0 -220
- package/src/zig/test_integration.zig +0 -76
- package/src/zig/test_region.zig +0 -191
- package/src/zig/test_runner.zig +0 -27
- package/src/zig/test_throttle.zig +0 -59
- package/src/zig/throttle.zig +0 -38
- package/tsconfig.json +0 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.test.js","sourceRoot":"","sources":["../../src/native/diff.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export interface RegionOptions {
|
|
2
|
+
width?: number;
|
|
3
|
+
height?: number;
|
|
4
|
+
stdout?: NodeJS.WriteStream;
|
|
5
|
+
disableRendering?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* TerminalRegion manages a rectangular region of the terminal.
|
|
9
|
+
*
|
|
10
|
+
* The region reserves new lines at the bottom of the terminal and only
|
|
11
|
+
* updates within those reserved lines. This prevents overwriting existing
|
|
12
|
+
* terminal content.
|
|
13
|
+
*
|
|
14
|
+
* Performance optimizations:
|
|
15
|
+
* - Frame diffing to minimize writes
|
|
16
|
+
* - Throttling to limit render frequency
|
|
17
|
+
* - Batched writes to stdout
|
|
18
|
+
* - Efficient string operations
|
|
19
|
+
* - Relative cursor movements (no absolute positioning)
|
|
20
|
+
*/
|
|
21
|
+
export declare class TerminalRegion {
|
|
22
|
+
private width;
|
|
23
|
+
private height;
|
|
24
|
+
private pendingFrame;
|
|
25
|
+
private previousFrame;
|
|
26
|
+
private renderScheduled;
|
|
27
|
+
private throttle;
|
|
28
|
+
private renderBuffer;
|
|
29
|
+
private stdout;
|
|
30
|
+
private disableRendering;
|
|
31
|
+
private isInitialized;
|
|
32
|
+
private resizeCleanup?;
|
|
33
|
+
private widthExplicitlySet;
|
|
34
|
+
private savedCursorPosition;
|
|
35
|
+
private autoWrapDisabled;
|
|
36
|
+
private startRow;
|
|
37
|
+
private lastRenderedHeight;
|
|
38
|
+
constructor(options?: RegionOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Set up process exit handler to automatically clean up the region
|
|
41
|
+
*/
|
|
42
|
+
private setupExitHandler;
|
|
43
|
+
/**
|
|
44
|
+
* Set up resize event handler to react to terminal size changes
|
|
45
|
+
*/
|
|
46
|
+
private setupResizeHandler;
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the region by reserving new lines at the bottom of the terminal.
|
|
49
|
+
* This appends new lines so the region doesn't overwrite existing content.
|
|
50
|
+
*
|
|
51
|
+
* Also disables terminal auto-wrap so we can manage all wrapping ourselves.
|
|
52
|
+
*/
|
|
53
|
+
private initializeRegion;
|
|
54
|
+
/**
|
|
55
|
+
* Expand region to accommodate more lines
|
|
56
|
+
*/
|
|
57
|
+
private expandTo;
|
|
58
|
+
/**
|
|
59
|
+
* Get a single line (1-based line numbers)
|
|
60
|
+
* Returns empty string if line doesn't exist
|
|
61
|
+
*/
|
|
62
|
+
getLine(lineNumber: number): string;
|
|
63
|
+
/**
|
|
64
|
+
* Set a single line (1-based line numbers)
|
|
65
|
+
*
|
|
66
|
+
* Note: With auto-wrap disabled, we manage all wrapping ourselves.
|
|
67
|
+
* This method sets a single line - if content needs to wrap, it should
|
|
68
|
+
* be handled by the component layer (col, flex, etc.) before calling this.
|
|
69
|
+
*/
|
|
70
|
+
setLine(lineNumber: number, content: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Set entire content (multiple lines with \n separators)
|
|
73
|
+
*/
|
|
74
|
+
set(content: string): void;
|
|
75
|
+
/**
|
|
76
|
+
* Schedule a render (respects throttle)
|
|
77
|
+
*/
|
|
78
|
+
private scheduleRender;
|
|
79
|
+
/**
|
|
80
|
+
* Copy pending frame to previous frame
|
|
81
|
+
*/
|
|
82
|
+
private copyPendingToPrevious;
|
|
83
|
+
/**
|
|
84
|
+
* Render immediately (bypasses throttle)
|
|
85
|
+
* Uses relative cursor movements to update only within reserved lines
|
|
86
|
+
*/
|
|
87
|
+
renderNow(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Force immediate render of pending updates (bypasses throttle)
|
|
90
|
+
*/
|
|
91
|
+
flush(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Set throttle FPS
|
|
94
|
+
*/
|
|
95
|
+
setThrottleFps(fps: number): void;
|
|
96
|
+
/**
|
|
97
|
+
* Clear a single line (1-based)
|
|
98
|
+
*/
|
|
99
|
+
clearLine(lineNumber: number): void;
|
|
100
|
+
/**
|
|
101
|
+
* Clear entire region
|
|
102
|
+
*/
|
|
103
|
+
clear(): void;
|
|
104
|
+
/**
|
|
105
|
+
* Destroy the region (cleanup)
|
|
106
|
+
* Automatically deletes any blank lines from the terminal, but preserves content
|
|
107
|
+
*
|
|
108
|
+
* @param clearFirst - If true, clears the region before destroying (default: false)
|
|
109
|
+
*
|
|
110
|
+
* Note: This is automatically called on process exit, but you can also call it explicitly
|
|
111
|
+
* to clean up resources earlier (e.g., before continuing with other terminal output)
|
|
112
|
+
*/
|
|
113
|
+
destroy(clearFirst?: boolean): void;
|
|
114
|
+
getWidth(): number;
|
|
115
|
+
getHeight(): number;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=region-old.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"region-old.d.ts","sourceRoot":"","sources":["../../src/native/region-old.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,kBAAkB,CAAa;gBAE3B,OAAO,GAAE,aAAkB;IAgCvC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA2BxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoE1B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAuBxB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAuBhB;;;OAGG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAanC;;;;;;OAMG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IA6BlD;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IA8B1B;;OAEG;IACH,OAAO,CAAC,cAAc;IActB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAK7B;;;OAGG;IACH,SAAS,IAAI,IAAI;IA6JjB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIjC;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOnC;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU,GAAE,OAAe,GAAG,IAAI;IA+D1C,QAAQ,IAAI,MAAM;IAIlB,SAAS,IAAI,MAAM;CAGpB"}
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
// Region management for terminal rendering - TypeScript implementation
|
|
2
|
+
// Optimized for Node.js stdout performance
|
|
3
|
+
import * as ansi from './ansi';
|
|
4
|
+
import { RenderBuffer } from './buffer';
|
|
5
|
+
import { Throttle } from './throttle';
|
|
6
|
+
import { getTerminalWidth, onResize } from '../utils/terminal';
|
|
7
|
+
/**
|
|
8
|
+
* TerminalRegion manages a rectangular region of the terminal.
|
|
9
|
+
*
|
|
10
|
+
* The region reserves new lines at the bottom of the terminal and only
|
|
11
|
+
* updates within those reserved lines. This prevents overwriting existing
|
|
12
|
+
* terminal content.
|
|
13
|
+
*
|
|
14
|
+
* Performance optimizations:
|
|
15
|
+
* - Frame diffing to minimize writes
|
|
16
|
+
* - Throttling to limit render frequency
|
|
17
|
+
* - Batched writes to stdout
|
|
18
|
+
* - Efficient string operations
|
|
19
|
+
* - Relative cursor movements (no absolute positioning)
|
|
20
|
+
*/
|
|
21
|
+
export class TerminalRegion {
|
|
22
|
+
width;
|
|
23
|
+
height;
|
|
24
|
+
pendingFrame = [];
|
|
25
|
+
previousFrame = [];
|
|
26
|
+
renderScheduled = false;
|
|
27
|
+
throttle;
|
|
28
|
+
renderBuffer;
|
|
29
|
+
stdout;
|
|
30
|
+
disableRendering;
|
|
31
|
+
isInitialized = false;
|
|
32
|
+
resizeCleanup;
|
|
33
|
+
widthExplicitlySet;
|
|
34
|
+
savedCursorPosition = false; // Track if we've saved the cursor position
|
|
35
|
+
autoWrapDisabled = false; // Track if we've disabled terminal auto-wrap
|
|
36
|
+
startRow = null; // Absolute terminal row where region starts (1-based)
|
|
37
|
+
lastRenderedHeight = 0; // Track height from last render to detect changes
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
this.widthExplicitlySet = options.width !== undefined;
|
|
40
|
+
this.width = options.width ?? getTerminalWidth();
|
|
41
|
+
this.height = options.height ?? 1;
|
|
42
|
+
this.lastRenderedHeight = this.height; // Initialize to current height
|
|
43
|
+
this.stdout = options.stdout ?? process.stdout;
|
|
44
|
+
this.disableRendering = options.disableRendering ?? false;
|
|
45
|
+
// Initialize frames with empty lines
|
|
46
|
+
this.pendingFrame = Array(this.height).fill('');
|
|
47
|
+
this.previousFrame = Array(this.height).fill('');
|
|
48
|
+
this.throttle = new Throttle(60); // Default 60 FPS
|
|
49
|
+
this.renderBuffer = new RenderBuffer(this.stdout);
|
|
50
|
+
// Reserve space for the region by printing newlines
|
|
51
|
+
if (!this.disableRendering) {
|
|
52
|
+
this.initializeRegion();
|
|
53
|
+
}
|
|
54
|
+
// Set up resize handling if width was not explicitly set (auto-resize enabled)
|
|
55
|
+
if (!this.widthExplicitlySet && !this.disableRendering) {
|
|
56
|
+
this.setupResizeHandler();
|
|
57
|
+
}
|
|
58
|
+
// Set up automatic cleanup on process exit
|
|
59
|
+
// This ensures regions are properly cleaned up even if destroy() isn't called
|
|
60
|
+
if (!this.disableRendering) {
|
|
61
|
+
this.setupExitHandler();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set up process exit handler to automatically clean up the region
|
|
66
|
+
*/
|
|
67
|
+
setupExitHandler() {
|
|
68
|
+
// Use a weak reference pattern - store a cleanup function
|
|
69
|
+
// that will be called on process exit
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
if (this.isInitialized) {
|
|
72
|
+
this.destroy();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
// Register cleanup on various exit events
|
|
76
|
+
process.once('exit', cleanup);
|
|
77
|
+
process.once('SIGINT', cleanup);
|
|
78
|
+
process.once('SIGTERM', cleanup);
|
|
79
|
+
// Also handle uncaught exceptions (but don't prevent default behavior)
|
|
80
|
+
const originalUncaughtException = process.listeners('uncaughtException');
|
|
81
|
+
process.once('uncaughtException', (error, origin) => {
|
|
82
|
+
cleanup();
|
|
83
|
+
// Re-emit if there were other listeners
|
|
84
|
+
if (originalUncaughtException.length > 0) {
|
|
85
|
+
originalUncaughtException.forEach((listener) => {
|
|
86
|
+
listener(error, origin);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set up resize event handler to react to terminal size changes
|
|
93
|
+
*/
|
|
94
|
+
setupResizeHandler() {
|
|
95
|
+
// If a custom stdout is provided (e.g., for testing),
|
|
96
|
+
// listen to its resize events. Otherwise, use the global onResize utility.
|
|
97
|
+
if (this.stdout && this.stdout !== process.stdout) {
|
|
98
|
+
// Custom stdout - listen to its resize events directly
|
|
99
|
+
const resizeHandler = () => {
|
|
100
|
+
// Re-disable auto-wrap on resize (some terminals reset state on resize)
|
|
101
|
+
if (!this.disableRendering && this.autoWrapDisabled) {
|
|
102
|
+
this.stdout.write(ansi.DISABLE_AUTO_WRAP);
|
|
103
|
+
}
|
|
104
|
+
// Only auto-resize if width wasn't explicitly set by the user
|
|
105
|
+
if (!this.widthExplicitlySet) {
|
|
106
|
+
const oldWidth = this.width;
|
|
107
|
+
// Read width directly from the custom stdout
|
|
108
|
+
// CRITICAL: Apply the same margin as getTerminalWidth() - leave last column empty
|
|
109
|
+
const rawWidth = this.stdout.isTTY && this.stdout.columns
|
|
110
|
+
? this.stdout.columns
|
|
111
|
+
: this.width;
|
|
112
|
+
const actualWidth = Math.max(1, rawWidth - 1);
|
|
113
|
+
// Only update if it actually changed
|
|
114
|
+
if (actualWidth !== oldWidth) {
|
|
115
|
+
this.width = actualWidth;
|
|
116
|
+
// Don't auto-render here - the high-level API (or user code) will handle rebuilding
|
|
117
|
+
// flex layouts and other dynamic content with the new width
|
|
118
|
+
// This prevents rendering broken layouts before they're rebuilt
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
this.stdout.on('resize', resizeHandler);
|
|
123
|
+
this.resizeCleanup = () => {
|
|
124
|
+
this.stdout.off('resize', resizeHandler);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Use the global onResize utility (for real process.stdout)
|
|
129
|
+
this.resizeCleanup = onResize((newWidth, newHeight) => {
|
|
130
|
+
// Re-disable auto-wrap on resize (some terminals reset state on resize)
|
|
131
|
+
// This ensures content doesn't reflow automatically
|
|
132
|
+
// Write directly to stdout since this happens outside of render cycle
|
|
133
|
+
if (!this.disableRendering && this.autoWrapDisabled) {
|
|
134
|
+
this.stdout.write(ansi.DISABLE_AUTO_WRAP);
|
|
135
|
+
}
|
|
136
|
+
// Only auto-resize if width wasn't explicitly set by the user
|
|
137
|
+
if (!this.widthExplicitlySet) {
|
|
138
|
+
const oldWidth = this.width;
|
|
139
|
+
// Read width directly from stdout to ensure we get the latest value
|
|
140
|
+
// Node.js updates process.stdout.columns when resize happens
|
|
141
|
+
// CRITICAL: Apply the same margin as getTerminalWidth() - leave last column empty
|
|
142
|
+
const rawWidth = process.stdout.isTTY && process.stdout.columns
|
|
143
|
+
? process.stdout.columns
|
|
144
|
+
: newWidth;
|
|
145
|
+
const actualWidth = Math.max(1, rawWidth - 1);
|
|
146
|
+
// Only update if it actually changed
|
|
147
|
+
if (actualWidth !== oldWidth) {
|
|
148
|
+
this.width = actualWidth;
|
|
149
|
+
// Don't auto-render here - the high-level API (or user code) will handle rebuilding
|
|
150
|
+
// flex layouts and other dynamic content with the new width
|
|
151
|
+
// This prevents rendering broken layouts before they're rebuilt
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Initialize the region by reserving new lines at the bottom of the terminal.
|
|
159
|
+
* This appends new lines so the region doesn't overwrite existing content.
|
|
160
|
+
*
|
|
161
|
+
* Also disables terminal auto-wrap so we can manage all wrapping ourselves.
|
|
162
|
+
*/
|
|
163
|
+
initializeRegion() {
|
|
164
|
+
// Disable terminal auto-wrap - we'll manage all wrapping ourselves
|
|
165
|
+
// This makes reflow math much easier because we control it, not the terminal
|
|
166
|
+
// IMPORTANT: Write directly to stdout and flush immediately so it takes effect before any content
|
|
167
|
+
if (!this.disableRendering && !this.autoWrapDisabled) {
|
|
168
|
+
this.stdout.write(ansi.DISABLE_AUTO_WRAP);
|
|
169
|
+
this.autoWrapDisabled = true;
|
|
170
|
+
}
|
|
171
|
+
if (this.isInitialized)
|
|
172
|
+
return;
|
|
173
|
+
// Reserve space by printing newlines (this moves cursor down)
|
|
174
|
+
// Each newline reserves one line for the region
|
|
175
|
+
for (let i = 0; i < this.height; i++) {
|
|
176
|
+
this.stdout.write('\n');
|
|
177
|
+
}
|
|
178
|
+
// After printing newlines, cursor is at the start of the line after the region
|
|
179
|
+
// Save this position - it's our anchor point for relative positioning
|
|
180
|
+
this.stdout.write(ansi.SAVE_CURSOR);
|
|
181
|
+
this.savedCursorPosition = true;
|
|
182
|
+
this.isInitialized = true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Expand region to accommodate more lines
|
|
186
|
+
*/
|
|
187
|
+
expandTo(newHeight) {
|
|
188
|
+
const oldHeight = this.height;
|
|
189
|
+
this.height = newHeight;
|
|
190
|
+
// Expand pending frame
|
|
191
|
+
while (this.pendingFrame.length < newHeight) {
|
|
192
|
+
this.pendingFrame.push('');
|
|
193
|
+
}
|
|
194
|
+
// Expand previous frame
|
|
195
|
+
while (this.previousFrame.length < newHeight) {
|
|
196
|
+
this.previousFrame.push('');
|
|
197
|
+
}
|
|
198
|
+
// If we need more lines and region is initialized, reserve additional space
|
|
199
|
+
if (this.isInitialized && newHeight > oldHeight && !this.disableRendering) {
|
|
200
|
+
const additionalLines = newHeight - oldHeight;
|
|
201
|
+
for (let i = 0; i < additionalLines; i++) {
|
|
202
|
+
this.stdout.write('\n');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get a single line (1-based line numbers)
|
|
208
|
+
* Returns empty string if line doesn't exist
|
|
209
|
+
*/
|
|
210
|
+
getLine(lineNumber) {
|
|
211
|
+
if (lineNumber < 1) {
|
|
212
|
+
throw new Error('Line numbers start at 1');
|
|
213
|
+
}
|
|
214
|
+
const lineIndex = lineNumber - 1;
|
|
215
|
+
if (lineIndex >= this.pendingFrame.length) {
|
|
216
|
+
return '';
|
|
217
|
+
}
|
|
218
|
+
return this.pendingFrame[lineIndex] || '';
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Set a single line (1-based line numbers)
|
|
222
|
+
*
|
|
223
|
+
* Note: With auto-wrap disabled, we manage all wrapping ourselves.
|
|
224
|
+
* This method sets a single line - if content needs to wrap, it should
|
|
225
|
+
* be handled by the component layer (col, flex, etc.) before calling this.
|
|
226
|
+
*/
|
|
227
|
+
setLine(lineNumber, content) {
|
|
228
|
+
if (lineNumber < 1) {
|
|
229
|
+
throw new Error('Line numbers start at 1');
|
|
230
|
+
}
|
|
231
|
+
const lineIndex = lineNumber - 1;
|
|
232
|
+
// CRITICAL: Don't expand beyond current height if height was explicitly set
|
|
233
|
+
// The region.set() method for components sets the height explicitly,
|
|
234
|
+
// so we should not expand here. Only expand if we're in "auto-expand" mode.
|
|
235
|
+
// For now, we'll allow expansion but the caller (region.set) will truncate after rendering.
|
|
236
|
+
if (lineIndex >= this.height) {
|
|
237
|
+
this.expandTo(lineIndex + 1);
|
|
238
|
+
// Update height to match
|
|
239
|
+
this.height = lineIndex + 1;
|
|
240
|
+
}
|
|
241
|
+
// Ensure pending frame has enough lines
|
|
242
|
+
while (this.pendingFrame.length <= lineIndex) {
|
|
243
|
+
this.pendingFrame.push('');
|
|
244
|
+
}
|
|
245
|
+
// Update the line
|
|
246
|
+
this.pendingFrame[lineIndex] = content;
|
|
247
|
+
// Schedule render
|
|
248
|
+
this.scheduleRender();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Set entire content (multiple lines with \n separators)
|
|
252
|
+
*/
|
|
253
|
+
set(content) {
|
|
254
|
+
// Split by newlines
|
|
255
|
+
const lines = content.split('\n');
|
|
256
|
+
// Expand region if needed
|
|
257
|
+
if (lines.length > this.height) {
|
|
258
|
+
this.expandTo(lines.length);
|
|
259
|
+
}
|
|
260
|
+
// Update all lines in pending frame
|
|
261
|
+
// If new content has fewer lines than current height, clear the extra lines
|
|
262
|
+
this.pendingFrame = [...lines];
|
|
263
|
+
// Ensure frame is the right size - pad with empty strings if needed
|
|
264
|
+
while (this.pendingFrame.length < this.height) {
|
|
265
|
+
this.pendingFrame.push('');
|
|
266
|
+
}
|
|
267
|
+
// If we shrunk (fewer lines than before), clear the extra lines in previous frame too
|
|
268
|
+
// This ensures the diff algorithm will detect them as needing to be cleared
|
|
269
|
+
if (lines.length < this.previousFrame.length) {
|
|
270
|
+
for (let i = lines.length; i < this.previousFrame.length; i++) {
|
|
271
|
+
this.previousFrame[i] = this.previousFrame[i] || '';
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Schedule render
|
|
275
|
+
this.scheduleRender();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Schedule a render (respects throttle)
|
|
279
|
+
*/
|
|
280
|
+
scheduleRender() {
|
|
281
|
+
if (this.disableRendering) {
|
|
282
|
+
// For tests - just copy pending to previous
|
|
283
|
+
this.previousFrame = [...this.pendingFrame];
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (this.throttle.shouldRender()) {
|
|
287
|
+
this.renderNow();
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
this.renderScheduled = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Copy pending frame to previous frame
|
|
295
|
+
*/
|
|
296
|
+
copyPendingToPrevious() {
|
|
297
|
+
// Use spread operator for efficient shallow copy
|
|
298
|
+
this.previousFrame = [...this.pendingFrame];
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Render immediately (bypasses throttle)
|
|
302
|
+
* Uses relative cursor movements to update only within reserved lines
|
|
303
|
+
*/
|
|
304
|
+
renderNow() {
|
|
305
|
+
if (this.disableRendering) {
|
|
306
|
+
this.copyPendingToPrevious();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// Ensure region is initialized
|
|
310
|
+
if (!this.isInitialized) {
|
|
311
|
+
this.initializeRegion();
|
|
312
|
+
}
|
|
313
|
+
// CRITICAL: Always disable auto-wrap before every render
|
|
314
|
+
// Some terminals reset this state, or other code might enable it
|
|
315
|
+
// We MUST write it directly to stdout (not to render buffer) so it takes effect immediately
|
|
316
|
+
// Node.js stdout.write() is synchronous and blocks until written, so it's effectively flushed
|
|
317
|
+
if (!this.disableRendering) {
|
|
318
|
+
// Write directly to stdout (bypasses render buffer) to ensure immediate effect
|
|
319
|
+
this.stdout.write(ansi.DISABLE_AUTO_WRAP);
|
|
320
|
+
this.autoWrapDisabled = true;
|
|
321
|
+
}
|
|
322
|
+
// Hide cursor in render buffer too (for consistency)
|
|
323
|
+
this.renderBuffer.write(ansi.HIDE_CURSOR);
|
|
324
|
+
// CRITICAL: OhMyZsh uses absolute positioning, not SAVE/RESTORE cursor
|
|
325
|
+
// SAVE/RESTORE can be unreliable, especially after resize
|
|
326
|
+
// Instead, we'll use a simpler approach: always restore to saved position,
|
|
327
|
+
// but re-save it after every render to keep it accurate
|
|
328
|
+
// Restore to saved position (end of region) - this is our anchor point
|
|
329
|
+
this.renderBuffer.write(ansi.RESTORE_CURSOR);
|
|
330
|
+
// CRITICAL: If height changed, we need to adjust the saved position
|
|
331
|
+
// The saved cursor is at: startRow + oldHeight
|
|
332
|
+
// We need it at: startRow + newHeight
|
|
333
|
+
if (this.lastRenderedHeight !== this.height && this.savedCursorPosition) {
|
|
334
|
+
const heightDiff = this.height - this.lastRenderedHeight;
|
|
335
|
+
if (heightDiff > 0) {
|
|
336
|
+
// Height increased: move down to new end using cursor movement (not newlines)
|
|
337
|
+
this.renderBuffer.write(ansi.moveCursorDown(heightDiff));
|
|
338
|
+
}
|
|
339
|
+
else if (heightDiff < 0) {
|
|
340
|
+
// Height decreased: move up to new end
|
|
341
|
+
this.renderBuffer.write(ansi.moveCursorUp(-heightDiff));
|
|
342
|
+
}
|
|
343
|
+
// Re-save the corrected position
|
|
344
|
+
this.renderBuffer.write(ansi.SAVE_CURSOR);
|
|
345
|
+
}
|
|
346
|
+
// CRITICAL: Don't use SAVE/RESTORE - it's unreliable
|
|
347
|
+
// Instead, move to start of region by going up from saved position
|
|
348
|
+
// Then render each line, clearing before writing to ensure clean slate
|
|
349
|
+
// CRITICAL: Only render up to this.height lines, even if pendingFrame has more
|
|
350
|
+
// This prevents rendering extra lines that were accidentally added by setLine
|
|
351
|
+
// The region.set() method for components should have already truncated pendingFrame,
|
|
352
|
+
// but this is a safety check
|
|
353
|
+
const linesToRender = Math.min(this.pendingFrame.length, this.height);
|
|
354
|
+
// Move to start of region (from saved position at end)
|
|
355
|
+
// Use this.height to position correctly
|
|
356
|
+
if (this.height > 0) {
|
|
357
|
+
this.renderBuffer.write(ansi.moveCursorUp(this.height));
|
|
358
|
+
}
|
|
359
|
+
this.renderBuffer.write(ansi.MOVE_TO_START_OF_LINE);
|
|
360
|
+
// Now render each line, one at a time, clearing before writing
|
|
361
|
+
// This ensures we're always writing to the correct line and prevents duplicates
|
|
362
|
+
for (let i = 0; i < linesToRender; i++) {
|
|
363
|
+
const content = this.pendingFrame[i];
|
|
364
|
+
// If this is not the first line, move down one line
|
|
365
|
+
if (i > 0) {
|
|
366
|
+
this.renderBuffer.write(ansi.moveCursorDown(1));
|
|
367
|
+
}
|
|
368
|
+
// CRITICAL: Always clear the line BEFORE writing
|
|
369
|
+
// This prevents any leftover content from causing duplicates
|
|
370
|
+
this.renderBuffer.write(ansi.MOVE_TO_START_OF_LINE);
|
|
371
|
+
this.renderBuffer.write(ansi.CLEAR_LINE);
|
|
372
|
+
this.renderBuffer.write(ansi.MOVE_TO_START_OF_LINE);
|
|
373
|
+
// Truncate content to terminal width BEFORE writing
|
|
374
|
+
const plainContent = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
375
|
+
let contentToWrite = content;
|
|
376
|
+
if (plainContent.length > this.width) {
|
|
377
|
+
// Truncate: find where to cut while preserving ANSI codes
|
|
378
|
+
let visualPos = 0;
|
|
379
|
+
let charPos = 0;
|
|
380
|
+
while (charPos < content.length && visualPos < this.width) {
|
|
381
|
+
if (content[charPos] === '\x1b') {
|
|
382
|
+
// Skip ANSI code
|
|
383
|
+
let ansiEnd = charPos + 1;
|
|
384
|
+
while (ansiEnd < content.length) {
|
|
385
|
+
if (content[ansiEnd] === 'm') {
|
|
386
|
+
ansiEnd++;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
if ((content[ansiEnd] >= '0' && content[ansiEnd] <= '9') ||
|
|
390
|
+
content[ansiEnd] === ';' ||
|
|
391
|
+
content[ansiEnd] === '[') {
|
|
392
|
+
ansiEnd++;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
charPos = ansiEnd;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
charPos++;
|
|
402
|
+
visualPos++;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
contentToWrite = content.substring(0, charPos);
|
|
406
|
+
}
|
|
407
|
+
// Write truncated content
|
|
408
|
+
this.renderBuffer.write(contentToWrite);
|
|
409
|
+
// CRITICAL: After writing, return to start of line
|
|
410
|
+
// This ensures cursor is at a known position for the next iteration
|
|
411
|
+
this.renderBuffer.write(ansi.MOVE_TO_START_OF_LINE);
|
|
412
|
+
}
|
|
413
|
+
// Handle deletions (lines that were in previousFrame but not in pendingFrame)
|
|
414
|
+
// Since we've already cleared and re-rendered all lines, deletions are handled
|
|
415
|
+
// by the fact that those lines are no longer in pendingFrame
|
|
416
|
+
// No additional action needed - the lines are already cleared
|
|
417
|
+
// After rendering all lines, we're at the start of the last line we rendered
|
|
418
|
+
// this.height should already match linesToRender (updated by region.set() before render)
|
|
419
|
+
// But if they don't match for some reason, update it now
|
|
420
|
+
if (linesToRender !== this.height) {
|
|
421
|
+
this.height = linesToRender;
|
|
422
|
+
}
|
|
423
|
+
// We're already at the start of the last rendered line
|
|
424
|
+
// No need to move - we're already at the end of the region
|
|
425
|
+
// CRITICAL: Save cursor position at end of region for next render
|
|
426
|
+
// This is our anchor point - we'll move up from here to get to the start
|
|
427
|
+
this.renderBuffer.write(ansi.SAVE_CURSOR);
|
|
428
|
+
this.savedCursorPosition = true;
|
|
429
|
+
// Show cursor
|
|
430
|
+
this.renderBuffer.write(ansi.SHOW_CURSOR);
|
|
431
|
+
// Flush buffer (single write to stdout)
|
|
432
|
+
this.renderBuffer.flush();
|
|
433
|
+
// Copy pending to previous
|
|
434
|
+
this.copyPendingToPrevious();
|
|
435
|
+
// Update last rendered height for next render
|
|
436
|
+
this.lastRenderedHeight = this.height;
|
|
437
|
+
this.renderScheduled = false;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Force immediate render of pending updates (bypasses throttle)
|
|
441
|
+
*/
|
|
442
|
+
flush() {
|
|
443
|
+
this.renderNow();
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Set throttle FPS
|
|
447
|
+
*/
|
|
448
|
+
setThrottleFps(fps) {
|
|
449
|
+
this.throttle.setFps(fps);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Clear a single line (1-based)
|
|
453
|
+
*/
|
|
454
|
+
clearLine(lineNumber) {
|
|
455
|
+
if (lineNumber < 1) {
|
|
456
|
+
throw new Error('Line numbers start at 1');
|
|
457
|
+
}
|
|
458
|
+
this.setLine(lineNumber, '');
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Clear entire region
|
|
462
|
+
*/
|
|
463
|
+
clear() {
|
|
464
|
+
// Clear all lines in pendingFrame (which may be larger than height if region expanded)
|
|
465
|
+
const maxLines = Math.max(this.height, this.pendingFrame.length);
|
|
466
|
+
for (let i = 1; i <= maxLines; i++) {
|
|
467
|
+
this.setLine(i, '');
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Destroy the region (cleanup)
|
|
472
|
+
* Automatically deletes any blank lines from the terminal, but preserves content
|
|
473
|
+
*
|
|
474
|
+
* @param clearFirst - If true, clears the region before destroying (default: false)
|
|
475
|
+
*
|
|
476
|
+
* Note: This is automatically called on process exit, but you can also call it explicitly
|
|
477
|
+
* to clean up resources earlier (e.g., before continuing with other terminal output)
|
|
478
|
+
*/
|
|
479
|
+
destroy(clearFirst = false) {
|
|
480
|
+
// Re-enable terminal auto-wrap if we disabled it
|
|
481
|
+
// Write directly to stdout to ensure it takes effect immediately
|
|
482
|
+
if (!this.disableRendering && this.autoWrapDisabled) {
|
|
483
|
+
this.stdout.write(ansi.ENABLE_AUTO_WRAP);
|
|
484
|
+
this.autoWrapDisabled = false;
|
|
485
|
+
}
|
|
486
|
+
// Prevent double-destruction
|
|
487
|
+
if (!this.isInitialized && this.pendingFrame.length === 0) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
// Clean up resize handler if it exists
|
|
491
|
+
if (this.resizeCleanup) {
|
|
492
|
+
this.resizeCleanup();
|
|
493
|
+
this.resizeCleanup = undefined;
|
|
494
|
+
}
|
|
495
|
+
// Clear if requested
|
|
496
|
+
if (clearFirst) {
|
|
497
|
+
this.clear();
|
|
498
|
+
// Render the clear immediately so previousFrame is updated
|
|
499
|
+
this.renderNow();
|
|
500
|
+
}
|
|
501
|
+
if (!this.disableRendering && this.isInitialized) {
|
|
502
|
+
// Check if all lines are blank
|
|
503
|
+
// After clearFirst + renderNow(), previousFrame will have empty lines
|
|
504
|
+
// Otherwise, check previousFrame to see if content exists
|
|
505
|
+
const allLinesBlank = this.previousFrame.every(line => line.trim() === '');
|
|
506
|
+
// Only delete lines if they are blank
|
|
507
|
+
// If clearFirst was true, we've already cleared and rendered, so previousFrame will be empty
|
|
508
|
+
// If clearFirst was false, we check previousFrame to see if content exists
|
|
509
|
+
if (allLinesBlank && this.height > 0) {
|
|
510
|
+
// Restore to saved position (end of region)
|
|
511
|
+
this.renderBuffer.write(ansi.RESTORE_CURSOR);
|
|
512
|
+
// Move to start of first line of region
|
|
513
|
+
this.renderBuffer.write(ansi.moveCursorUp(this.height));
|
|
514
|
+
// Use MOVE_TO_START_OF_LINE which is more reliable than \r
|
|
515
|
+
this.renderBuffer.write(ansi.MOVE_TO_START_OF_LINE);
|
|
516
|
+
// Delete the blank lines (shifts content up if supported)
|
|
517
|
+
// This will remove the lines from the terminal display
|
|
518
|
+
this.renderBuffer.write(ansi.deleteLines(this.height));
|
|
519
|
+
// Flush the cleanup
|
|
520
|
+
this.renderBuffer.flush();
|
|
521
|
+
}
|
|
522
|
+
// If lines have content, we leave them as-is - the user can see the output
|
|
523
|
+
}
|
|
524
|
+
// Clear buffers (internal state only, doesn't affect terminal)
|
|
525
|
+
this.pendingFrame = [];
|
|
526
|
+
this.previousFrame = [];
|
|
527
|
+
this.renderBuffer.clear();
|
|
528
|
+
// Mark as destroyed to prevent double-cleanup
|
|
529
|
+
this.isInitialized = false;
|
|
530
|
+
}
|
|
531
|
+
// Getters for width and height
|
|
532
|
+
getWidth() {
|
|
533
|
+
return this.width;
|
|
534
|
+
}
|
|
535
|
+
getHeight() {
|
|
536
|
+
return this.height;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
//# sourceMappingURL=region-old.js.map
|