@linxiraos/pi-tui 1.0.3 → 1.0.5

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/types/autocomplete.d.ts +116 -0
  3. package/dist/types/bracketed-paste.d.ts +51 -0
  4. package/dist/types/components/box.d.ts +31 -0
  5. package/dist/types/components/cancellable-loader.d.ts +21 -0
  6. package/dist/types/components/editor.d.ts +166 -0
  7. package/dist/types/components/image.d.ts +165 -0
  8. package/dist/types/components/input.d.ts +25 -0
  9. package/dist/types/components/loader.d.ts +25 -0
  10. package/dist/types/components/markdown.d.ts +92 -0
  11. package/dist/types/components/scroll-view.d.ts +62 -0
  12. package/dist/types/components/select-list.d.ts +69 -0
  13. package/dist/types/components/settings-list.d.ts +123 -0
  14. package/dist/types/components/spacer.d.ts +11 -0
  15. package/dist/types/components/tab-bar.d.ts +89 -0
  16. package/dist/types/components/text.d.ts +28 -0
  17. package/dist/types/components/truncated-text.d.ts +10 -0
  18. package/dist/types/deccara.d.ts +49 -0
  19. package/dist/types/desktop-notify.d.ts +52 -0
  20. package/dist/types/editor-component.d.ts +38 -0
  21. package/dist/types/fuzzy.d.ts +48 -0
  22. package/dist/types/index.d.ts +32 -0
  23. package/dist/types/keybindings.d.ts +197 -0
  24. package/dist/types/keys.d.ts +210 -0
  25. package/dist/types/kill-ring.d.ts +20 -0
  26. package/dist/types/kitty-graphics.d.ts +76 -0
  27. package/dist/types/latex-block.d.ts +8 -0
  28. package/dist/types/latex-to-unicode.d.ts +50 -0
  29. package/dist/types/loop-watchdog.d.ts +44 -0
  30. package/dist/types/mouse.d.ts +67 -0
  31. package/dist/types/stdin-buffer.d.ts +60 -0
  32. package/dist/types/symbols.d.ts +25 -0
  33. package/dist/types/terminal-capabilities.d.ts +324 -0
  34. package/dist/types/terminal.d.ts +175 -0
  35. package/dist/types/tmux.d.ts +6 -0
  36. package/dist/types/ttyid.d.ts +9 -0
  37. package/dist/types/tui.d.ts +480 -0
  38. package/dist/types/utils.d.ts +112 -0
  39. package/package.json +70 -69
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [1.0.5] - 2026-08-16
4
4
 
5
5
  ## [1.0.2] - 2026-08-15
