@linxiraos/pi-tui 1.0.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 +2219 -0
- package/README.md +705 -0
- package/dist/types/autocomplete.d.ts +116 -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 +162 -0
- package/dist/types/components/image.d.ts +112 -0
- package/dist/types/components/input.d.ts +25 -0
- package/dist/types/components/loader.d.ts +25 -0
- package/dist/types/components/markdown.d.ts +88 -0
- package/dist/types/components/scroll-view.d.ts +62 -0
- package/dist/types/components/select-list.d.ts +69 -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 +27 -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 +52 -0
- package/dist/types/editor-component.d.ts +38 -0
- package/dist/types/fuzzy.d.ts +48 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/keybindings.d.ts +197 -0
- package/dist/types/keys.d.ts +210 -0
- package/dist/types/kill-ring.d.ts +20 -0
- package/dist/types/kitty-graphics.d.ts +76 -0
- package/dist/types/latex-block.d.ts +8 -0
- package/dist/types/latex-to-unicode.d.ts +50 -0
- package/dist/types/loop-watchdog.d.ts +44 -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 +285 -0
- package/dist/types/terminal.d.ts +175 -0
- package/dist/types/tmux.d.ts +6 -0
- package/dist/types/ttyid.d.ts +9 -0
- package/dist/types/tui.d.ts +457 -0
- package/dist/types/utils.d.ts +100 -0
- package/package.json +70 -0
- package/src/autocomplete.ts +1079 -0
- package/src/bracketed-paste.ts +123 -0
- package/src/components/box.ts +236 -0
- package/src/components/cancellable-loader.ts +40 -0
- package/src/components/editor.ts +3301 -0
- package/src/components/image.ts +460 -0
- package/src/components/input.ts +482 -0
- package/src/components/loader.ts +174 -0
- package/src/components/markdown.ts +3119 -0
- package/src/components/scroll-view.ts +227 -0
- package/src/components/select-list.ts +539 -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 +173 -0
- package/src/components/truncated-text.ts +69 -0
- package/src/deccara.ts +314 -0
- package/src/desktop-notify.ts +192 -0
- package/src/editor-component.ts +74 -0
- package/src/fuzzy.ts +384 -0
- package/src/index.ts +51 -0
- package/src/keybindings.ts +346 -0
- package/src/keys.ts +566 -0
- package/src/kill-ring.ts +51 -0
- package/src/kitty-graphics.ts +171 -0
- package/src/latex-block.ts +1338 -0
- package/src/latex-to-unicode.ts +2017 -0
- package/src/loop-watchdog.ts +115 -0
- package/src/mouse.ts +105 -0
- package/src/stdin-buffer.ts +781 -0
- package/src/symbols.ts +26 -0
- package/src/terminal-capabilities.ts +1211 -0
- package/src/terminal.ts +1854 -0
- package/src/tmux.ts +14 -0
- package/src/ttyid.ts +84 -0
- package/src/tui.ts +4275 -0
- package/src/utils.ts +619 -0
|
@@ -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,162 @@
|
|
|
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
|
+
}
|
|
19
|
+
interface HistoryEntry {
|
|
20
|
+
prompt: string;
|
|
21
|
+
}
|
|
22
|
+
interface HistoryStorage {
|
|
23
|
+
add(prompt: string, cwd?: string): Promise<void>;
|
|
24
|
+
getRecent(limit: number): HistoryEntry[];
|
|
25
|
+
}
|
|
26
|
+
export declare class Editor implements Component, Focusable {
|
|
27
|
+
#private;
|
|
28
|
+
/** Focusable interface - set by TUI when focus changes */
|
|
29
|
+
focused: boolean;
|
|
30
|
+
/** When set, replaces the normal cursor glyph at end-of-text with this ANSI-styled string. */
|
|
31
|
+
cursorOverride: string | undefined;
|
|
32
|
+
/** Display width of the cursorOverride glyph (needed because override may contain ANSI escapes). */
|
|
33
|
+
cursorOverrideWidth: number | undefined;
|
|
34
|
+
/** Optional hook that decorates displayed user text after source-text layout.
|
|
35
|
+
* Width-changing output is allowed on lines without the cursor; it is truncated
|
|
36
|
+
* to the content width rather than reflowed. Cursor glyphs and inline hints are excluded. */
|
|
37
|
+
decorateText: ((text: string) => string) | undefined;
|
|
38
|
+
borderColor: (str: string) => string;
|
|
39
|
+
onAutocompleteUpdate?: () => void;
|
|
40
|
+
/** Optional pattern matching atomic placeholder tokens (e.g. `[Image #1, 800x600]` or
|
|
41
|
+
* `[Paste #2, +30 lines]`) that the editor treats as indivisible: a backspace or forward-delete
|
|
42
|
+
* landing on any character of a token removes the whole token instead of corrupting it into
|
|
43
|
+
* stray text. MUST be a global regex; the editor recompiles a private copy so its `lastIndex`
|
|
44
|
+
* is never shared with the caller. */
|
|
45
|
+
atomicTokenPattern: RegExp | undefined;
|
|
46
|
+
onSubmit?: (text: string) => void | Promise<void>;
|
|
47
|
+
onAltEnter?: (text: string) => void;
|
|
48
|
+
onChange?: (text: string) => void;
|
|
49
|
+
/** Called for a "marker-sized" paste — the point where the editor would otherwise collapse it
|
|
50
|
+
* into a `[Paste #N]` token (> 10 lines or > 1000 characters). Return `true` to intercept:
|
|
51
|
+
* the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
|
|
52
|
+
* "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
|
|
53
|
+
* via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
|
|
54
|
+
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count. */
|
|
55
|
+
onLargePaste?: (text: string, lineCount: number) => boolean;
|
|
56
|
+
onAutocompleteCancel?: () => void;
|
|
57
|
+
disableSubmit: boolean;
|
|
58
|
+
constructor(theme: EditorTheme);
|
|
59
|
+
setAutocompleteProvider(provider: AutocompleteProvider): void;
|
|
60
|
+
/**
|
|
61
|
+
* Set custom content for the top border (e.g., status line).
|
|
62
|
+
* Pass undefined to use the default plain border.
|
|
63
|
+
*
|
|
64
|
+
* Eager: the passed value is cached and reused every frame. Callers that
|
|
65
|
+
* mutate status upstream must recompute and call this again. Prefer
|
|
66
|
+
* {@link setTopBorderProvider} for high-frequency updates — it collapses
|
|
67
|
+
* per-event rebuilds to one per painted frame.
|
|
68
|
+
*/
|
|
69
|
+
setTopBorder(content: EditorTopBorder | undefined): void;
|
|
70
|
+
/**
|
|
71
|
+
* Install a lazy provider invoked once per editor render with the current
|
|
72
|
+
* `availableWidth`. Overrides any eager content set via {@link setTopBorder}
|
|
73
|
+
* — pass `undefined` to detach and fall back to the eager slot.
|
|
74
|
+
*
|
|
75
|
+
* Use this when the top border derives from state that mutates far faster
|
|
76
|
+
* than the render cadence (session events, streaming, subagent updates).
|
|
77
|
+
* The TUI already throttles renders, so a provider is invoked at most once
|
|
78
|
+
* per frame and never does wasted work between paints.
|
|
79
|
+
*/
|
|
80
|
+
setTopBorderProvider(provider: ((availableWidth: number) => EditorTopBorder | undefined) | undefined): void;
|
|
81
|
+
/**
|
|
82
|
+
* Show or hide the editor border chrome.
|
|
83
|
+
*/
|
|
84
|
+
setBorderVisible(borderVisible: boolean): void;
|
|
85
|
+
setPromptGutter(promptGutter: string | undefined): void;
|
|
86
|
+
/**
|
|
87
|
+
* Get the available width for top border content given a total terminal width.
|
|
88
|
+
* Accounts for the border characters and horizontal padding when visible.
|
|
89
|
+
*/
|
|
90
|
+
getTopBorderAvailableWidth(terminalWidth: number): number;
|
|
91
|
+
/**
|
|
92
|
+
* Use the real terminal cursor instead of rendering a cursor glyph.
|
|
93
|
+
*/
|
|
94
|
+
setUseTerminalCursor(useTerminalCursor: boolean): void;
|
|
95
|
+
/** Render a dedicated bottom border so terminal-local IME preedit cannot shift editor chrome. */
|
|
96
|
+
setImeSafeCursorLayout(enabled: boolean): void;
|
|
97
|
+
getUseTerminalCursor(): boolean;
|
|
98
|
+
setMaxHeight(maxHeight: number | undefined): void;
|
|
99
|
+
/** Enable/disable the right-border scrollbar. Only shown when content overflows. */
|
|
100
|
+
setScrollbarVisible(visible: boolean): void;
|
|
101
|
+
setPaddingX(paddingX: number): void;
|
|
102
|
+
getAutocompleteMaxVisible(): number;
|
|
103
|
+
setAutocompleteMaxVisible(maxVisible: number): void;
|
|
104
|
+
setHistoryStorage(storage: HistoryStorage): void;
|
|
105
|
+
/**
|
|
106
|
+
* Add a prompt to history for up/down arrow navigation.
|
|
107
|
+
* Called after successful submission.
|
|
108
|
+
*/
|
|
109
|
+
addToHistory(text: string): void;
|
|
110
|
+
invalidate(): void;
|
|
111
|
+
render(width: number): readonly string[];
|
|
112
|
+
handleInput(data: string): void;
|
|
113
|
+
getText(): string;
|
|
114
|
+
/** Whether the buffer text equals `value`, without `getText()`'s full join —
|
|
115
|
+
* O(1) for the hot per-keystroke probes against short single-line values. */
|
|
116
|
+
textEquals(value: string): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Get text with paste markers expanded to their actual content.
|
|
119
|
+
* Use this when you need the full content (e.g., for external editor).
|
|
120
|
+
*/
|
|
121
|
+
getExpandedText(): string;
|
|
122
|
+
getLines(): string[];
|
|
123
|
+
getCursor(): {
|
|
124
|
+
line: number;
|
|
125
|
+
col: number;
|
|
126
|
+
};
|
|
127
|
+
moveToLineStart(): void;
|
|
128
|
+
moveToLineEnd(): void;
|
|
129
|
+
moveToMessageStart(): void;
|
|
130
|
+
moveToMessageEnd(): void;
|
|
131
|
+
/**
|
|
132
|
+
* Undo the last meaningful edit while ignoring transient text that is still present at the cursor.
|
|
133
|
+
* Used for command-like autocomplete actions whose typed trigger should not count as the edit being undone.
|
|
134
|
+
*/
|
|
135
|
+
undoPastTransientText(transientText: string): void;
|
|
136
|
+
setText(text: string): void;
|
|
137
|
+
submit(): void;
|
|
138
|
+
/** Insert text at the current cursor position */
|
|
139
|
+
insertText(text: string): void;
|
|
140
|
+
/** Delete up to `count` characters immediately before the cursor on the current line.
|
|
141
|
+
* Used to "track back" the auto-repeat spaces that the space-hold push-to-talk gesture
|
|
142
|
+
* optimistically inserts before it recognizes the hold. Capped at the cursor column so it
|
|
143
|
+
* never crosses a line boundary or under-runs the line. */
|
|
144
|
+
deleteBeforeCursor(count: number): void;
|
|
145
|
+
/** Show or replace a volatile speech-to-text preview at the cursor. The text is
|
|
146
|
+
* inserted with undo suspended so a long live dictation never floods the undo
|
|
147
|
+
* stack; finalize it with {@link commitVolatileText} or drop it with
|
|
148
|
+
* {@link clearVolatileText}. Newlines are allowed. */
|
|
149
|
+
setVolatileText(text: string): void;
|
|
150
|
+
/** Remove the current volatile preview without committing it. */
|
|
151
|
+
clearVolatileText(): void;
|
|
152
|
+
/** Drop any volatile preview, then insert `text` as a single undoable edit. */
|
|
153
|
+
commitVolatileText(text: string): void;
|
|
154
|
+
/** Apply terminal paste semantics to text from non-bracketed paste transports. */
|
|
155
|
+
pasteText(text: string): void;
|
|
156
|
+
/** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
|
|
157
|
+
* {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
|
|
158
|
+
* this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
|
|
159
|
+
insertPaste(content: string): void;
|
|
160
|
+
isShowingAutocomplete(): boolean;
|
|
161
|
+
}
|
|
162
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
* Queue a one-time transmit for `imageId`. No-op if already transmitted, so a
|
|
83
|
+
* repeated call (e.g. a width-change re-render) never re-sends the data.
|
|
84
|
+
*/
|
|
85
|
+
enqueueTransmit(imageId: number, sequence: string): void;
|
|
86
|
+
/** Whether a frame has image data queued but not yet written to the terminal. */
|
|
87
|
+
hasPendingTransmits(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* True when the budget has nothing in flight: no live images observed on
|
|
90
|
+
* the last pass, no queued transmits, no pending purges, and no stricter
|
|
91
|
+
* threshold left to apply. A component-scoped frame may skip the observe
|
|
92
|
+
* pass only then — a partial tree walk would under-count display order.
|
|
93
|
+
*/
|
|
94
|
+
get quiescent(): boolean;
|
|
95
|
+
/** Transmit sequences to write before this frame's placements; clears the queue. */
|
|
96
|
+
takeTransmits(): readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* Drop transmit tracking so every still-live image re-enqueues its data
|
|
99
|
+
* (`a=t`) on the next render. Recovers when the terminal dropped the original
|
|
100
|
+
* transmit — e.g. Ghostty discarding graphics sent during its post-startup
|
|
101
|
+
* window — where a placement-only replay can never bind a Unicode placeholder.
|
|
102
|
+
* Pair with a component invalidate + forced repaint so the data and placement
|
|
103
|
+
* re-emit together; keeps no base64 in budget state (the transmit-once design).
|
|
104
|
+
*/
|
|
105
|
+
forgetTransmitted(): void;
|
|
106
|
+
}
|
|
107
|
+
export declare class Image implements Component {
|
|
108
|
+
#private;
|
|
109
|
+
constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
|
|
110
|
+
invalidate(): void;
|
|
111
|
+
render(width: number): readonly string[];
|
|
112
|
+
}
|
|
@@ -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 {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { SymbolTheme } from "../symbols.js";
|
|
2
|
+
import type { Component, NativeScrollbackCommittedRows, NativeScrollbackReplay } from "../tui.js";
|
|
3
|
+
/** @internal exported for tests — must stay index-identical to the old regex scan. */
|
|
4
|
+
export declare function mathStartIndex(src: string): number | undefined;
|
|
5
|
+
/** @internal exported for tests — must stay index-identical to the old regex scan. */
|
|
6
|
+
export declare function autolinkSchemeScanIndex(src: string): number | undefined;
|
|
7
|
+
/** @internal exported for tests — must never return false for a src the built-in url regex matches. */
|
|
8
|
+
export declare function urlTokenPossible(src: string): boolean;
|
|
9
|
+
/** Drop all L2 cache entries. Call on theme change to prevent stale styled output. */
|
|
10
|
+
export declare function clearRenderCache(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Default text styling for markdown content.
|
|
13
|
+
* Applied to all text unless overridden by markdown formatting.
|
|
14
|
+
*/
|
|
15
|
+
export interface DefaultTextStyle {
|
|
16
|
+
/** Foreground color function */
|
|
17
|
+
color?: (text: string) => string;
|
|
18
|
+
/** Background color function */
|
|
19
|
+
bgColor?: (text: string) => string;
|
|
20
|
+
/** Bold text */
|
|
21
|
+
bold?: boolean;
|
|
22
|
+
/** Italic text */
|
|
23
|
+
italic?: boolean;
|
|
24
|
+
/** Strikethrough text */
|
|
25
|
+
strikethrough?: boolean;
|
|
26
|
+
/** Underline text */
|
|
27
|
+
underline?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Theme functions for markdown elements.
|
|
31
|
+
* Each function takes text and returns styled text with ANSI codes.
|
|
32
|
+
*/
|
|
33
|
+
export interface MarkdownTheme {
|
|
34
|
+
heading: (text: string) => string;
|
|
35
|
+
link: (text: string) => string;
|
|
36
|
+
linkUrl: (text: string) => string;
|
|
37
|
+
code: (text: string) => string;
|
|
38
|
+
codeBlock: (text: string) => string;
|
|
39
|
+
codeBlockBorder: (text: string) => string;
|
|
40
|
+
quote: (text: string) => string;
|
|
41
|
+
quoteBorder: (text: string) => string;
|
|
42
|
+
hr: (text: string) => string;
|
|
43
|
+
listBullet: (text: string) => string;
|
|
44
|
+
bold: (text: string) => string;
|
|
45
|
+
italic: (text: string) => string;
|
|
46
|
+
strikethrough: (text: string) => string;
|
|
47
|
+
underline: (text: string) => string;
|
|
48
|
+
highlightCode?: (code: string, lang?: string) => string[];
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a mermaid ASCII rendering by fenced block source text.
|
|
51
|
+
* Return null to fall back to fenced code rendering.
|
|
52
|
+
*/
|
|
53
|
+
resolveMermaidAscii?: (source: string, maxWidth?: number) => string | null;
|
|
54
|
+
symbols: SymbolTheme;
|
|
55
|
+
}
|
|
56
|
+
export declare class Markdown implements Component, NativeScrollbackCommittedRows, NativeScrollbackReplay {
|
|
57
|
+
#private;
|
|
58
|
+
setIgnoreTight(ignore: boolean): this;
|
|
59
|
+
constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
|
|
60
|
+
setText(text: string): boolean;
|
|
61
|
+
invalidate(): void;
|
|
62
|
+
get transientRenderCache(): boolean;
|
|
63
|
+
set transientRenderCache(value: boolean);
|
|
64
|
+
/**
|
|
65
|
+
* Rows at the top of the most recent render() (top padding + rendered
|
|
66
|
+
* frozen-token prefix) whose bytes are settled: byte-stable at this
|
|
67
|
+
* width/theme for as long as the text keeps growing append-only. Hosts
|
|
68
|
+
* feed this to transcript commit gating (see the coding agent's
|
|
69
|
+
* `FinalizableBlock.getTranscriptBlockSettledRows`). 0 outside streaming
|
|
70
|
+
* (`transientRenderCache`) mode, after a text rewind (re-earned on the new
|
|
71
|
+
* lineage), and on cache-served non-streaming renders.
|
|
72
|
+
*/
|
|
73
|
+
getLastRenderSettledRows(): number;
|
|
74
|
+
/**
|
|
75
|
+
* Freeze every table whose first physical row is already part of the native
|
|
76
|
+
* scrollback prefix. The recorded widths came from the exact frame that was
|
|
77
|
+
* just emitted, so the next streamed delta cannot retroactively widen it.
|
|
78
|
+
*/
|
|
79
|
+
setNativeScrollbackCommittedRows(rows: number): void;
|
|
80
|
+
/** A destructive replay removes the immutable tape this layout was guarding. */
|
|
81
|
+
prepareNativeScrollbackReplay(): void;
|
|
82
|
+
render(width: number): readonly string[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Render inline markdown (bold, italic, code, links, strikethrough) to a styled string.
|
|
86
|
+
* Unlike the full Markdown component, this produces a single line with no block-level elements.
|
|
87
|
+
*/
|
|
88
|
+
export declare function renderInlineMarkdown(text: string, mdTheme: MarkdownTheme, baseColor?: (t: string) => string): string;
|