jeopi-tui 16.2.13
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 +1861 -0
- package/README.md +705 -0
- package/dist/types/autocomplete.d.ts +99 -0
- package/dist/types/bracketed-paste.d.ts +51 -0
- package/dist/types/components/box.d.ts +31 -0
- package/dist/types/components/cancellable-loader.d.ts +21 -0
- package/dist/types/components/editor.d.ts +155 -0
- package/dist/types/components/image.d.ts +112 -0
- package/dist/types/components/input.d.ts +23 -0
- package/dist/types/components/loader.d.ts +20 -0
- package/dist/types/components/markdown.d.ts +64 -0
- package/dist/types/components/scroll-view.d.ts +62 -0
- package/dist/types/components/select-list.d.ts +68 -0
- package/dist/types/components/settings-list.d.ts +123 -0
- package/dist/types/components/spacer.d.ts +11 -0
- package/dist/types/components/tab-bar.d.ts +89 -0
- package/dist/types/components/text.d.ts +14 -0
- package/dist/types/components/truncated-text.d.ts +10 -0
- package/dist/types/deccara.d.ts +49 -0
- package/dist/types/desktop-notify.d.ts +51 -0
- package/dist/types/editor-component.d.ts +38 -0
- package/dist/types/fuzzy.d.ts +32 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/keybindings.d.ts +191 -0
- package/dist/types/keys.d.ts +208 -0
- package/dist/types/kill-ring.d.ts +20 -0
- package/dist/types/kitty-graphics.d.ts +79 -0
- package/dist/types/latex-block.d.ts +7 -0
- package/dist/types/latex-to-unicode.d.ts +33 -0
- package/dist/types/loop-watchdog.d.ts +39 -0
- package/dist/types/mouse.d.ts +67 -0
- package/dist/types/stdin-buffer.d.ts +60 -0
- package/dist/types/symbols.d.ts +25 -0
- package/dist/types/terminal-capabilities.d.ts +284 -0
- package/dist/types/terminal.d.ts +107 -0
- package/dist/types/ttyid.d.ts +9 -0
- package/dist/types/tui.d.ts +423 -0
- package/dist/types/utils.d.ts +95 -0
- package/package.json +73 -0
- package/src/autocomplete.ts +1026 -0
- package/src/bracketed-paste.ts +123 -0
- package/src/components/box.ts +194 -0
- package/src/components/cancellable-loader.ts +40 -0
- package/src/components/editor.ts +3092 -0
- package/src/components/image.ts +444 -0
- package/src/components/input.ts +474 -0
- package/src/components/loader.ts +103 -0
- package/src/components/markdown.ts +2068 -0
- package/src/components/scroll-view.ts +227 -0
- package/src/components/select-list.ts +531 -0
- package/src/components/settings-list.ts +793 -0
- package/src/components/spacer.ts +32 -0
- package/src/components/tab-bar.ts +300 -0
- package/src/components/text.ts +122 -0
- package/src/components/truncated-text.ts +69 -0
- package/src/deccara.ts +314 -0
- package/src/desktop-notify.ts +186 -0
- package/src/editor-component.ts +74 -0
- package/src/fuzzy.ts +356 -0
- package/src/index.ts +51 -0
- package/src/keybindings.ts +337 -0
- package/src/keys.ts +561 -0
- package/src/kill-ring.ts +51 -0
- package/src/kitty-graphics.ts +171 -0
- package/src/latex-block.ts +461 -0
- package/src/latex-to-unicode.ts +1994 -0
- package/src/loop-watchdog.ts +106 -0
- package/src/mouse.ts +105 -0
- package/src/stdin-buffer.ts +669 -0
- package/src/symbols.ts +26 -0
- package/src/terminal-capabilities.ts +1152 -0
- package/src/terminal.ts +1463 -0
- package/src/ttyid.ts +84 -0
- package/src/tui.ts +3901 -0
- package/src/utils.ts +570 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { ImageBudget } from "./components/image";
|
|
2
|
+
import { type Terminal } from "./terminal";
|
|
3
|
+
import { visibleWidth } from "./utils";
|
|
4
|
+
type InputListenerResult = {
|
|
5
|
+
consume?: boolean;
|
|
6
|
+
data?: string;
|
|
7
|
+
} | undefined;
|
|
8
|
+
type InputListener = (data: string) => InputListenerResult;
|
|
9
|
+
type StartListener = () => void;
|
|
10
|
+
export interface RenderTimer {
|
|
11
|
+
cancel(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface RenderScheduler {
|
|
14
|
+
now(): number;
|
|
15
|
+
scheduleImmediate(callback: () => void): void;
|
|
16
|
+
scheduleRender(callback: () => void, delayMs: number): RenderTimer;
|
|
17
|
+
}
|
|
18
|
+
export interface TUIOptions {
|
|
19
|
+
renderScheduler?: RenderScheduler;
|
|
20
|
+
}
|
|
21
|
+
export interface TUIStartOptions {
|
|
22
|
+
/** Clear saved native scrollback before the first paint. */
|
|
23
|
+
clearScrollback?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Component interface - all components must implement this
|
|
27
|
+
*
|
|
28
|
+
* Render contract: the returned array (and its rows) belongs to the component.
|
|
29
|
+
* Callers MUST NOT mutate it — components are allowed to return a cached array
|
|
30
|
+
* and will return the exact same reference for as long as their rendered
|
|
31
|
+
* content is unchanged. Conversely, a component MUST return a fresh array
|
|
32
|
+
* reference whenever its content changed; reference equality across two
|
|
33
|
+
* render() calls is the engine's proof that the rows are byte-identical
|
|
34
|
+
* (containers memoize their concatenation on it, and the TUI derives the
|
|
35
|
+
* frame's stable prefix from it). A component that mutates a previously
|
|
36
|
+
* returned array in place must implement {@link RenderStablePrefix} to declare
|
|
37
|
+
* which leading rows survived.
|
|
38
|
+
*/
|
|
39
|
+
export interface Component {
|
|
40
|
+
/**
|
|
41
|
+
* Render the component to an array of physical rows at the given width.
|
|
42
|
+
* The result is component-owned and `readonly` to the caller; an unchanged
|
|
43
|
+
* component may (and should) return the same array reference it returned
|
|
44
|
+
* last time.
|
|
45
|
+
*/
|
|
46
|
+
render(width: number): readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Optional handler for keyboard input when component has focus
|
|
49
|
+
*/
|
|
50
|
+
handleInput?(data: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* If true, component receives key release events (Kitty protocol).
|
|
53
|
+
* Default is false - release events are filtered out.
|
|
54
|
+
*/
|
|
55
|
+
wantsKeyRelease?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Optional hook to invalidate any cached rendering state.
|
|
58
|
+
* Called when theme changes or when component needs to re-render from scratch.
|
|
59
|
+
*/
|
|
60
|
+
invalidate?(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Optional hook to set whether this component ignores tight layout mode.
|
|
63
|
+
*/
|
|
64
|
+
setIgnoreTight?(ignore: boolean): any;
|
|
65
|
+
/**
|
|
66
|
+
* Optional teardown. Called when the component is permanently removed from
|
|
67
|
+
* the live tree (e.g. a transcript reset). Release timers, intervals, and
|
|
68
|
+
* subscriptions here. Must be idempotent. Containers propagate dispose to
|
|
69
|
+
* their children; leaf components without resources may omit it.
|
|
70
|
+
*/
|
|
71
|
+
dispose?(): void;
|
|
72
|
+
}
|
|
73
|
+
/** Lets an overlay root delegate keyboard focus to components it owns. */
|
|
74
|
+
export interface OverlayFocusOwner {
|
|
75
|
+
/** Returns true when `component` is a focus target inside this overlay. */
|
|
76
|
+
ownsOverlayFocusTarget(component: Component): boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Component seam for append-only native-scrollback commits. A component that
|
|
80
|
+
* renders a finalized prefix followed by a live/mutating suffix reports the
|
|
81
|
+
* local line index where that suffix begins after each render. The engine
|
|
82
|
+
* commits rows to native scrollback only up to that boundary; everything
|
|
83
|
+
* below repaints in place inside the visible window and never enters history
|
|
84
|
+
* until it finalizes.
|
|
85
|
+
*
|
|
86
|
+
* `getNativeScrollbackCommitSafeEnd` optionally reports a *deeper* boundary
|
|
87
|
+
* inside the live suffix: the line index up to which the live region is
|
|
88
|
+
* append-only (earlier rows never re-layout — a streaming assistant message).
|
|
89
|
+
* Rows in `[liveRegionStart, commitSafeEnd)` may commit even though they are
|
|
90
|
+
* technically live, because they will never change. Without it, a single live
|
|
91
|
+
* block that alone overflows the window would hold its scrolled-off head out
|
|
92
|
+
* of history until it finalizes. Volatile live blocks (tool previews that
|
|
93
|
+
* collapse) omit it. Defaults to `liveRegionStart` when absent; a root that
|
|
94
|
+
* reports no seam at all commits everything that scrolls (shell semantics).
|
|
95
|
+
* `getNativeScrollbackSnapshotSafeEnd` optionally reports a still deeper
|
|
96
|
+
* boundary: the line index up to which the live region is *durable* — its rows
|
|
97
|
+
* may still change bytes later (a streaming markdown table re-aligning its
|
|
98
|
+
* columns every row), but their CURRENT snapshot is permanent content, so
|
|
99
|
+
* dropping them when they scroll above the window is forbidden. Unlike
|
|
100
|
+
* `commitSafeEnd` (byte-stable: offered rows are asserted never to re-layout and
|
|
101
|
+
* stay under the committed-prefix audit), rows committed under the snapshot end
|
|
102
|
+
* are audit-EXEMPT once they pass the window top — the engine appends their
|
|
103
|
+
* scroll-off snapshot and never recommits them, so later layout drift becomes a
|
|
104
|
+
* frozen stale row in history (duplication never loss) instead of either a
|
|
105
|
+
* dropped row or an audit re-anchor spray. Provisional live blocks (collapsing
|
|
106
|
+
* tool/edit previews whose head is a throwaway tail window) omit it. Defaults to
|
|
107
|
+
* `commitSafeEnd ?? liveRegionStart` when absent.
|
|
108
|
+
*
|
|
109
|
+
* When several root children report a seam in the same frame, the topmost
|
|
110
|
+
* one (and its commit-safe / snapshot-safe extension) defines the boundary:
|
|
111
|
+
* commits are prefix-only, so everything below the first seam is already
|
|
112
|
+
* excluded.
|
|
113
|
+
*/
|
|
114
|
+
export interface NativeScrollbackLiveRegion {
|
|
115
|
+
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
116
|
+
getNativeScrollbackCommitSafeEnd?(): number | undefined;
|
|
117
|
+
getNativeScrollbackSnapshotSafeEnd?(): number | undefined;
|
|
118
|
+
}
|
|
119
|
+
export interface NativeScrollbackCommittedRows {
|
|
120
|
+
setNativeScrollbackCommittedRows(rows: number): void;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Opt-in stability report for components that mutate their returned render
|
|
124
|
+
* array in place across frames (instead of returning a fresh array per
|
|
125
|
+
* change). The engine reads it right after the component's `render()` returns:
|
|
126
|
+
* the report counts the leading rows of the just-returned array that are
|
|
127
|
+
* byte-identical to the array state the reader last observed. The engine uses
|
|
128
|
+
* it to reuse the composed frame's prefix — skipping marker extraction, line
|
|
129
|
+
* preparation, and the committed-prefix audit for those rows.
|
|
130
|
+
*
|
|
131
|
+
* Contract:
|
|
132
|
+
* - Reading CONSUMES the report: it re-bases the baseline to the current
|
|
133
|
+
* array state. The accumulated count therefore covers every render since
|
|
134
|
+
* the previous read, so out-of-band `render()` calls between engine frames
|
|
135
|
+
* (an exporter walking the tree) can only lower the report, never inflate
|
|
136
|
+
* it past what the engine actually has.
|
|
137
|
+
* - An implementer that cannot prove stability for a frame must lower the
|
|
138
|
+
* accumulated count to 0 for that render.
|
|
139
|
+
* - Rows at or beyond the report may have been mutated in place; rows before
|
|
140
|
+
* it must be the identical string values at the identical indices.
|
|
141
|
+
*/
|
|
142
|
+
export interface RenderStablePrefix {
|
|
143
|
+
getRenderStablePrefixRows(): number;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Opt-in fast path for composing only the visible tail of a tall component
|
|
147
|
+
* during a terminal resize. A drag emits a SIGWINCH burst, and the width
|
|
148
|
+
* changes on every event: a full compose re-lays-out (and, for markdown,
|
|
149
|
+
* re-lexes) the entire transcript per event — O(history) work that is
|
|
150
|
+
* discarded the instant the next event arrives. While the resize is in flight
|
|
151
|
+
* the engine paints only the viewport, so it asks each tall root child for at
|
|
152
|
+
* most `maxRows` rows from the bottom of its render at `width` and skips
|
|
153
|
+
* composing everything above the fold. The authoritative full paint replays
|
|
154
|
+
* once the drag settles (see {@link TUI} resize handling).
|
|
155
|
+
*
|
|
156
|
+
* Contract:
|
|
157
|
+
* - Returns the BOTTOM rows of the component's full render at `width`, in
|
|
158
|
+
* top-to-bottom order, capped at `maxRows` (fewer when the component is
|
|
159
|
+
* shorter). The rows MUST be byte-identical to the corresponding tail of
|
|
160
|
+
* what `render(width)` would have returned, modulo a one-row separator at
|
|
161
|
+
* the very top edge (a transient frame the settle paint overwrites).
|
|
162
|
+
* - MUST NOT mutate any persistent full-compose state: the next `render()`
|
|
163
|
+
* (the settle paint) has to reconcile exactly as if the tail render never
|
|
164
|
+
* happened. Warming pure per-width render caches is fine and desirable.
|
|
165
|
+
*/
|
|
166
|
+
export interface ViewportTailProvider {
|
|
167
|
+
renderViewportTail(width: number, maxRows: number): readonly string[];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Interface for components that can receive focus and display a cursor.
|
|
171
|
+
* When focused, the component should emit CURSOR_MARKER at the cursor position
|
|
172
|
+
* in its render output. TUI will find this marker and position the hardware
|
|
173
|
+
* cursor there for proper IME candidate window positioning.
|
|
174
|
+
*
|
|
175
|
+
* Components that can switch between terminal-cursor and software-cursor
|
|
176
|
+
* rendering expose `setUseTerminalCursor`; TUI keeps that mode in sync with
|
|
177
|
+
* its resolved hardware-cursor preference whenever focus or the preference
|
|
178
|
+
* changes.
|
|
179
|
+
*/
|
|
180
|
+
export interface Focusable {
|
|
181
|
+
/** Set by TUI when focus changes. Component should emit CURSOR_MARKER when true. */
|
|
182
|
+
focused: boolean;
|
|
183
|
+
/** Set by TUI when hardware cursor rendering is enabled or disabled. */
|
|
184
|
+
setUseTerminalCursor?(useTerminalCursor: boolean): void;
|
|
185
|
+
}
|
|
186
|
+
/** Options for scheduling a TUI render. */
|
|
187
|
+
export interface RenderRequestOptions {
|
|
188
|
+
/** Clear terminal scrollback for intentional transcript replacement. */
|
|
189
|
+
clearScrollback?: boolean;
|
|
190
|
+
}
|
|
191
|
+
/** Type guard to check if a component implements Focusable */
|
|
192
|
+
export declare function isFocusable(component: Component | null): component is Component & Focusable;
|
|
193
|
+
/**
|
|
194
|
+
* Cursor position marker - APC (Application Program Command) sequence.
|
|
195
|
+
* This is a zero-width escape sequence that terminals ignore.
|
|
196
|
+
* Components emit this at the cursor position when focused.
|
|
197
|
+
* TUI finds and strips this marker, then positions the hardware cursor there.
|
|
198
|
+
*/
|
|
199
|
+
export declare const CURSOR_MARKER = "\u001B_pi:c\u0007";
|
|
200
|
+
export { visibleWidth };
|
|
201
|
+
/**
|
|
202
|
+
* Anchor position for overlays
|
|
203
|
+
*/
|
|
204
|
+
export type OverlayAnchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" | "left-center" | "right-center";
|
|
205
|
+
/**
|
|
206
|
+
* Margin configuration for overlays
|
|
207
|
+
*/
|
|
208
|
+
export interface OverlayMargin {
|
|
209
|
+
top?: number;
|
|
210
|
+
right?: number;
|
|
211
|
+
bottom?: number;
|
|
212
|
+
left?: number;
|
|
213
|
+
}
|
|
214
|
+
/** Value that can be absolute (number) or percentage (string like "50%") */
|
|
215
|
+
export type SizeValue = number | `${number}%`;
|
|
216
|
+
/**
|
|
217
|
+
* Options for overlay positioning and sizing.
|
|
218
|
+
* Values can be absolute numbers or percentage strings (e.g., "50%").
|
|
219
|
+
*/
|
|
220
|
+
export interface OverlayOptions {
|
|
221
|
+
/** Width in columns, or percentage of terminal width (e.g., "50%") */
|
|
222
|
+
width?: SizeValue;
|
|
223
|
+
/** Minimum width in columns */
|
|
224
|
+
minWidth?: number;
|
|
225
|
+
/** Maximum height in rows, or percentage of terminal height (e.g., "50%") */
|
|
226
|
+
maxHeight?: SizeValue;
|
|
227
|
+
/** Anchor point for positioning (default: 'center') */
|
|
228
|
+
anchor?: OverlayAnchor;
|
|
229
|
+
/** Horizontal offset from anchor position (positive = right) */
|
|
230
|
+
offsetX?: number;
|
|
231
|
+
/** Vertical offset from anchor position (positive = down) */
|
|
232
|
+
offsetY?: number;
|
|
233
|
+
/** Row position: absolute number, or percentage (e.g., "25%" = 25% from top) */
|
|
234
|
+
row?: SizeValue;
|
|
235
|
+
/** Column position: absolute number, or percentage (e.g., "50%" = centered horizontally) */
|
|
236
|
+
col?: SizeValue;
|
|
237
|
+
/** Margin from terminal edges. Number applies to all sides. */
|
|
238
|
+
margin?: OverlayMargin | number;
|
|
239
|
+
/**
|
|
240
|
+
* Control overlay visibility based on terminal dimensions.
|
|
241
|
+
* If provided, overlay is only rendered when this returns true.
|
|
242
|
+
* Called each render cycle with current terminal dimensions.
|
|
243
|
+
*/
|
|
244
|
+
visible?: (termWidth: number, termHeight: number) => boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Borrow the terminal's alternate screen buffer for this overlay's lifetime
|
|
247
|
+
* (vim/less idiom). While the topmost visible overlay sets this, the engine
|
|
248
|
+
* paints only the modal on the alt screen and emits no ED3 / scrollback
|
|
249
|
+
* bytes, so the transcript on the normal screen stays untouched and is not
|
|
250
|
+
* scrollable behind the modal. Defaults off — all other overlays are
|
|
251
|
+
* unchanged and still draw over the transcript on the normal screen.
|
|
252
|
+
*/
|
|
253
|
+
fullscreen?: boolean;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Handle returned by showOverlay for controlling the overlay
|
|
257
|
+
*/
|
|
258
|
+
export interface OverlayHandle {
|
|
259
|
+
/** Permanently remove the overlay (cannot be shown again) */
|
|
260
|
+
hide(): void;
|
|
261
|
+
/** Temporarily hide or show the overlay */
|
|
262
|
+
setHidden(hidden: boolean): void;
|
|
263
|
+
/** Check if overlay is temporarily hidden */
|
|
264
|
+
isHidden(): boolean;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Container - a component that contains other components
|
|
268
|
+
*/
|
|
269
|
+
export declare class Container implements Component {
|
|
270
|
+
#private;
|
|
271
|
+
children: Component[];
|
|
272
|
+
setIgnoreTight(ignore: boolean): this;
|
|
273
|
+
addChild(component: Component): void;
|
|
274
|
+
removeChild(component: Component): void;
|
|
275
|
+
clear(): void;
|
|
276
|
+
invalidate(): void;
|
|
277
|
+
/**
|
|
278
|
+
* Propagate teardown to children. Call when the container's children are
|
|
279
|
+
* being permanently discarded (not when they are detached for reuse — use
|
|
280
|
+
* {@link clear} for that). Idempotent per child via each child's own dispose.
|
|
281
|
+
*/
|
|
282
|
+
dispose(): void;
|
|
283
|
+
render(width: number): readonly string[];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Merge runs of byte-adjacent SGR sequences (`CSI [0-9;:]* m`) into one. Only
|
|
287
|
+
* CSI-SGR sequences are touched; text, cursor moves, OSC, hyperlinks and image
|
|
288
|
+
* payloads pass through verbatim. Returns the original reference when nothing
|
|
289
|
+
* merges, so SGR-light lines incur only a single `indexOf` scan.
|
|
290
|
+
*/
|
|
291
|
+
export declare function coalesceAdjacentSgr(line: string): string;
|
|
292
|
+
/**
|
|
293
|
+
* Decide whether `frame` still aligns with the committed prefix, and where to
|
|
294
|
+
* re-anchor the commit index when it does not. Returns the resync row index,
|
|
295
|
+
* or -1 when no resync is needed.
|
|
296
|
+
*
|
|
297
|
+
* Audits the committed prefix [0, auditTo) EXCEPT the exempt window
|
|
298
|
+
* [exemptFrom, exemptTo): rows in the window are durable snapshots (a streaming
|
|
299
|
+
* table re-aligning its columns) that may drift legitimately, so their drift
|
|
300
|
+
* never triggers a re-anchor. Rows below the window — including forced-overflow
|
|
301
|
+
* rows committed only because they scrolled above the viewport under a
|
|
302
|
+
* commit-unstable barrier — ARE audited.
|
|
303
|
+
*
|
|
304
|
+
* Two detectors run over the audited rows:
|
|
305
|
+
*
|
|
306
|
+
* 1. Hard scan of the now-permanent forced suffix [exemptTo, permanentEnd):
|
|
307
|
+
* forced-overflow rows that THIS frame asserts are durable/permanent (index <
|
|
308
|
+
* permanentEnd — the barrier above them finalized or cleared, so durableBoundary
|
|
309
|
+
* rose past them). A content change there is real finalized content, so ANY
|
|
310
|
+
* mismatch re-anchors. Scanned in FULL, not sampled, so a single edit far above
|
|
311
|
+
* the commit boundary with an unchanged tail still re-anchors (duplication,
|
|
312
|
+
* never loss) instead of being committed nowhere and painted nowhere.
|
|
313
|
+
* 2. Tail sample (only when the hard scan is clean): exploits the asymmetry
|
|
314
|
+
* between the two mutation classes — an in-place edit/restyle of a committed
|
|
315
|
+
* row disturbs only the touched rows (alignment below intact; the stale copy
|
|
316
|
+
* in history is the long-accepted artifact), while an insertion/deletion
|
|
317
|
+
* shifts EVERY row below it. So up to 8 non-blank rows within the last 24
|
|
318
|
+
* audited rows are compared SGR-stripped (theme changes stay quiet),
|
|
319
|
+
* tolerating a SINGLE non-hard mismatch (a legitimate one-row edit): aligned ⇒
|
|
320
|
+
* no resync; misaligned ⇒ resync at the first non-equivalent audited row. The
|
|
321
|
+
* tolerance keeps both an offscreen still-live barrier (a ticking spinner) and
|
|
322
|
+
* a no-seam in-place row edit from spraying duplicate snapshots every frame;
|
|
323
|
+
* the hard scan above is what forbids it from swallowing a finalized row.
|
|
324
|
+
*
|
|
325
|
+
* Highly repetitive tails (identical filler rows) can mask a shift in the tail
|
|
326
|
+
* sample, in which case the skipped rows are content-identical to the committed
|
|
327
|
+
* ones — observationally harmless. Exported for the render-stress harness, whose
|
|
328
|
+
* shadow commit ledger must mirror the engine's law exactly.
|
|
329
|
+
*/
|
|
330
|
+
export declare function findCommittedPrefixResync(frame: readonly string[], prefix: readonly string[], auditTo?: number, exemptFrom?: number, exemptTo?: number, permanentEnd?: number): number;
|
|
331
|
+
/**
|
|
332
|
+
* TUI - Main class for managing terminal UI with differential rendering
|
|
333
|
+
*/
|
|
334
|
+
export declare class TUI extends Container {
|
|
335
|
+
#private;
|
|
336
|
+
terminal: Terminal;
|
|
337
|
+
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
338
|
+
onDebug?: () => void;
|
|
339
|
+
overlayStack: {
|
|
340
|
+
component: Component;
|
|
341
|
+
options?: OverlayOptions;
|
|
342
|
+
preFocus: Component | null;
|
|
343
|
+
hidden: boolean;
|
|
344
|
+
}[];
|
|
345
|
+
constructor(terminal: Terminal, showHardwareCursor?: boolean, options?: TUIOptions);
|
|
346
|
+
render(width: number): readonly string[];
|
|
347
|
+
get fullRedraws(): number;
|
|
348
|
+
/**
|
|
349
|
+
* Transient viewport-only paints emitted by the non-multiplexer resize fast
|
|
350
|
+
* path. These never touch native scrollback or the commit ledger, so they
|
|
351
|
+
* are counted apart from {@link fullRedraws}.
|
|
352
|
+
*/
|
|
353
|
+
get resizeViewportPaints(): number;
|
|
354
|
+
/** Whether a non-multiplexer resize drag is currently in flight. */
|
|
355
|
+
get resizeViewportActive(): boolean;
|
|
356
|
+
/** Shared budget that caps how many inline images render as live graphics. */
|
|
357
|
+
get imageBudget(): ImageBudget;
|
|
358
|
+
/**
|
|
359
|
+
* Set how many inline images stay live graphics before older ones fall back
|
|
360
|
+
* to text (`0` disables the cap). Older images are hidden via a graphics purge
|
|
361
|
+
* plus a full redraw on the frame after a new image exceeds the cap.
|
|
362
|
+
*/
|
|
363
|
+
setMaxInlineImages(cap: number): void;
|
|
364
|
+
getShowHardwareCursor(): boolean;
|
|
365
|
+
setShowHardwareCursor(enabled: boolean): void;
|
|
366
|
+
/**
|
|
367
|
+
* Whether DEC 2026 synchronized-output wrappers are currently emitted around
|
|
368
|
+
* paints. Starts from conservative terminal/env detection and is reconciled at
|
|
369
|
+
* runtime against the terminal's DECRQM mode-2026 report — enabled on a
|
|
370
|
+
* positive report, disabled on a negative one.
|
|
371
|
+
*/
|
|
372
|
+
get synchronizedOutput(): boolean;
|
|
373
|
+
setFocus(component: Component | null): void;
|
|
374
|
+
/** Component currently receiving keyboard input, if any. */
|
|
375
|
+
getFocused(): Component | null;
|
|
376
|
+
/**
|
|
377
|
+
* Show an overlay component with configurable positioning and sizing.
|
|
378
|
+
* Returns a handle to control the overlay's visibility.
|
|
379
|
+
*/
|
|
380
|
+
showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
|
|
381
|
+
/** Hide the topmost overlay and restore previous focus. */
|
|
382
|
+
hideOverlay(): void;
|
|
383
|
+
/** Check if there are any visible overlays */
|
|
384
|
+
hasOverlay(): boolean;
|
|
385
|
+
invalidate(): void;
|
|
386
|
+
start(options?: TUIStartOptions): void;
|
|
387
|
+
addStartListener(listener: StartListener): () => void;
|
|
388
|
+
addInputListener(listener: InputListener): () => void;
|
|
389
|
+
removeInputListener(listener: InputListener): void;
|
|
390
|
+
stop(): void;
|
|
391
|
+
/**
|
|
392
|
+
* Force an immediate full replay of the current frame, including native
|
|
393
|
+
* scrollback. This is the keyboard-accessible equivalent of the resize reset:
|
|
394
|
+
* no queued diff frame or terminal scrollback probe can downgrade it to a
|
|
395
|
+
* viewport-only repaint.
|
|
396
|
+
*
|
|
397
|
+
* Invalidates every component first so the replay reflects current state. A
|
|
398
|
+
* geometry-driven reset thaws frozen scrollback snapshots implicitly (the new
|
|
399
|
+
* width misses every cached snapshot), but a same-width reset would otherwise
|
|
400
|
+
* replay stale snapshots — leaving host-frozen blocks (e.g. a transcript whose
|
|
401
|
+
* committed rows are immutable on ED3-risk terminals) showing pre-mutation
|
|
402
|
+
* content. Invalidation is the generic signal those containers use to retire
|
|
403
|
+
* their snapshots, which is exactly what a user-driven display reset wants.
|
|
404
|
+
*/
|
|
405
|
+
resetDisplay(): void;
|
|
406
|
+
requestRender(force?: boolean, options?: RenderRequestOptions): void;
|
|
407
|
+
/**
|
|
408
|
+
* Schedule a render on behalf of `component` after a self-contained change
|
|
409
|
+
* (spinner frame, blink) that cannot have affected any other component.
|
|
410
|
+
*
|
|
411
|
+
* When every request since the last frame is component-scoped and the
|
|
412
|
+
* frame is otherwise quiet — no resize or geometry change, no overlays, no
|
|
413
|
+
* live inline images, no forced repaint, unchanged root child list — the
|
|
414
|
+
* next compose re-renders only the root subtrees containing the requesting
|
|
415
|
+
* components and reuses the previous frame's rows (and seam reports) for
|
|
416
|
+
* every other root child, skipping the full component-tree walk that makes
|
|
417
|
+
* long transcripts expensive to repaint at animation rate. Any concurrent
|
|
418
|
+
* full request or unsafe condition downgrades the frame to a normal full
|
|
419
|
+
* compose, so this is never less correct than `requestRender()` — only
|
|
420
|
+
* cheaper.
|
|
421
|
+
*/
|
|
422
|
+
requestComponentRender(component: Component): void;
|
|
423
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Ellipsis, type ExtractSegmentsResult, type SliceResult } from "jeopi-natives";
|
|
2
|
+
export { Ellipsis } from "jeopi-natives";
|
|
3
|
+
export { DEFAULT_TAB_WIDTH } from "jeopi-utils";
|
|
4
|
+
export type HangulCompatibilityJamoWidth = "platform" | "unicode" | 1 | 2;
|
|
5
|
+
export declare function getHangulCompatibilityJamoWidth(): HangulCompatibilityJamoWidth;
|
|
6
|
+
export declare function setHangulCompatibilityJamoWidth(width: HangulCompatibilityJamoWidth): boolean;
|
|
7
|
+
export declare function resetHangulCompatibilityJamoWidthForTests(): void;
|
|
8
|
+
export type TextSizingScale = 1 | 2 | 3;
|
|
9
|
+
export type TextSizingVerticalAlign = "top" | "bottom" | "center";
|
|
10
|
+
export type TextSizingHorizontalAlign = "left" | "right" | "center";
|
|
11
|
+
export interface TextSizingOptions {
|
|
12
|
+
scale?: TextSizingScale;
|
|
13
|
+
widthCells?: number;
|
|
14
|
+
verticalAlign?: TextSizingVerticalAlign;
|
|
15
|
+
horizontalAlign?: TextSizingHorizontalAlign;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encode a plain-text span using Kitty's OSC 66 text-sizing protocol. The TUI
|
|
19
|
+
* emits only safe UTF-8 payloads and ST terminators so its ANSI parser and the
|
|
20
|
+
* terminal agree on span boundaries.
|
|
21
|
+
*/
|
|
22
|
+
export declare function encodeTextSized(text: string, options?: TextSizingOptions): string;
|
|
23
|
+
export declare function sliceWithWidth(line: string, startCol: number, length: number, strict?: boolean | null): SliceResult;
|
|
24
|
+
export declare function truncateToWidth(text: string, maxWidth: number, ellipsisKind?: Ellipsis | null | "", pad?: boolean | null): string;
|
|
25
|
+
export declare function wrapTextWithAnsi(text: string, width: number): string[];
|
|
26
|
+
export declare function extractSegments(line: string, beforeEnd: number, afterStart: number, afterLen: number, strictAfter: boolean): ExtractSegmentsResult;
|
|
27
|
+
export declare function replaceTabs(text: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns a string of n spaces. Uses a pre-allocated buffer for efficiency.
|
|
30
|
+
*/
|
|
31
|
+
export declare function padding(n: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* Get the shared grapheme segmenter instance.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getSegmenter(): Intl.Segmenter;
|
|
36
|
+
/**
|
|
37
|
+
* Visible width of a string in terminal columns, excluding ANSI/OSC escapes.
|
|
38
|
+
*
|
|
39
|
+
* `Bun.stringWidth` does the heavy lifting (UAX#11 width tables + ANSI/OSC
|
|
40
|
+
* stripping); this adds the two corrections it omits — tabs (expanded to
|
|
41
|
+
* `tabWidth` cells) and OSC 66 text-sizing payloads (scaled by `s=`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function visibleWidth(str: string): number;
|
|
44
|
+
/**
|
|
45
|
+
* Normalize text for terminal output without changing logical editor content.
|
|
46
|
+
* Some terminals render precomposed Thai/Lao AM vowels inconsistently during
|
|
47
|
+
* differential repaint. Their compatibility decompositions have the same cell
|
|
48
|
+
* width but avoid stale-cell artifacts in terminal renderers.
|
|
49
|
+
*/
|
|
50
|
+
export declare function normalizeTerminalOutput(str: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Check if a character is whitespace.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isWhitespaceChar(char: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Check if a character is punctuation.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isPunctuationChar(char: string): boolean;
|
|
59
|
+
export type WordNavKind = "whitespace" | "delimiter" | "cjk" | "word" | "other";
|
|
60
|
+
/**
|
|
61
|
+
* Coarse Unicode-aware character classification for word navigation (Option/Alt + Left/Right).
|
|
62
|
+
* This intentionally avoids language-specific word segmentation for predictability across scripts.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getWordNavKind(grapheme: string): WordNavKind;
|
|
65
|
+
export declare function isWordNavJoiner(grapheme: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Move the cursor one "word" to the left using Unicode-aware coarse navigation.
|
|
68
|
+
*
|
|
69
|
+
* Returns a new cursor index in the range [0, text.length].
|
|
70
|
+
*/
|
|
71
|
+
export declare function moveWordLeft(text: string, cursor: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* Move the cursor one "word" to the right using Unicode-aware coarse navigation.
|
|
74
|
+
*
|
|
75
|
+
* Returns a new cursor index in the range [0, text.length].
|
|
76
|
+
*/
|
|
77
|
+
export declare function moveWordRight(text: string, cursor: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* Apply background color to a line, padding to full width.
|
|
80
|
+
*
|
|
81
|
+
* @param line - Line of text (may contain ANSI codes)
|
|
82
|
+
* @param width - Total width to pad to
|
|
83
|
+
* @param bgFn - Background color function
|
|
84
|
+
* @returns Line with background applied and padded to width
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Extract a range of visible columns from a line. Handles ANSI codes and wide chars.
|
|
89
|
+
*
|
|
90
|
+
* @param strict - If true, exclude wide chars at boundary that would extend past the range
|
|
91
|
+
*/
|
|
92
|
+
export declare function sliceByColumn(line: string, startCol: number, length: number, strict?: boolean): string;
|
|
93
|
+
export declare function setTuiTight(tight: boolean): void;
|
|
94
|
+
export declare function isTuiTight(): boolean;
|
|
95
|
+
export declare function getPaddingX(basePadding: number): number;
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "jeopi-tui",
|
|
4
|
+
"version": "16.2.13",
|
|
5
|
+
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
|
+
"homepage": "https://github.com/akillness/jeopi",
|
|
7
|
+
"author": "Can Boluk",
|
|
8
|
+
"contributors": [
|
|
9
|
+
"Mario Zechner"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/akillness/jeopi.git",
|
|
15
|
+
"directory": "packages/tui"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/akillness/jeopi/issues"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"tui",
|
|
22
|
+
"terminal",
|
|
23
|
+
"ui",
|
|
24
|
+
"text-editor",
|
|
25
|
+
"differential-rendering",
|
|
26
|
+
"typescript",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"main": "./src/index.ts",
|
|
30
|
+
"types": "./dist/types/index.d.ts",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"check": "biome check . && bun run check:types",
|
|
33
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
34
|
+
"lint": "biome lint .",
|
|
35
|
+
"test": "bun test --parallel test/*.test.ts",
|
|
36
|
+
"fix": "biome check --write --unsafe .",
|
|
37
|
+
"fmt": "biome format --write ."
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"jeopi-natives": "16.2.13",
|
|
41
|
+
"jeopi-utils": "16.2.13",
|
|
42
|
+
"lru-cache": "11.5.1",
|
|
43
|
+
"marked": "^18.0.5"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"chalk": "^5.6.2",
|
|
47
|
+
"ghostty-web": "^0.4.0"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"bun": ">=1.3.14"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"src",
|
|
54
|
+
"README.md",
|
|
55
|
+
"CHANGELOG.md",
|
|
56
|
+
"dist/types"
|
|
57
|
+
],
|
|
58
|
+
"exports": {
|
|
59
|
+
".": {
|
|
60
|
+
"types": "./dist/types/index.d.ts",
|
|
61
|
+
"import": "./src/index.ts"
|
|
62
|
+
},
|
|
63
|
+
"./*": {
|
|
64
|
+
"types": "./dist/types/*.d.ts",
|
|
65
|
+
"import": "./src/*.ts"
|
|
66
|
+
},
|
|
67
|
+
"./components/*": {
|
|
68
|
+
"types": "./dist/types/components/*.d.ts",
|
|
69
|
+
"import": "./src/components/*.ts"
|
|
70
|
+
},
|
|
71
|
+
"./*.js": "./src/*.ts"
|
|
72
|
+
}
|
|
73
|
+
}
|