6
6
 
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Locate the slash that opens a slash command on the line, allowing leading
3
+ * whitespace. Returns the index of the `/` or `null` when the line is not a
4
+ * slash command. Aligns with `trimStart` semantics so the editor and provider
5
+ * agree on which prefixes count.
6
+ */
7
+ export declare function findLeadingSlashCommandStart(text: string): number | null;
8
+ export declare function findTrailingSlashCommandStart(text: string): number | null;
9
+ export interface AutocompleteItem {
10
+ value: string;
11
+ label: string;
12
+ description?: string;
13
+ /** Dim hint text shown inline after cursor when this item is selected */
14
+ hint?: string;
15
+ }
16
+ type Awaitable<T> = T | Promise<T>;
17
+ export interface SlashCommand {
18
+ name: string;
19
+ aliases?: string[];
20
+ description?: string;
21
+ argumentHint?: string;
22
+ /** Whether the command consumes argument text after the command name. False means the full input stays normal prompt text once args are present. */
23
+ allowArgs?: boolean;
24
+ /** Dynamic display-only description for slash-command autocomplete. Must be synchronous and side-effect free. */
25
+ getAutocompleteDescription?: () => string | undefined;
26
+ getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
27
+ /** Return inline hint text for the current argument state (shown as dim ghost text after cursor) */
28
+ getInlineHint?(argumentText: string): string | null;
29
+ }
30
+ export interface AutocompleteProvider {
31
+ /** Get autocomplete suggestions for current text/cursor position */
32
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
33
+ items: AutocompleteItem[];
34
+ prefix: string;
35
+ } | null>;
36
+ /** Apply the selected item and return new text + cursor position */
37
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
38
+ lines: string[];
39
+ cursorLine: number;
40
+ cursorCol: number;
41
+ onApplied?: () => void;
42
+ };
43
+ /** Get inline hint text to show as dim ghost text after the cursor */
44
+ getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
45
+ /** Synchronously try to complete a slash command at the start of a line (no async I/O). */
46
+ /** Returns matched items and the full prefix, or null if not applicable. */
47
+ trySyncSlashCompletion?(textBeforeCursor: string): {
48
+ items: AutocompleteItem[];
49
+ prefix: string;
50
+ } | null;
51
+ /**
52
+ * Synchronously try to expand text immediately before the cursor (no async I/O).
53
+ * Called after every single-character insert. Implementations MUST cheaply
54
+ * early-return when the trailing context cannot trigger them.
55
+ * Returns the number of characters to delete immediately before the cursor
56
+ * and the literal string to insert in their place, or null to leave the
57
+ * buffer untouched.
58
+ */
59
+ trySyncInlineReplace?(textBeforeCursor: string): {
60
+ replaceLen: number;
61
+ insert: string;
62
+ } | null;
63
+ /**
64
+ * Force file-path completion (called on Tab). Returns matched items plus the
65
+ * full prefix, or null when no path token sits before the cursor. Present on
66
+ * file-aware providers; absent on slash-only ones.
67
+ */
68
+ getForceFileSuggestions?(lines: string[], cursorLine: number, cursorCol: number): Promise<{
69
+ items: AutocompleteItem[];
70
+ prefix: string;
71
+ } | null>;
72
+ /** Whether a Tab press should attempt file completion at the cursor. */
73
+ shouldTriggerFileCompletion?(lines: string[], cursorLine: number, cursorCol: number): boolean;
74
+ }
75
+ type CommandEntry = SlashCommand | AutocompleteItem;
76
+ export declare function scoreCommandTextMatch(lowerPrefix: string, lowerTarget: string): number;
77
+ /**
78
+ * Whether a mid-prompt slash token (`prose … /tok`) is skill-shaped enough to
79
+ * surface `name` in the skill popup. Deliberately stricter than submitted
80
+ * slash-command matching: a stray `/word` in running prose must not keep the
81
+ * popup alive through fuzzy name/description hits, so a token only matches as
82
+ * - a prefix of the `skill:` namespace (incl. the bare `/` entry point),
83
+ * - an explicit `skill:…` query (full fuzzy name/description search), or
84
+ * - a prefix of the skill's bare name (`/hum` → `skill:humanizer`).
85
+ * Anything else yields no items, letting the caller fall through to path
86
+ * completion or close the popup. Shared with the editor's accept-time
87
+ * staleness guard so Tab/Enter never accepts a skill the refreshed popup
88
+ * would no longer show.
89
+ */
90
+ export declare function midPromptSkillTokenMatches(lowerToken: string, name: string, description?: string): boolean;
91
+ export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
92
+ #private;
93
+ constructor(commands?: CommandEntry[], basePath?: string);
94
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
95
+ items: AutocompleteItem[];
96
+ prefix: string;
97
+ } | null>;
98
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
99
+ lines: string[];
100
+ cursorLine: number;
101
+ cursorCol: number;
102
+ };
103
+ invalidateDirCache(dir?: string): void;
104
+ getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
105
+ items: AutocompleteItem[];
106
+ prefix: string;
107
+ } | null>;
108
+ shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
109
+ /** Get inline hint text for slash commands with subcommand hints */
110
+ getInlineHint(lines: string[], cursorLine: number, cursorCol: number): string | null;
111
+ trySyncSlashCompletion(textBeforeCursor: string): {
112
+ items: AutocompleteItem[];
113
+ prefix: string;
114
+ } | null;
115
+ }
116
+ export {};
@@ -0,0 +1,51 @@
1
+ export type PasteResult = {
2
+ handled: false;
3
+ } | {
4
+ handled: true;
5
+ pasteContent?: string;
6
+ remaining: string;
7
+ };
8
+ /**
9
+ * Decode tmux's re-encoded control bytes (both `extended-keys-format` variants) inside a
10
+ * bracketed-paste payload back to their literal byte (e.g. Ctrl+J → "\n"). Leaves the rest of
11
+ * the text untouched. Call before any control-character stripping so newlines/tabs survive
12
+ * instead of leaking the printable escape tail into the buffer.
13
+ */
14
+ export declare function decodeReencodedPasteControls(text: string): string;
15
+ /**
16
+ * Options for {@link BracketedPasteHandler}.
17
+ */
18
+ export type BracketedPasteHandlerOptions = {
19
+ /**
20
+ * Byte cap for buffered paste content (default: 64 MiB). When exceeded,
21
+ * paste mode is aborted and the accumulated content is delivered as
22
+ * `pasteContent` on the same `process()` call so a lost/corrupted end
23
+ * marker cannot consume unbounded memory. Mirrors `StdinBuffer#abortPaste`
24
+ * — defense in depth for callers that bypass `StdinBuffer` (issue #4073
25
+ * case B). The normal `ProcessTerminal` path re-wraps `StdinBuffer`'s
26
+ * bounded paste with both markers, so this cap only fires on alternate
27
+ * callers.
28
+ */
29
+ byteLimit?: number;
30
+ };
31
+ /**
32
+ * Handles bracketed paste mode buffering for terminal input components.
33
+ *
34
+ * Bracketed paste mode wraps pasted content between start (\x1b[200~) and
35
+ * end (\x1b[201~) markers, which may arrive split across multiple chunks.
36
+ * This class buffers incoming data and assembles complete paste payloads.
37
+ */
38
+ export declare class BracketedPasteHandler {
39
+ #private;
40
+ constructor(options?: BracketedPasteHandlerOptions);
41
+ /**
42
+ * Process incoming terminal data for bracketed paste sequences.
43
+ *
44
+ * @returns `{ handled: false }` if the data contains no paste sequence and
45
+ * should be processed normally. `{ handled: true }` if the data was
46
+ * consumed by paste buffering — `pasteContent` is set when a complete
47
+ * paste has been assembled (or the byte cap has aborted a runaway
48
+ * buffer); omitted when still buffering.
49
+ */
50
+ process(data: string): PasteResult;
51
+ }
@@ -0,0 +1,31 @@
1
+ import type { Component } from "../tui.js";
2
+ /** Box-drawing glyphs plus an optional colorizer for an outline drawn around a {@link Box}. */
3
+ export interface BoxBorder {
4
+ chars: {
5
+ topLeft: string;
6
+ topRight: string;
7
+ bottomLeft: string;
8
+ bottomRight: string;
9
+ horizontal: string;
10
+ vertical: string;
11
+ };
12
+ color?: (text: string) => string;
13
+ }
14
+ /**
15
+ * Box component - a container that applies padding and background to all children
16
+ */
17
+ export declare class Box implements Component {
18
+ #private;
19
+ children: Component[];
20
+ setIgnoreTight(ignore: boolean): this;
21
+ constructor(paddingX?: number, paddingY?: number, bgFn?: (text: string) => string, border?: BoxBorder);
22
+ addChild(component: Component): void;
23
+ removeChild(component: Component): void;
24
+ clear(): void;
25
+ setPaddingX(paddingX: number): void;
26
+ setPaddingY(paddingY: number): void;
27
+ setBgFn(bgFn?: (text: string) => string): void;
28
+ setBorder(border?: BoxBorder): void;
29
+ invalidate(): void;
30
+ render(width: number): readonly string[];
31
+ }
@@ -0,0 +1,21 @@
1
+ import { Loader } from "./loader.js";
2
+ /**
3
+ * Loader that can be cancelled with Escape.
4
+ * Extends Loader with an AbortSignal for cancelling async operations.
5
+ *
6
+ * @example
7
+ * const loader = new CancellableLoader(tui, cyan, dim, "Working...");
8
+ * loader.onAbort = () => done(null);
9
+ * doWork(loader.signal).then(done);
10
+ */
11
+ export declare class CancellableLoader extends Loader {
12
+ #private;
13
+ /** Called when user presses Escape */
14
+ onAbort?: () => void;
15
+ /** AbortSignal that is aborted when user presses Escape */
16
+ get signal(): AbortSignal;
17
+ /** Whether the loader was aborted */
18
+ get aborted(): boolean;
19
+ handleInput(data: string): void;
20
+ dispose(): void;
21
+ }
@@ -0,0 +1,166 @@
1
+ import { type AutocompleteProvider } from "../autocomplete.js";
2
+ import type { SymbolTheme } from "../symbols.js";
3
+ import { type Component, type Focusable } from "../tui.js";
4
+ import { type SelectListTheme } from "./select-list.js";
5
+ export interface EditorTheme {
6
+ borderColor: (str: string) => string;
7
+ selectList: SelectListTheme;
8
+ symbols: SymbolTheme;
9
+ editorPaddingX?: number;
10
+ /** Style function for inline hint/ghost text (dim text after cursor) */
11
+ hintStyle?: (text: string) => string;
12
+ }
13
+ export interface EditorTopBorder {
14
+ /** The status content (already styled) */
15
+ content: string;
16
+ /** Visible width of the content */
17
+ width: number;
18
+ /** Optional logical revision that changes independently of available width. */
19
+ revision?: number;
20
+ }
21
+ interface HistoryEntry {
22
+ prompt: string;
23
+ }
24
+ interface HistoryStorage {
25
+ add(prompt: string, cwd?: string): Promise<void>;
26
+ getRecent(limit: number): HistoryEntry[];
27
+ }
28
+ export declare class Editor implements Component, Focusable {
29
+ #private;
30
+ /** Focusable interface - set by TUI when focus changes */
31
+ focused: boolean;
32
+ /** When set, replaces the normal cursor glyph at end-of-text with this ANSI-styled string. */
33
+ cursorOverride: string | undefined;
34
+ /** Display width of the cursorOverride glyph (needed because override may contain ANSI escapes). */
35
+ cursorOverrideWidth: number | undefined;
36
+ /** Optional hook that decorates displayed user text after source-text layout.
37
+ * Width-changing output is allowed on lines without the cursor; it is truncated
38
+ * to the content width rather than reflowed. Cursor glyphs and inline hints are excluded. */
39
+ decorateText: ((text: string) => string) | undefined;
40
+ borderColor: (str: string) => string;
41
+ onAutocompleteUpdate?: () => void;
42
+ /** Optional pattern matching atomic placeholder tokens (e.g. `[Image #1, 800x600]` or
43
+ * `[Paste #2, +30 lines]`) that the editor treats as indivisible: a backspace or forward-delete
44
+ * landing on any character of a token removes the whole token instead of corrupting it into
45
+ * stray text. MUST be a global regex; the editor recompiles a private copy so its `lastIndex`
46
+ * is never shared with the caller. */
47
+ atomicTokenPattern: RegExp | undefined;
48
+ onSubmit?: (text: string) => void | Promise<void>;
49
+ onAltEnter?: (text: string) => void;
50
+ onChange?: (text: string) => void;
51
+ /** Called for a "marker-sized" paste — the point where the editor would otherwise collapse it
52
+ * into a `[Paste #N]` token (> 10 lines or > 1000 characters). Return `true` to intercept:
53
+ * the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
54
+ * "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
55
+ * via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
56
+ * default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count. */
57
+ onLargePaste?: (text: string, lineCount: number) => boolean;
58
+ onAutocompleteCancel?: () => void;
59
+ disableSubmit: boolean;
60
+ constructor(theme: EditorTheme);
61
+ setAutocompleteProvider(provider: AutocompleteProvider): void;
62
+ /**
63
+ * Set custom content for the top border (e.g., status line).
64
+ * Pass undefined to use the default plain border.
65
+ *
66
+ * Eager: the passed value is cached and reused every frame. Callers that
67
+ * mutate status upstream must recompute and call this again. Prefer
68
+ * {@link setTopBorderProvider} for high-frequency updates — it collapses
69
+ * per-event rebuilds to one per painted frame.
70
+ */
71
+ setTopBorder(content: EditorTopBorder | undefined): void;
72
+ /**
73
+ * Install a lazy provider invoked once per editor render with the current
74
+ * `availableWidth`. Overrides any eager content set via {@link setTopBorder}
75
+ * — pass `undefined` to detach and fall back to the eager slot.
76
+ *
77
+ * Use this when the top border derives from state that mutates far faster
78
+ * than the render cadence (session events, streaming, subagent updates).
79
+ * The TUI already throttles renders, so a provider is invoked exactly once
80
+ * per frame and does no work between paints. Return a logical `revision` to
81
+ * distinguish concurrent status mutations from pure width reflow.
82
+ */
83
+ setTopBorderProvider(provider: ((availableWidth: number) => EditorTopBorder | undefined) | undefined): void;
84
+ /**
85
+ * Show or hide the editor border chrome.
86
+ */
87
+ setBorderVisible(borderVisible: boolean): void;
88
+ setPromptGutter(promptGutter: string | undefined): void;
89
+ /**
90
+ * Get the available width for top border content given a total terminal width.
91
+ * Accounts for the border characters and horizontal padding when visible.
92
+ */
93
+ getTopBorderAvailableWidth(terminalWidth: number): number;
94
+ /**
95
+ * Use the real terminal cursor instead of rendering a cursor glyph.
96
+ */
97
+ setUseTerminalCursor(useTerminalCursor: boolean): void;
98
+ /** Render a dedicated bottom border so terminal-local IME preedit cannot shift editor chrome. */
99
+ setImeSafeCursorLayout(enabled: boolean): void;
100
+ getUseTerminalCursor(): boolean;
101
+ setMaxHeight(maxHeight: number | undefined): void;
102
+ /** Enable/disable the right-border scrollbar. Only shown when content overflows. */
103
+ setScrollbarVisible(visible: boolean): void;
104
+ setPaddingX(paddingX: number): void;
105
+ getAutocompleteMaxVisible(): number;
106
+ setAutocompleteMaxVisible(maxVisible: number): void;
107
+ setHistoryStorage(storage: HistoryStorage): void;
108
+ /**
109
+ * Add a prompt to history for up/down arrow navigation.
110
+ * Called after successful submission.
111
+ */
112
+ addToHistory(text: string): void;
113
+ invalidate(): void;
114
+ render(width: number): readonly string[];
115
+ handleInput(data: string): void;
116
+ getText(): string;
117
+ getNativeScrollbackWidthEpochRevision(): number;
118
+ /** Whether the buffer text equals `value`, without `getText()`'s full join —
119
+ * O(1) for the hot per-keystroke probes against short single-line values. */
120
+ textEquals(value: string): boolean;
121
+ /**
122
+ * Get text with paste markers expanded to their actual content.
123
+ * Use this when you need the full content (e.g., for external editor).
124
+ */
125
+ getExpandedText(): string;
126
+ getLines(): string[];
127
+ getCursor(): {
128
+ line: number;
129
+ col: number;
130
+ };
131
+ moveToLineStart(): void;
132
+ moveToLineEnd(): void;
133
+ moveToMessageStart(): void;
134
+ moveToMessageEnd(): void;
135
+ /**
136
+ * Undo the last meaningful edit while ignoring transient text that is still present at the cursor.
137
+ * Used for command-like autocomplete actions whose typed trigger should not count as the edit being undone.
138
+ */
139
+ undoPastTransientText(transientText: string): void;
140
+ setText(text: string): void;
141
+ submit(): void;
142
+ /** Insert text at the current cursor position */
143
+ insertText(text: string): void;
144
+ /** Delete up to `count` characters immediately before the cursor on the current line.
145
+ * Used to "track back" the auto-repeat spaces that the space-hold push-to-talk gesture
146
+ * optimistically inserts before it recognizes the hold. Capped at the cursor column so it
147
+ * never crosses a line boundary or under-runs the line. */
148
+ deleteBeforeCursor(count: number): void;
149
+ /** Show or replace a volatile speech-to-text preview at the cursor. The text is
150
+ * inserted with undo suspended so a long live dictation never floods the undo
151
+ * stack; finalize it with {@link commitVolatileText} or drop it with
152
+ * {@link clearVolatileText}. Newlines are allowed. */
153
+ setVolatileText(text: string): void;
154
+ /** Remove the current volatile preview without committing it. */
155
+ clearVolatileText(): void;
156
+ /** Drop any volatile preview, then insert `text` as a single undoable edit. */
157
+ commitVolatileText(text: string): void;
158
+ /** Apply terminal paste semantics to text from non-bracketed paste transports. */
159
+ pasteText(text: string): void;
160
+ /** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
161
+ * {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
162
+ * this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
163
+ insertPaste(content: string): void;
164
+ isShowingAutocomplete(): boolean;
165
+ }
166
+ export {};
@@ -0,0 +1,165 @@
1
+ import { type ImageDimensions } from "../terminal-capabilities.js";
2
+ import type { Component } from "../tui.js";
3
+ export interface ImageTheme {
4
+ fallbackColor: (str: string) => string;
5
+ }
6
+ export interface ImageOptions {
7
+ maxWidthCells?: number;
8
+ maxHeightCells?: number;
9
+ filename?: string;
10
+ /** Shared budget that caps how many inline images render as live graphics. */
11
+ budget?: ImageBudget;
12
+ /**
13
+ * Stable identity for the underlying image (e.g. `toolCallId:index`). Lets the
14
+ * budget hand back the same graphics id across component re-creations so a
15
+ * repaint replaces the placement instead of stacking a duplicate.
16
+ */
17
+ imageKey?: string;
18
+ }
19
+ /** Default count of inline images kept as live graphics before older ones fall back to text. */
20
+ export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
21
+ /**
22
+ * Bounds how many inline images render as live terminal graphics at once.
23
+ *
24
+ * Terminal graphics protocols — Kitty especially — keep every transmitted image
25
+ * in a per-terminal store and re-draw placements as content scrolls; text-clear
26
+ * escapes (`CSI 2 J` / `CSI 3 J`) do not remove them. Unbounded, a session that
27
+ * shows many images piles up placements plus store memory and leaves ghosts in
28
+ * scrollback.
29
+ *
30
+ * The budget keeps the most recent `cap` images live and demotes older ones to
31
+ * their text fallback. Demotion needs a full redraw (so off-screen rows are
32
+ * rewritten) plus an explicit graphics purge of the demoted ids — {@link Image}
33
+ * reports display order via {@link observe}, and the TUI drives the purge +
34
+ * redraw on the frame after a new image pushes the count past the cap.
35
+ *
36
+ * `cap <= 0` disables budgeting: every image stays a live graphic.
37
+ */
38
+ export declare class ImageBudget {
39
+ #private;
40
+ constructor(cap?: number, requestRender?: () => void);
41
+ get cap(): number;
42
+ get enabled(): boolean;
43
+ setRequestRender(requestRender: () => void): void;
44
+ setCap(cap: number): void;
45
+ /**
46
+ * Stable graphics id for a logical image. A non-empty `key` maps to the same
47
+ * id across re-creations (so repaints replace the placement); a missing key
48
+ * gets a fresh id every call.
49
+ */
50
+ acquireId(key?: string): number;
51
+ /**
52
+ * Begin a render pass. Called by the renderer before composing the frame.
53
+ * Pass `stable: true` for a partial/throwaway pass that does not walk the
54
+ * whole tree in display order (the resize viewport fast path): {@link observe}
55
+ * then replays the last committed per-id decision instead of one derived from
56
+ * call order, and the pass must NOT be closed with {@link endPass}.
57
+ */
58
+ beginPass(stable?: boolean): void;
59
+ /**
60
+ * Record an image in display order and report whether it must render its text
61
+ * fallback this frame. Called by every {@link Image} during render — including
62
+ * on a cache hit, so the image keeps its display-order slot.
63
+ *
64
+ * During a `stable` pass ({@link beginPass}) the call order and visible subset
65
+ * are not authoritative, so the decision is the committed on-terminal split
66
+ * (`#suppressedIds`) keyed by id — order- and partiality-independent.
67
+ */
68
+ observe(imageId: number): boolean;
69
+ /**
70
+ * End a render pass. Returns true when this frame must purge graphics and
71
+ * fully repaint to apply a stricter budget; read the ids via
72
+ * {@link takePurgeIds}.
73
+ */
74
+ endPass(): boolean;
75
+ /** Image ids to delete from the terminal this frame; clears the pending set. */
76
+ takePurgeIds(): readonly number[];
77
+ /** All image ids believed to be loaded in the terminal store; clears tracking. */
78
+ takeAllTransmittedIds(): readonly number[];
79
+ /** Whether `imageId`'s data still needs to be transmitted to the terminal. */
80
+ shouldTransmit(imageId: number): boolean;
81
+ /**
82
+ * Record a direct-placement image's source pixel geometry so the renderer
83
+ * can clip its placement to the visible slice at write time; cleared when
84
+ * the image is purged from the terminal store.
85
+ */
86
+ registerPlacementGeometry(imageId: number, widthPx: number, heightPx: number): void;
87
+ /**
88
+ * Record this frame's native-scrollback commit target (the frame-row count
89
+ * that is committed once the frame's writes land). Called once per rendered
90
+ * frame — including frames that emit no placements — so an epoch whose rows
91
+ * commit while its line is never rewritten is still flagged before the next
92
+ * re-emission.
93
+ */
94
+ observeCommitWatermark(committedTo: number): void;
95
+ /**
96
+ * End the physical-row coordinate epoch after observing its final commit
97
+ * watermark. Placement ids and latched archive state survive, but attachment
98
+ * rows do not: the next placement emit records them in the new-width frame.
99
+ */
100
+ beginPlacementCoordinateEpoch(): void;
101
+ /**
102
+ * Resolve the placement id and geometry for a direct-placement emit whose
103
+ * topmost attached cell sits at `attachTopFrameRow` — the first frame row
104
+ * the placement covers, i.e. the block's first *visible* row, not its
105
+ * origin (-1 when the writer has no frame-space position: alt-screen,
106
+ * resize, ConPTY-truncated replays). `committedTo` is this frame's commit
107
+ * target in the same frame-row space (-1 when unknown).
108
+ *
109
+ * Invariant: a placement id may be re-used (Kitty replace strips that id's
110
+ * cells everywhere, scrollback included) only while none of the cells it
111
+ * attached have entered native scrollback. The epoch — the `p=` id —
112
+ * advances exactly when the archived flag says otherwise; rewrites with no
113
+ * commit progression keep replacing the same id in place.
114
+ */
115
+ resolvePlacementEmit(imageId: number, attachTopFrameRow: number, committedTo: number): {
116
+ placementId: number;
117
+ widthPx: number;
118
+ heightPx: number;
119
+ } | null;
120
+ /**
121
+ * Restart every placement epoch after a destructive history clear (`CSI 3 J`
122
+ * full paint). The clear destroys all placement cells — scrollback rows are
123
+ * gone and the replay rewrites the viewport — so no archive remains to
124
+ * protect. Reverting to epoch 1 lets the replay's placements replace the
125
+ * terminal's stale registry entries; the returned list names every image
126
+ * and the highest epoch it reached so the caller can delete all of its
127
+ * registry entries explicitly (`d=i` keeps the transmitted data) — an image
128
+ * absent from the replay never re-places, so even its epoch-1 entry must go.
129
+ */
130
+ resetPlacementEpochs(): ReadonlyArray<{
131
+ imageId: number;
132
+ lastEpoch: number;
133
+ }>;
134
+ /**
135
+ * Queue a one-time transmit for `imageId`. No-op if already transmitted, so a
136
+ * repeated call (e.g. a width-change re-render) never re-sends the data.
137
+ */
138
+ enqueueTransmit(imageId: number, sequence: string): void;
139
+ /** Whether a frame has image data queued but not yet written to the terminal. */
140
+ hasPendingTransmits(): boolean;
141
+ /**
142
+ * True when the budget has nothing in flight: no live images observed on
143
+ * the last pass, no queued transmits, no pending purges, and no stricter
144
+ * threshold left to apply. A component-scoped frame may skip the observe
145
+ * pass only then — a partial tree walk would under-count display order.
146
+ */
147
+ get quiescent(): boolean;
148
+ /** Transmit sequences to write before this frame's placements; clears the queue. */
149
+ takeTransmits(): readonly string[];
150
+ /**
151
+ * Drop transmit tracking so every still-live image re-enqueues its data
152
+ * (`a=t`) on the next render. Recovers when the terminal dropped the original
153
+ * transmit — e.g. Ghostty discarding graphics sent during its post-startup
154
+ * window — where a placement-only replay can never bind a Unicode placeholder.
155
+ * Pair with a component invalidate + forced repaint so the data and placement
156
+ * re-emit together; keeps no base64 in budget state (the transmit-once design).
157
+ */
158
+ forgetTransmitted(): void;
159
+ }
160
+ export declare class Image implements Component {
161
+ #private;
162
+ constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
163
+ invalidate(): void;
164
+ render(width: number): readonly string[];
165
+ }
@@ -0,0 +1,25 @@
1
+ import { type Component, type Focusable } from "../tui.js";
2
+ /**
3
+ * Input component - single-line text input with horizontal scrolling
4
+ */
5
+ export declare class Input implements Component, Focusable {
6
+ #private;
7
+ /** Rendered before the editable area; set to "" for chrome-less embedding. */
8
+ prompt: string;
9
+ /** Render the editable value as bullets while retaining the real value internally. */
10
+ mask: boolean;
11
+ onSubmit?: (value: string) => void;
12
+ onEscape?: () => void;
13
+ /** Focusable interface - set by TUI when focus changes */
14
+ focused: boolean;
15
+ getValue(): string;
16
+ setValue(value: string): void;
17
+ setUseTerminalCursor(useTerminalCursor: boolean): void;
18
+ getUseTerminalCursor(): boolean;
19
+ handleInput(data: string): void;
20
+ /** Apply terminal paste semantics to text from non-bracketed paste transports
21
+ * (e.g. kitty's OSC 5522 enhanced clipboard read). Mirrors `Editor.pasteText`. */
22
+ pasteText(text: string): void;
23
+ invalidate(): void;
24
+ render(width: number): readonly string[];
25
+ }
@@ -0,0 +1,25 @@
1
+ import type { TUI } from "../tui.js";
2
+ import { Text } from "./text.js";
3
+ type ColorFn = (str: string) => string;
4
+ /**
5
+ * Styles Loader message fragments without changing their visible text or width.
6
+ * Set `animated` for colorizers whose ANSI output changes over time.
7
+ */
8
+ export type LoaderMessageColorFn = ColorFn & {
9
+ readonly animated?: true;
10
+ };
11
+ /** Animates a spinner and colorized message while asynchronous work is pending. */
12
+ export declare class Loader extends Text {
13
+ #private;
14
+ private spinnerColorFn;
15
+ private messageColorFn;
16
+ private message;
17
+ constructor(ui: TUI, spinnerColorFn: ColorFn, messageColorFn: LoaderMessageColorFn, message?: string, spinnerFrames?: string[]);
18
+ render(width: number): readonly string[];
19
+ start(): void;
20
+ stop(): void;
21
+ /** Lifecycle teardown: stop the animation timer. Idempotent. */
22
+ dispose(): void;
23
+ setMessage(message: string): void;
24
+ }
25
+ export {};