@oh-my-pi/pi-tui 13.3.13 → 13.4.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/CHANGELOG.md +14 -0
- package/package.json +3 -3
- package/src/components/loader.ts +9 -1
- package/src/index.ts +25 -52
- package/src/tui.ts +28 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.4.0] - 2026-03-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `PI_TUI_RESIZE_CLEAR_STRATEGY` environment variable to control terminal behavior on resize: `viewport` (default) clears/redraws the viewport while preserving scrollback, or `scrollback` clears all history
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Changed resize redraw behavior to use configurable clear semantics (`viewport` vs `scrollback`) while keeping full content rendering for scrollback navigation
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed loader component rendering lines wider than terminal width, preventing text overflow and display artifacts
|
|
18
|
+
|
|
5
19
|
## [13.3.11] - 2026-02-28
|
|
6
20
|
|
|
7
21
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "13.
|
|
4
|
+
"version": "13.4.0",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"test": "bun test test/*.test.ts"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@oh-my-pi/pi-natives": "13.
|
|
37
|
-
"@oh-my-pi/pi-utils": "13.
|
|
36
|
+
"@oh-my-pi/pi-natives": "13.4.0",
|
|
37
|
+
"@oh-my-pi/pi-utils": "13.4.0",
|
|
38
38
|
"marked": "^17.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
package/src/components/loader.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TUI } from "../tui";
|
|
2
|
+
import { sliceByColumn, visibleWidth } from "../utils";
|
|
2
3
|
import { Text } from "./text";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -26,7 +27,14 @@ export class Loader extends Text {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
render(width: number): string[] {
|
|
29
|
-
|
|
30
|
+
const lines = ["", ...super.render(width)];
|
|
31
|
+
for (let i = 0; i < lines.length; i++) {
|
|
32
|
+
const line = lines[i];
|
|
33
|
+
if (visibleWidth(line) > width) {
|
|
34
|
+
lines[i] = sliceByColumn(line, 0, width, true);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
start() {
|
package/src/index.ts
CHANGED
|
@@ -1,67 +1,40 @@
|
|
|
1
1
|
// Core TUI interfaces and classes
|
|
2
2
|
|
|
3
3
|
// Autocomplete support
|
|
4
|
-
export
|
|
5
|
-
type AutocompleteItem,
|
|
6
|
-
type AutocompleteProvider,
|
|
7
|
-
CombinedAutocompleteProvider,
|
|
8
|
-
type SlashCommand,
|
|
9
|
-
} from "./autocomplete";
|
|
4
|
+
export * from "./autocomplete";
|
|
10
5
|
// Components
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
18
|
-
export
|
|
19
|
-
export
|
|
20
|
-
export
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
export
|
|
6
|
+
export * from "./components/box";
|
|
7
|
+
export * from "./components/cancellable-loader";
|
|
8
|
+
export * from "./components/editor";
|
|
9
|
+
export * from "./components/image";
|
|
10
|
+
export * from "./components/input";
|
|
11
|
+
export * from "./components/loader";
|
|
12
|
+
export * from "./components/markdown";
|
|
13
|
+
export * from "./components/select-list";
|
|
14
|
+
export * from "./components/settings-list";
|
|
15
|
+
export * from "./components/spacer";
|
|
16
|
+
export * from "./components/tab-bar";
|
|
17
|
+
export * from "./components/text";
|
|
18
|
+
export * from "./components/truncated-text";
|
|
24
19
|
// Editor component interface (for custom editors)
|
|
25
|
-
export type
|
|
20
|
+
export type * from "./editor-component";
|
|
26
21
|
// Fuzzy matching
|
|
27
|
-
export
|
|
22
|
+
export * from "./fuzzy";
|
|
28
23
|
// Keybindings
|
|
29
|
-
export
|
|
30
|
-
DEFAULT_EDITOR_KEYBINDINGS,
|
|
31
|
-
type EditorAction,
|
|
32
|
-
type EditorKeybindingsConfig,
|
|
33
|
-
EditorKeybindingsManager,
|
|
34
|
-
getEditorKeybindings,
|
|
35
|
-
setEditorKeybindings,
|
|
36
|
-
} from "./keybindings";
|
|
24
|
+
export * from "./keybindings";
|
|
37
25
|
// Kitty keyboard protocol helpers
|
|
38
|
-
export
|
|
39
|
-
isKeyRelease,
|
|
40
|
-
isKeyRepeat,
|
|
41
|
-
isKittyProtocolActive,
|
|
42
|
-
type KeyId,
|
|
43
|
-
matchesKey,
|
|
44
|
-
parseKey,
|
|
45
|
-
parseKittySequence,
|
|
46
|
-
setKittyProtocolActive,
|
|
47
|
-
} from "./keys";
|
|
26
|
+
export * from "./keys";
|
|
48
27
|
// Mermaid diagram support
|
|
49
|
-
export
|
|
50
|
-
extractMermaidBlocks,
|
|
51
|
-
type MermaidImage,
|
|
52
|
-
type MermaidRenderOptions,
|
|
53
|
-
prerenderMermaidBlocks,
|
|
54
|
-
renderMermaidToPng,
|
|
55
|
-
} from "./mermaid";
|
|
28
|
+
export * from "./mermaid";
|
|
56
29
|
// Input buffering for batch splitting
|
|
57
|
-
export
|
|
58
|
-
export type
|
|
30
|
+
export * from "./stdin-buffer";
|
|
31
|
+
export type * from "./symbols";
|
|
59
32
|
// Terminal interface and implementations
|
|
60
|
-
export
|
|
33
|
+
export * from "./terminal";
|
|
61
34
|
// Terminal image support
|
|
62
35
|
export * from "./terminal-capabilities";
|
|
63
36
|
// TTY ID
|
|
64
|
-
export
|
|
65
|
-
export
|
|
37
|
+
export * from "./ttyid";
|
|
38
|
+
export * from "./tui";
|
|
66
39
|
// Utilities
|
|
67
|
-
export
|
|
40
|
+
export * from "./utils";
|
package/src/tui.ts
CHANGED
|
@@ -11,6 +11,11 @@ import { extractSegments, sliceByColumn, sliceWithWidth, visibleWidth } from "./
|
|
|
11
11
|
|
|
12
12
|
const SEGMENT_RESET = "\x1b[0m";
|
|
13
13
|
|
|
14
|
+
type ResizeClearStrategy = "viewport" | "scrollback";
|
|
15
|
+
|
|
16
|
+
function resolveResizeClearStrategy(value: string | undefined): ResizeClearStrategy {
|
|
17
|
+
return value?.toLowerCase() === "scrollback" ? "scrollback" : "viewport";
|
|
18
|
+
}
|
|
14
19
|
type InputListenerResult = { consume?: boolean; data?: string } | undefined;
|
|
15
20
|
type InputListener = (data: string) => InputListenerResult;
|
|
16
21
|
|
|
@@ -217,6 +222,7 @@ export class TUI extends Container {
|
|
|
217
222
|
#cellSizeQueryPending = false;
|
|
218
223
|
#showHardwareCursor = process.env.PI_HARDWARE_CURSOR === "1";
|
|
219
224
|
#clearOnShrink = process.env.PI_CLEAR_ON_SHRINK === "1"; // Clear empty rows when content shrinks (default: off)
|
|
225
|
+
#resizeClearStrategy = resolveResizeClearStrategy(process.env.PI_TUI_RESIZE_CLEAR_STRATEGY); // Resize full-redraw strategy: viewport(default) or scrollback
|
|
220
226
|
#maxLinesRendered = 0; // Track terminal's working area (max lines ever rendered)
|
|
221
227
|
#previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
|
|
222
228
|
#fullRedrawCount = 0;
|
|
@@ -885,19 +891,34 @@ export class TUI extends Container {
|
|
|
885
891
|
// Helper to clear scrollback and viewport and render all new lines
|
|
886
892
|
const fullRender = (clear: boolean): void => {
|
|
887
893
|
this.#fullRedrawCount += 1;
|
|
894
|
+
const isResizeRedraw = clear && (widthChanged || heightChanged);
|
|
895
|
+
const shouldRenderViewportOnly =
|
|
896
|
+
isResizeRedraw && this.#resizeClearStrategy === "viewport" && newLines.length <= this.#previousLines.length;
|
|
897
|
+
const renderFrom = shouldRenderViewportOnly ? Math.max(0, newLines.length - height) : 0;
|
|
898
|
+
const renderedLines = renderFrom > 0 ? newLines.slice(renderFrom) : newLines;
|
|
888
899
|
let buffer = "\x1b[?2026h"; // Begin synchronized output
|
|
889
|
-
if (clear)
|
|
890
|
-
|
|
900
|
+
if (clear) {
|
|
901
|
+
// Keep default home+erase-below semantics for non-resize redraws.
|
|
902
|
+
// On resize, choose behavior via PI_TUI_RESIZE_CLEAR_STRATEGY.
|
|
903
|
+
if (this.#clearScrollbackOnNextFullRender) buffer += "\x1b[3J\x1b[2J\x1b[H";
|
|
904
|
+
else if (isResizeRedraw && this.#resizeClearStrategy === "scrollback") buffer += "\x1b[3J\x1b[2J\x1b[H";
|
|
905
|
+
else if (isResizeRedraw) buffer += "\x1b[2J\x1b[H";
|
|
906
|
+
else buffer += "\x1b[H\x1b[0J";
|
|
907
|
+
}
|
|
908
|
+
for (let i = 0; i < renderedLines.length; i++) {
|
|
891
909
|
if (i > 0) buffer += "\r\n";
|
|
892
|
-
buffer +=
|
|
910
|
+
buffer += renderedLines[i];
|
|
893
911
|
}
|
|
894
|
-
const renderCursorRow = Math.max(0,
|
|
895
|
-
const
|
|
912
|
+
const renderCursorRow = Math.max(0, renderedLines.length - 1);
|
|
913
|
+
const renderCursorPos = cursorPos
|
|
914
|
+
? { row: Math.max(0, cursorPos.row - renderFrom), col: cursorPos.col }
|
|
915
|
+
: null;
|
|
916
|
+
const cursorUpdate = this.#buildHardwareCursorSequence(renderCursorPos, renderedLines.length, renderCursorRow);
|
|
896
917
|
buffer += cursorUpdate.sequence;
|
|
897
918
|
buffer += "\x1b[?2026l"; // End synchronized output
|
|
898
919
|
this.terminal.write(buffer);
|
|
899
|
-
this.#cursorRow =
|
|
900
|
-
this.#hardwareCursorRow = cursorUpdate.row;
|
|
920
|
+
this.#cursorRow = Math.max(0, newLines.length - 1);
|
|
921
|
+
this.#hardwareCursorRow = cursorUpdate.row + renderFrom;
|
|
901
922
|
// Reset max lines when clearing, otherwise track growth
|
|
902
923
|
if (clear) {
|
|
903
924
|
this.#maxLinesRendered = newLines.length;
|