@averyyy/pi-tui 0.83.0-piclient.1 → 0.84.1-piclient.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.
- package/README.md +77 -14
- package/dist/components/alt-screen-flash.d.ts +13 -0
- package/dist/components/alt-screen-flash.d.ts.map +1 -0
- package/dist/components/alt-screen-flash.js +37 -0
- package/dist/components/alt-screen-flash.js.map +1 -0
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +11 -0
- package/dist/components/editor.js.map +1 -1
- package/dist/components/h-stack.d.ts +7 -0
- package/dist/components/h-stack.d.ts.map +1 -0
- package/dist/components/h-stack.js +43 -0
- package/dist/components/h-stack.js.map +1 -0
- package/dist/components/markdown.d.ts +4 -0
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +128 -2
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/scroll-view.d.ts +51 -0
- package/dist/components/scroll-view.d.ts.map +1 -0
- package/dist/components/scroll-view.js +173 -0
- package/dist/components/scroll-view.js.map +1 -0
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +3 -6
- package/dist/components/settings-list.js.map +1 -1
- package/dist/components/stack.d.ts +32 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +111 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/v-stack.d.ts +8 -0
- package/dist/components/v-stack.d.ts.map +1 -0
- package/dist/components/v-stack.js +26 -0
- package/dist/components/v-stack.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +54 -4
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +39 -4
- package/dist/keybindings.js.map +1 -1
- package/dist/latex.d.ts +10 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +1264 -0
- package/dist/latex.js.map +1 -0
- package/dist/layout-node.d.ts +40 -0
- package/dist/layout-node.d.ts.map +1 -0
- package/dist/layout-node.js +6 -0
- package/dist/layout-node.js.map +1 -0
- package/dist/layout.d.ts +40 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +314 -0
- package/dist/layout.js.map +1 -0
- package/dist/native-modifiers.d.ts.map +1 -1
- package/dist/native-modifiers.js +10 -3
- package/dist/native-modifiers.js.map +1 -1
- package/dist/terminal-colors.d.ts.map +1 -1
- package/dist/terminal-colors.js +1 -1
- package/dist/terminal-colors.js.map +1 -1
- package/dist/terminal-image.d.ts +21 -0
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js +134 -6
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +10 -7
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +97 -0
- package/dist/tui-alt-screen.d.ts.map +1 -0
- package/dist/tui-alt-screen.js +902 -0
- package/dist/tui-alt-screen.js.map +1 -0
- package/dist/tui-main-screen.d.ts +39 -0
- package/dist/tui-main-screen.d.ts.map +1 -0
- package/dist/tui-main-screen.js +533 -0
- package/dist/tui-main-screen.js.map +1 -0
- package/dist/tui.d.ts +75 -38
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +98 -573
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +119 -6
- package/dist/utils.js.map +1 -1
- package/native/darwin/README.md +20 -0
- package/native/darwin/build.sh +62 -0
- package/native/darwin/src/darwin-modifiers.c +70 -0
- package/native/win32/README.md +22 -0
- package/native/win32/build.mjs +229 -0
- package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/native/win32/src/win32-console-mode.c +119 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -4,25 +4,27 @@ Minimal terminal UI framework with differential rendering and synchronized outpu
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
7
|
+
- **Interchangeable Renderers**: Shared `TUI` interface with main-screen and alternate-screen implementations
|
|
8
|
+
- **Differential Rendering**: Updates only changed lines or viewport rows
|
|
9
|
+
- **Application-owned Scrolling**: Alternate-screen viewport supports mouse, trackpad, and keyboard navigation
|
|
8
10
|
- **Synchronized Output**: Uses CSI 2026 for atomic screen updates (no flicker)
|
|
9
11
|
- **Bracketed Paste Mode**: Handles large pastes correctly with markers for >10 line pastes
|
|
10
12
|
- **Component-based**: Simple Component interface with render() method
|
|
11
13
|
- **Theme Support**: Components accept theme interfaces for customizable styling
|
|
12
|
-
- **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container
|
|
14
|
+
- **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container, VStack, HStack, ScrollView
|
|
13
15
|
- **Inline Images**: Renders images in terminals that support Kitty or iTerm2 graphics protocols
|
|
14
16
|
- **Autocomplete Support**: File paths and slash commands
|
|
15
17
|
|
|
16
18
|
## Quick Start
|
|
17
19
|
|
|
18
20
|
```typescript
|
|
19
|
-
import { TUI, Text, Editor, ProcessTerminal, matchesKey } from "@earendil-works/pi-tui";
|
|
21
|
+
import { type TUI, Text, Editor, ProcessTerminal, TuiMainScreen, matchesKey } from "@earendil-works/pi-tui";
|
|
20
22
|
|
|
21
23
|
// Create terminal
|
|
22
24
|
const terminal = new ProcessTerminal();
|
|
23
25
|
|
|
24
|
-
// Create TUI
|
|
25
|
-
const tui = new
|
|
26
|
+
// Create the default main-screen renderer through the shared TUI interface
|
|
27
|
+
const tui: TUI = new TuiMainScreen(terminal);
|
|
26
28
|
|
|
27
29
|
// Add components
|
|
28
30
|
tui.addChild(new Text("Welcome to my app!"));
|
|
@@ -52,12 +54,20 @@ tui.start();
|
|
|
52
54
|
|
|
53
55
|
## Core API
|
|
54
56
|
|
|
55
|
-
### TUI
|
|
57
|
+
### TUI interface and renderers
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
`TUI` is the shared interface for component management, focus, overlays, input, lifecycle, terminal queries, and rendering. Choose a concrete renderer only when constructing the application:
|
|
60
|
+
|
|
61
|
+
- `TuiMainScreen` renders into the main terminal buffer and preserves terminal scrollback.
|
|
62
|
+
- `TuiAltScreen` renders a fixed-height viewport in the alternate terminal buffer with application-owned scrolling. When stopped, it restores the main buffer and prints the complete final document.
|
|
58
63
|
|
|
59
64
|
```typescript
|
|
60
|
-
|
|
65
|
+
import { type TUI, TuiAltScreen, TuiMainScreen } from "@earendil-works/pi-tui";
|
|
66
|
+
|
|
67
|
+
const tui: TUI = new TuiMainScreen(terminal);
|
|
68
|
+
// To use an application-owned viewport in the alternate terminal buffer instead:
|
|
69
|
+
// const tui: TUI = new TuiAltScreen(terminal);
|
|
70
|
+
|
|
61
71
|
tui.addChild(component);
|
|
62
72
|
tui.removeChild(component);
|
|
63
73
|
tui.start();
|
|
@@ -68,6 +78,53 @@ tui.requestRender(); // Request a re-render
|
|
|
68
78
|
tui.onDebug = () => console.log("Debug triggered");
|
|
69
79
|
```
|
|
70
80
|
|
|
81
|
+
### Alternate-screen viewport layouts
|
|
82
|
+
|
|
83
|
+
`TuiAltScreen` can render an explicit terminal-height layout. `VStack` and `HStack` allocate constrained regions, while `ScrollView` owns scrolling for one region. These semantics are intentionally unavailable on `TuiMainScreen`, where the terminal owns scrollback.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import {
|
|
87
|
+
Container,
|
|
88
|
+
isViewportTUI,
|
|
89
|
+
ScrollView,
|
|
90
|
+
Text,
|
|
91
|
+
VStack,
|
|
92
|
+
} from "@earendil-works/pi-tui";
|
|
93
|
+
|
|
94
|
+
const transcript = new Container();
|
|
95
|
+
transcript.addChild(new Text("History"));
|
|
96
|
+
|
|
97
|
+
const editorAndFooter = new VStack([
|
|
98
|
+
editor,
|
|
99
|
+
new Text("status"),
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
if (isViewportTUI(tui)) {
|
|
103
|
+
tui.setLayoutRoot(new VStack([
|
|
104
|
+
{
|
|
105
|
+
component: new ScrollView(transcript, {
|
|
106
|
+
follow: "end",
|
|
107
|
+
primary: true,
|
|
108
|
+
overscroll: "chain",
|
|
109
|
+
}),
|
|
110
|
+
basis: 0,
|
|
111
|
+
grow: 1,
|
|
112
|
+
minSize: 1,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
component: editorAndFooter,
|
|
116
|
+
basis: "auto",
|
|
117
|
+
shrink: 1,
|
|
118
|
+
minSize: 1,
|
|
119
|
+
},
|
|
120
|
+
]));
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Stack entries support `basis`, `grow`, `shrink`, `minSize`, `maxSize`, and responsive `visible` callbacks. Mouse-wheel input targets the scroll view under the pointer and unused delta chains to outer scroll views by default. The primary scroll view receives the alternate-screen keyboard navigation actions and wheel input over non-scrollable regions. It can also jump between OSC 133 semantic prompt markers, matching common terminal prompt-navigation shortcuts.
|
|
125
|
+
|
|
126
|
+
Layout geometry is rebuilt for each requested frame. Stateful components are retained, and their existing rendered-line caches remain effective. Calling `render(width)` directly on these layout components produces an unbounded document, which is also used when alt mode restores the main screen.
|
|
127
|
+
|
|
71
128
|
### Overlays
|
|
72
129
|
|
|
73
130
|
Overlays render components on top of existing content without replacing it. Useful for dialogs, menus, and modal UI.
|
|
@@ -190,7 +247,7 @@ When a `Focusable` component has focus, TUI:
|
|
|
190
247
|
3. Positions the hardware terminal cursor at that location
|
|
191
248
|
4. Shows the hardware cursor only when `showHardwareCursor` is enabled
|
|
192
249
|
|
|
193
|
-
The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with the `
|
|
250
|
+
The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with the renderer constructor's `showHardwareCursor` argument, `setShowHardwareCursor(true)`, or `PI_HARDWARE_CURSOR=1`. The `Editor` and `Input` built-in components already implement this interface.
|
|
194
251
|
|
|
195
252
|
**Container components with embedded inputs:** When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child:
|
|
196
253
|
|
|
@@ -535,6 +592,10 @@ tui.addChild(image);
|
|
|
535
592
|
|
|
536
593
|
Supported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.
|
|
537
594
|
|
|
595
|
+
#### Alternate-screen image compatibility
|
|
596
|
+
|
|
597
|
+
`TuiAltScreen` supports inline images and partial viewport cropping in terminals that implement the Kitty graphics protocol, including Kitty and Ghostty. iTerm2's inline-image protocol does not provide operations to delete an existing placement or crop its source while scrolling. To prevent stale images from remaining over repainted content, `TuiAltScreen` renders image components as text placeholders in iTerm2. `TuiMainScreen` continues to render iTerm2 inline images normally.
|
|
598
|
+
|
|
538
599
|
## Autocomplete
|
|
539
600
|
|
|
540
601
|
### CombinedAutocompleteProvider
|
|
@@ -588,15 +649,17 @@ if (matchesKey(data, Key.enter)) {
|
|
|
588
649
|
- With modifiers: `Key.ctrl("c")`, `Key.shift("tab")`, `Key.alt("left")`, `Key.ctrlShift("p")`
|
|
589
650
|
- String format also works: `"enter"`, `"ctrl+c"`, `"shift+tab"`, `"ctrl+shift+p"`
|
|
590
651
|
|
|
591
|
-
##
|
|
652
|
+
## Rendering modes
|
|
592
653
|
|
|
593
|
-
|
|
654
|
+
`TuiMainScreen` uses three rendering strategies:
|
|
594
655
|
|
|
595
656
|
1. **First Render**: Output all lines without clearing scrollback
|
|
596
|
-
2. **Width Changed or Change Above Viewport**: Clear screen and
|
|
597
|
-
3. **Normal Update**: Move cursor to first changed line, clear to end, render changed lines
|
|
657
|
+
2. **Width Changed or Change Above Viewport**: Clear screen and fully re-render
|
|
658
|
+
3. **Normal Update**: Move the cursor to the first changed line, clear to the end, and render changed lines
|
|
659
|
+
|
|
660
|
+
`TuiAltScreen` owns a terminal-height viewport. Without an explicit layout root it preserves the legacy single-document scrolling behavior. With `setLayoutRoot()`, `VStack`, `HStack`, and nested `ScrollView` components can reserve fixed regions and independently scroll constrained regions. It updates changed viewport rows in place, follows streaming output while at the bottom, and preserves a manually selected scroll position while content grows. Mouse-wheel and configurable keyboard navigation scroll without modifying terminal scrollback, including jumps between OSC 133 semantic prompt markers. Clicking an OSC 8 hyperlink opens it with the configured URL handler. Dragging with the primary mouse button selects text and copies it to the clipboard with OSC 52; holding the drag at a scroll view's top or bottom edge auto-scrolls and extends the selection into off-screen content. Kitty images support vertical viewport cropping; iTerm2 inline images fall back to text because the iTerm2 protocol cannot delete or crop placements during viewport repainting.
|
|
598
661
|
|
|
599
|
-
|
|
662
|
+
Both renderers wrap updates in **synchronized output** (`\x1b[?2026h` ... `\x1b[?2026l`) for atomic, flicker-free rendering.
|
|
600
663
|
|
|
601
664
|
## Terminal Interface
|
|
602
665
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Component } from "../tui.ts";
|
|
2
|
+
/** Transient messages composited by the alternate-screen renderer. */
|
|
3
|
+
export declare class AltScreenFlashContainer implements Component {
|
|
4
|
+
private readonly entries;
|
|
5
|
+
private nextId;
|
|
6
|
+
private readonly requestRender;
|
|
7
|
+
constructor(requestRender: () => void);
|
|
8
|
+
flash(message: string, durationMs?: number): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
invalidate(): void;
|
|
11
|
+
render(width: number): string[];
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=alt-screen-flash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alt-screen-flash.d.ts","sourceRoot":"","sources":["../../src/components/alt-screen-flash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAW3C,sEAAsE;AACtE,qBAAa,uBAAwB,YAAW,SAAS;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAE3C,YAAY,aAAa,EAAE,MAAM,IAAI,EAEpC;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAsB,GAAG,IAAI,CAc7D;IAED,OAAO,IAAI,IAAI,CAGd;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAK9B;CACD","sourcesContent":["import type { Component } from \"../tui.ts\";\nimport { truncateToWidth } from \"../utils.ts\";\n\nconst DEFAULT_DURATION_MS = 1000;\n\ninterface FlashEntry {\n\tid: number;\n\tmessage: string;\n\ttimer: NodeJS.Timeout;\n}\n\n/** Transient messages composited by the alternate-screen renderer. */\nexport class AltScreenFlashContainer implements Component {\n\tprivate readonly entries: FlashEntry[] = [];\n\tprivate nextId = 0;\n\tprivate readonly requestRender: () => void;\n\n\tconstructor(requestRender: () => void) {\n\t\tthis.requestRender = requestRender;\n\t}\n\n\tflash(message: string, durationMs = DEFAULT_DURATION_MS): void {\n\t\tconst id = this.nextId++;\n\t\tconst timer = setTimeout(\n\t\t\t() => {\n\t\t\t\tconst index = this.entries.findIndex((entry) => entry.id === id);\n\t\t\t\tif (index === -1) return;\n\t\t\t\tthis.entries.splice(index, 1);\n\t\t\t\tthis.requestRender();\n\t\t\t},\n\t\t\tMath.max(0, durationMs),\n\t\t);\n\t\ttimer.unref();\n\t\tthis.entries.push({ id, message, timer });\n\t\tthis.requestRender();\n\t}\n\n\tdispose(): void {\n\t\tfor (const entry of this.entries) clearTimeout(entry.timer);\n\t\tthis.entries.length = 0;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\treturn this.entries.map((entry) => {\n\t\t\tconst message = truncateToWidth(` ${entry.message} `, width, \"\");\n\t\t\treturn `\\x1b[7m${message}\\x1b[27m`;\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { truncateToWidth } from "../utils.js";
|
|
2
|
+
const DEFAULT_DURATION_MS = 1000;
|
|
3
|
+
/** Transient messages composited by the alternate-screen renderer. */
|
|
4
|
+
export class AltScreenFlashContainer {
|
|
5
|
+
entries = [];
|
|
6
|
+
nextId = 0;
|
|
7
|
+
requestRender;
|
|
8
|
+
constructor(requestRender) {
|
|
9
|
+
this.requestRender = requestRender;
|
|
10
|
+
}
|
|
11
|
+
flash(message, durationMs = DEFAULT_DURATION_MS) {
|
|
12
|
+
const id = this.nextId++;
|
|
13
|
+
const timer = setTimeout(() => {
|
|
14
|
+
const index = this.entries.findIndex((entry) => entry.id === id);
|
|
15
|
+
if (index === -1)
|
|
16
|
+
return;
|
|
17
|
+
this.entries.splice(index, 1);
|
|
18
|
+
this.requestRender();
|
|
19
|
+
}, Math.max(0, durationMs));
|
|
20
|
+
timer.unref();
|
|
21
|
+
this.entries.push({ id, message, timer });
|
|
22
|
+
this.requestRender();
|
|
23
|
+
}
|
|
24
|
+
dispose() {
|
|
25
|
+
for (const entry of this.entries)
|
|
26
|
+
clearTimeout(entry.timer);
|
|
27
|
+
this.entries.length = 0;
|
|
28
|
+
}
|
|
29
|
+
invalidate() { }
|
|
30
|
+
render(width) {
|
|
31
|
+
return this.entries.map((entry) => {
|
|
32
|
+
const message = truncateToWidth(` ${entry.message} `, width, "");
|
|
33
|
+
return `\x1b[7m${message}\x1b[27m`;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=alt-screen-flash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alt-screen-flash.js","sourceRoot":"","sources":["../../src/components/alt-screen-flash.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAQjC,sEAAsE;AACtE,MAAM,OAAO,uBAAuB;IAClB,OAAO,GAAiB,EAAE,CAAC;IACpC,MAAM,GAAG,CAAC,CAAC;IACF,aAAa,CAAa;IAE3C,YAAY,aAAyB,EAAE;QACtC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IAAA,CACnC;IAED,KAAK,CAAC,OAAe,EAAE,UAAU,GAAG,mBAAmB,EAAQ;QAC9D,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC;YACL,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO;YACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,CAAC,aAAa,EAAE,CAAC;QAAA,CACrB,EACD,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CACvB,CAAC;QACF,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAED,OAAO,GAAS;QACf,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO;YAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAAA,CACxB;IAED,UAAU,GAAS,EAAC,CAAC;IAErB,MAAM,CAAC,KAAa,EAAY;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACjE,OAAO,UAAU,OAAO,UAAU,CAAC;QAAA,CACnC,CAAC,CAAC;IAAA,CACH;CACD","sourcesContent":["import type { Component } from \"../tui.ts\";\nimport { truncateToWidth } from \"../utils.ts\";\n\nconst DEFAULT_DURATION_MS = 1000;\n\ninterface FlashEntry {\n\tid: number;\n\tmessage: string;\n\ttimer: NodeJS.Timeout;\n}\n\n/** Transient messages composited by the alternate-screen renderer. */\nexport class AltScreenFlashContainer implements Component {\n\tprivate readonly entries: FlashEntry[] = [];\n\tprivate nextId = 0;\n\tprivate readonly requestRender: () => void;\n\n\tconstructor(requestRender: () => void) {\n\t\tthis.requestRender = requestRender;\n\t}\n\n\tflash(message: string, durationMs = DEFAULT_DURATION_MS): void {\n\t\tconst id = this.nextId++;\n\t\tconst timer = setTimeout(\n\t\t\t() => {\n\t\t\t\tconst index = this.entries.findIndex((entry) => entry.id === id);\n\t\t\t\tif (index === -1) return;\n\t\t\t\tthis.entries.splice(index, 1);\n\t\t\t\tthis.requestRender();\n\t\t\t},\n\t\t\tMath.max(0, durationMs),\n\t\t);\n\t\ttimer.unref();\n\t\tthis.entries.push({ id, message, timer });\n\t\tthis.requestRender();\n\t}\n\n\tdispose(): void {\n\t\tfor (const entry of this.entries) clearTimeout(entry.timer);\n\t\tthis.entries.length = 0;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\treturn this.entries.map((entry) => {\n\t\t\tconst message = truncateToWidth(` ${entry.message} `, width, \"\");\n\t\t\treturn `\\x1b[7m${message}\\x1b[27m`;\n\t\t});\n\t}\n}\n"]}
|