@linxiraos/pi-tui 1.0.4 → 1.0.6
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 +18 -5
- 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 +166 -0
- package/dist/types/components/image.d.ts +165 -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 +92 -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 +28 -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 +49 -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 +324 -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 +480 -0
- package/dist/types/utils.d.ts +112 -0
- package/package.json +70 -69
- package/src/keys.ts +1 -1
- package/src/loop-watchdog.ts +32 -6
- package/src/terminal.ts +15 -8
- package/src/tui.ts +70 -16
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [1.0.
|
|
5
|
+
## [1.0.6] - 2026-08-17
|
|
6
|
+
|
|
7
|
+
## [17.3.5] - 2026-08-16
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Fixed long CPU-bound event-loop stalls being misclassified as system sleep and omitted from loop-blocked diagnostics.
|
|
12
|
+
- Fixed focused components with markers falling back to full-screen redraws instead of direct row updates, preserving cursor position and native scrollback across marker changes.
|
|
13
|
+
|
|
14
|
+
## [17.3.4] - 2026-08-14
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed a terminal Device-Attributes reply leaking into the composer as literal text (e.g. `1;22;…;52c`) when it arrived after the startup capability-probe sentinel FIFO drained, a race made observable by the added latency of an SSH/zmx PTY chain. DA1 replies (`CSI ? … c`) and split private-CSI responses are now consumed for the whole session lifetime, not only while a probe sentinel is outstanding ([#8542](https://github.com/can1357/oh-my-pi/issues/8542)).
|
|
6
19
|
|
|
7
20
|
## [17.3.3] - 2026-08-14
|
|
8
21
|
|
|
@@ -1969,7 +1982,7 @@
|
|
|
1969
1982
|
|
|
1970
1983
|
### Changed
|
|
1971
1984
|
|
|
1972
|
-
- Forked to @
|
|
1985
|
+
- Forked to @linxiraos scope with unified versioning across all packages
|
|
1973
1986
|
|
|
1974
1987
|
### Fixed
|
|
1975
1988
|
|
|
@@ -1979,7 +1992,7 @@
|
|
|
1979
1992
|
|
|
1980
1993
|
## [1.337.0] - 2026-01-02
|
|
1981
1994
|
|
|
1982
|
-
Initial release under @
|
|
1995
|
+
Initial release under @linxiraos scope. See previous releases at [badlogic/pi-mono](https://github.com/badlogic/pi-mono).
|
|
1983
1996
|
|
|
1984
1997
|
## [1.5.0] - 2026-01-03
|
|
1985
1998
|
|
|
@@ -1989,13 +2002,13 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
|
|
1989
2002
|
|
|
1990
2003
|
## [1.0.1] - 2026-08-14
|
|
1991
2004
|
|
|
1992
|
-
- Reset the version to 1.0.0 and republished under the `@linxiraos/*` scope, breaking from the `@
|
|
2005
|
+
- Reset the version to 1.0.0 and republished under the `@linxiraos/*` scope, breaking from the `@linxiraos` version lineage.
|
|
1993
2006
|
|
|
1994
2007
|
## [1.0.0] - 2026-08-13
|
|
1995
2008
|
|
|
1996
2009
|
### Changed
|
|
1997
2010
|
|
|
1998
|
-
- Reset the version to 1.0.0 and republished under the `@linxiraos/*` scope, breaking from the `@
|
|
2011
|
+
- Reset the version to 1.0.0 and republished under the `@linxiraos/*` scope, breaking from the `@linxiraos` version lineage.
|
|
1999
2012
|
|
|
2000
2013
|
## [0.50.0] - 2026-01-26
|
|
2001
2014
|
|
|
@@ -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 {};
|