@linxiraos/pi-tui 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/autocomplete.d.ts +127 -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/composer/borderless.d.ts +8 -0
- package/dist/types/components/composer/box.d.ts +2 -0
- package/dist/types/components/composer/claude.d.ts +8 -0
- package/dist/types/components/composer/field.d.ts +3 -0
- package/dist/types/components/composer/index.d.ts +9 -0
- package/dist/types/components/composer/pi.d.ts +2 -0
- package/dist/types/components/composer/rail.d.ts +3 -0
- package/dist/types/components/composer/registry.d.ts +12 -0
- package/dist/types/components/composer/rule.d.ts +7 -0
- package/dist/types/components/composer/types.d.ts +90 -0
- package/dist/types/components/editor.d.ts +213 -0
- package/dist/types/components/image.d.ts +162 -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 +86 -0
- package/dist/types/components/scroll-view.d.ts +62 -0
- package/dist/types/components/select-list.d.ts +78 -0
- package/dist/types/components/settings-list.d.ts +129 -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 +33 -0
- package/dist/types/keybindings.d.ts +202 -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 +338 -0
- package/dist/types/terminal.d.ts +215 -0
- package/dist/types/tmux.d.ts +6 -0
- package/dist/types/ttyid.d.ts +9 -0
- package/dist/types/tui.d.ts +332 -0
- package/dist/types/utils.d.ts +112 -0
- package/package.json +71 -68
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
/** Optional type-indicator glyph rendered in an aligned column before the label */
|
|
14
|
+
icon?: string;
|
|
15
|
+
/** Dim hint text shown inline after cursor when this item is selected */
|
|
16
|
+
hint?: string;
|
|
17
|
+
}
|
|
18
|
+
type Awaitable<T> = T | Promise<T>;
|
|
19
|
+
export interface SlashCommand {
|
|
20
|
+
name: string;
|
|
21
|
+
aliases?: string[];
|
|
22
|
+
description?: string;
|
|
23
|
+
/** Optional type-indicator glyph shown before the command name in autocomplete */
|
|
24
|
+
icon?: string;
|
|
25
|
+
argumentHint?: string;
|
|
26
|
+
/** Whether the command consumes argument text after the command name. False means the full input stays normal prompt text once args are present. */
|
|
27
|
+
allowArgs?: boolean;
|
|
28
|
+
/** Dynamic display-only description for slash-command autocomplete. Must be synchronous and side-effect free. */
|
|
29
|
+
getAutocompleteDescription?: () => string | undefined;
|
|
30
|
+
getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
|
|
31
|
+
/** Return inline hint text for the current argument state (shown as dim ghost text after cursor) */
|
|
32
|
+
getInlineHint?(argumentText: string): string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface AutocompleteProvider {
|
|
35
|
+
/** Get autocomplete suggestions for current text/cursor position. Expensive providers SHOULD stop when `signal` aborts. */
|
|
36
|
+
getSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
|
|
37
|
+
items: AutocompleteItem[];
|
|
38
|
+
prefix: string;
|
|
39
|
+
} | null>;
|
|
40
|
+
/** Apply the selected item and return new text + cursor position */
|
|
41
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
|
|
42
|
+
lines: string[];
|
|
43
|
+
cursorLine: number;
|
|
44
|
+
cursorCol: number;
|
|
45
|
+
onApplied?: () => void;
|
|
46
|
+
};
|
|
47
|
+
/** Get inline hint text to show as dim ghost text after the cursor */
|
|
48
|
+
getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
|
|
49
|
+
/** Synchronously try to complete a slash command at the start of a line (no async I/O). */
|
|
50
|
+
/** Returns matched items and the full prefix, or null if not applicable. */
|
|
51
|
+
trySyncSlashCompletion?(textBeforeCursor: string): {
|
|
52
|
+
items: AutocompleteItem[];
|
|
53
|
+
prefix: string;
|
|
54
|
+
} | null;
|
|
55
|
+
/**
|
|
56
|
+
* Synchronously try to expand text immediately before the cursor (no async I/O).
|
|
57
|
+
* Called after every single-character insert. Implementations MUST cheaply
|
|
58
|
+
* early-return when the trailing context cannot trigger them.
|
|
59
|
+
* Returns the number of characters to delete immediately before the cursor
|
|
60
|
+
* and the literal string to insert in their place, or null to leave the
|
|
61
|
+
* buffer untouched.
|
|
62
|
+
*/
|
|
63
|
+
trySyncInlineReplace?(textBeforeCursor: string): {
|
|
64
|
+
replaceLen: number;
|
|
65
|
+
insert: string;
|
|
66
|
+
} | null;
|
|
67
|
+
/**
|
|
68
|
+
* Force file-path completion (called on Tab). Returns matched items plus the
|
|
69
|
+
* full prefix, or null when no path token sits before the cursor. Present on
|
|
70
|
+
* file-aware providers; absent on slash-only ones.
|
|
71
|
+
* Expensive providers SHOULD stop when `signal` aborts.
|
|
72
|
+
*/
|
|
73
|
+
getForceFileSuggestions?(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
|
|
74
|
+
items: AutocompleteItem[];
|
|
75
|
+
prefix: string;
|
|
76
|
+
} | null>;
|
|
77
|
+
/** Whether a Tab press should attempt file completion at the cursor. */
|
|
78
|
+
shouldTriggerFileCompletion?(lines: string[], cursorLine: number, cursorCol: number): boolean;
|
|
79
|
+
}
|
|
80
|
+
type CommandEntry = SlashCommand | AutocompleteItem;
|
|
81
|
+
/** Optional behaviors for {@link CombinedAutocompleteProvider}. */
|
|
82
|
+
export interface CombinedAutocompleteOptions {
|
|
83
|
+
/** Usage count per command name; higher counts rank earlier among equal text-match scores. */
|
|
84
|
+
commandUsage?: (name: string) => number;
|
|
85
|
+
}
|
|
86
|
+
export declare function scoreCommandTextMatch(lowerPrefix: string, lowerTarget: string): number;
|
|
87
|
+
export declare const SKILL_NAMESPACE = "skill:";
|
|
88
|
+
/**
|
|
89
|
+
* Whether a mid-prompt slash token (`prose … /tok`) is skill-shaped enough to
|
|
90
|
+
* surface `name` in the skill popup. Deliberately stricter than submitted
|
|
91
|
+
* slash-command matching: a stray `/word` in running prose must not keep the
|
|
92
|
+
* popup alive through fuzzy name/description hits, so a token only matches as
|
|
93
|
+
* - a prefix of the `skill:` namespace (incl. the bare `/` entry point),
|
|
94
|
+
* - an explicit `skill:…` query (full fuzzy name/description search), or
|
|
95
|
+
* - a prefix of the skill's bare name (`/hum` → `skill:humanizer`).
|
|
96
|
+
* Anything else yields no items, letting the caller fall through to path
|
|
97
|
+
* completion or close the popup. Shared with the editor's accept-time
|
|
98
|
+
* staleness guard so Tab/Enter never accepts a skill the refreshed popup
|
|
99
|
+
* would no longer show.
|
|
100
|
+
*/
|
|
101
|
+
export declare function midPromptSkillTokenMatches(lowerToken: string, name: string, description?: string): boolean;
|
|
102
|
+
export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
103
|
+
#private;
|
|
104
|
+
constructor(commands?: CommandEntry[], basePath?: string, options?: CombinedAutocompleteOptions);
|
|
105
|
+
getSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
|
|
106
|
+
items: AutocompleteItem[];
|
|
107
|
+
prefix: string;
|
|
108
|
+
} | null>;
|
|
109
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
|
|
110
|
+
lines: string[];
|
|
111
|
+
cursorLine: number;
|
|
112
|
+
cursorCol: number;
|
|
113
|
+
};
|
|
114
|
+
invalidateDirCache(dir?: string): void;
|
|
115
|
+
getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
|
|
116
|
+
items: AutocompleteItem[];
|
|
117
|
+
prefix: string;
|
|
118
|
+
} | null>;
|
|
119
|
+
shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
|
|
120
|
+
/** Get inline hint text for slash commands with subcommand hints */
|
|
121
|
+
getInlineHint(lines: string[], cursorLine: number, cursorCol: number): string | null;
|
|
122
|
+
trySyncSlashCompletion(textBeforeCursor: string): {
|
|
123
|
+
items: AutocompleteItem[];
|
|
124
|
+
prefix: string;
|
|
125
|
+
} | null;
|
|
126
|
+
}
|
|
127
|
+
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,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome-free composer: a bare `❯ ` prompt with no rules or borders. Also the
|
|
3
|
+
* effective style whenever a host calls `setBorderVisible(false)` (hook
|
|
4
|
+
* editors, agents hub). The status bar renders as a plain standalone bottom
|
|
5
|
+
* bar with both segment groups.
|
|
6
|
+
*/
|
|
7
|
+
import type { ComposerStyle } from "./types.js";
|
|
8
|
+
export declare const borderlessComposerStyle: ComposerStyle;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code-like composer: full-width horizontal rules above and below a
|
|
3
|
+
* borderless `❯ ` prompt. The right status group rides the top rule as a
|
|
4
|
+
* bg chip near the right edge (`─────── hi ─`); the left group renders as a
|
|
5
|
+
* plain standalone bottom bar that yields its row to the autocomplete menu.
|
|
6
|
+
*/
|
|
7
|
+
import type { ComposerStyle } from "./types.js";
|
|
8
|
+
export declare const claudeComposerStyle: ComposerStyle;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./borderless.js";
|
|
2
|
+
export * from "./box.js";
|
|
3
|
+
export * from "./claude.js";
|
|
4
|
+
export * from "./field.js";
|
|
5
|
+
export * from "./pi.js";
|
|
6
|
+
export * from "./rail.js";
|
|
7
|
+
export * from "./registry.js";
|
|
8
|
+
export * from "./rule.js";
|
|
9
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComposerStyle, EditorBorderStyle } from "./types.js";
|
|
2
|
+
/** Whether an id names a composer style shipped by pi-tui. */
|
|
3
|
+
export declare function isBuiltinComposerStyle(id: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Register one extension-owned composer style for this process.
|
|
6
|
+
*
|
|
7
|
+
* Built-in ids and duplicate extension ids are rejected. The returned disposer
|
|
8
|
+
* removes only this registration.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerComposerStyle(style: ComposerStyle): () => void;
|
|
11
|
+
/** Style object for a composer shape; unknown ids fall back to `box`. */
|
|
12
|
+
export declare function getComposerStyle(id: EditorBorderStyle): ComposerStyle;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComposerChromeContext, ComposerStyle } from "./types.js";
|
|
2
|
+
/** Draw a full-width rule with status content docked at its right edge.
|
|
3
|
+
* Over-wide content is truncated (keeping one rule cell on each side) rather
|
|
4
|
+
* than dropped, so the chip survives narrow terminals and previews. */
|
|
5
|
+
export declare function renderTopRule(ctx: ComposerChromeContext): string;
|
|
6
|
+
/** Composer style with one status-bearing top rule and no bottom chrome. */
|
|
7
|
+
export declare const ruleComposerStyle: ComposerStyle;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer chrome contract. A {@link ComposerStyle} owns everything about how
|
|
3
|
+
* the input editor's frame looks — top/bottom chrome, per-row side chrome,
|
|
4
|
+
* default padding and prompt gutter — plus metadata telling the host where the
|
|
5
|
+
* status bar attaches. The editor, the /settings preview, and the setup-wizard
|
|
6
|
+
* preview all render through the same style object, so the three surfaces can
|
|
7
|
+
* never drift apart.
|
|
8
|
+
*/
|
|
9
|
+
import type { SymbolTheme } from "../../symbols.js";
|
|
10
|
+
/** Box-drawing glyph set used for composer chrome (the theme's `boxRound`). */
|
|
11
|
+
export type ComposerBox = SymbolTheme["boxRound"];
|
|
12
|
+
/** Built-in composer shape identifiers shipped by pi-tui. */
|
|
13
|
+
export declare const BUILTIN_EDITOR_BORDER_STYLES: readonly ["box", "claude", "pi", "borderless", "rule", "field", "rail"];
|
|
14
|
+
/** Identifier for a built-in composer shape. */
|
|
15
|
+
export type BuiltinEditorBorderStyle = (typeof BUILTIN_EDITOR_BORDER_STYLES)[number];
|
|
16
|
+
/** Composer shape identifier; extensions may register additional strings. */
|
|
17
|
+
export type EditorBorderStyle = string;
|
|
18
|
+
/** Pre-rendered status content injected into the top chrome. */
|
|
19
|
+
export interface EditorTopBorder {
|
|
20
|
+
/** The status content (already styled) */
|
|
21
|
+
content: string;
|
|
22
|
+
/** Visible width of the content */
|
|
23
|
+
width: number;
|
|
24
|
+
/** Optional logical revision that changes independently of available width. */
|
|
25
|
+
revision?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Inputs shared by every chrome row. */
|
|
28
|
+
export interface ComposerChromeContext {
|
|
29
|
+
/** Full terminal width available to the composer. */
|
|
30
|
+
width: number;
|
|
31
|
+
/** Horizontal padding inside the side chrome. */
|
|
32
|
+
paddingX: number;
|
|
33
|
+
borderColor: (str: string) => string;
|
|
34
|
+
/** Stable accent used by shape-defining chrome such as field caps and rails. */
|
|
35
|
+
accentColor: (str: string) => string;
|
|
36
|
+
/** Background fill for composer surfaces; preserves the fill across nested SGR resets. */
|
|
37
|
+
surfaceColor: (str: string) => string;
|
|
38
|
+
/** Box-drawing glyph set (theme's `boxRound`). */
|
|
39
|
+
box: ComposerBox;
|
|
40
|
+
/** Status content for the top chrome; box embeds it after the corner while
|
|
41
|
+
* rule-based styles dock it against the right edge. */
|
|
42
|
+
topBorder?: EditorTopBorder;
|
|
43
|
+
}
|
|
44
|
+
/** Inputs for one content row. */
|
|
45
|
+
export interface ComposerRowContext extends ComposerChromeContext {
|
|
46
|
+
/** Fully decorated row text (cursor glyph / IME marker included). */
|
|
47
|
+
text: string;
|
|
48
|
+
/** Spaces padding the text out to the content width. */
|
|
49
|
+
pad: string;
|
|
50
|
+
/** Prompt gutter cells for this row ("" when the style has none). */
|
|
51
|
+
gutter: string;
|
|
52
|
+
isLastRow: boolean;
|
|
53
|
+
/** Cells the end-of-line cursor overflowed into the right chrome (box). */
|
|
54
|
+
cursorOverflow: number;
|
|
55
|
+
/** Emit an empty right chrome after the cursor so terminal-local IME
|
|
56
|
+
* preedit cannot shift the frame (box last row). */
|
|
57
|
+
imeSafeCursorTail: boolean;
|
|
58
|
+
/** Row lies inside the right-border scrollbar thumb (box). */
|
|
59
|
+
scrollbarThumb: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface ComposerStyle {
|
|
62
|
+
readonly id: EditorBorderStyle;
|
|
63
|
+
/** Content rows carry left/right border glyphs; drives the cursor-reserve
|
|
64
|
+
* column, IME-safe layout, and the right-border scrollbar. */
|
|
65
|
+
readonly sideBorders: boolean;
|
|
66
|
+
/** Rows consumed by top+bottom chrome (drives maxHeight budgeting). */
|
|
67
|
+
readonly verticalChrome: 0 | 1 | 2;
|
|
68
|
+
/** Where the host should attach the status bar: embedded in the top border,
|
|
69
|
+
* docked onto a top rule, or detached into a standalone bottom bar. */
|
|
70
|
+
readonly statusAttachment: "top-border" | "top-rule-chip" | "none";
|
|
71
|
+
/** Which segment groups the standalone bottom status bar shows. */
|
|
72
|
+
readonly bottomBar: "none" | "left" | "full";
|
|
73
|
+
/** Insert a blank spacer row between the editor and the standalone bottom
|
|
74
|
+
* bar. Styles without bottom chrome need it so the bar doesn't sit flush
|
|
75
|
+
* against the last input row. */
|
|
76
|
+
readonly bottomBarGap: boolean;
|
|
77
|
+
/** Default prompt gutter when the host sets none. */
|
|
78
|
+
readonly defaultPromptGutter: string | undefined;
|
|
79
|
+
/** Default horizontal padding; `themePaddingX` is the theme's request. */
|
|
80
|
+
defaultPaddingX(themePaddingX: number | undefined): number;
|
|
81
|
+
/** Cells consumed per side on content rows (border glyph + padding). */
|
|
82
|
+
sideChromeWidth(paddingX: number): number;
|
|
83
|
+
/** Top chrome row; `undefined` renders none. */
|
|
84
|
+
renderTop(ctx: ComposerChromeContext): string | undefined;
|
|
85
|
+
/** Chrome-wrapped content row; box's IME-safe last row emits two rows. */
|
|
86
|
+
renderRow(ctx: ComposerRowContext): string[];
|
|
87
|
+
/** Bottom chrome row; `undefined` renders none (box merges the bottom
|
|
88
|
+
* border into its last content row). */
|
|
89
|
+
renderBottom(ctx: ComposerChromeContext): string | undefined;
|
|
90
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
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 EditorBorderStyle, type EditorTopBorder } from "./composer/index.js";
|
|
5
|
+
export type { EditorBorderStyle, EditorTopBorder };
|
|
6
|
+
import { type SelectListTheme } from "./select-list.js";
|
|
7
|
+
export interface EditorTheme {
|
|
8
|
+
borderColor: (str: string) => string;
|
|
9
|
+
/** Stable accent for composer chrome that should not follow the mutable border state. */
|
|
10
|
+
accentColor?: (str: string) => string;
|
|
11
|
+
/** Background fill used by filled composer styles. */
|
|
12
|
+
surfaceColor?: (str: string) => string;
|
|
13
|
+
selectList: SelectListTheme;
|
|
14
|
+
symbols: SymbolTheme;
|
|
15
|
+
editorPaddingX?: number;
|
|
16
|
+
/** Style function for inline hint/ghost text (dim text after cursor) */
|
|
17
|
+
hintStyle?: (text: string) => string;
|
|
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
|
+
/** A synchronous replacement immediately before the editor cursor. */
|
|
27
|
+
export interface EditorInlineReplacement {
|
|
28
|
+
/** UTF-16 code units to remove immediately before the cursor. */
|
|
29
|
+
replaceLen: number;
|
|
30
|
+
/** Literal text inserted where the removed suffix started. */
|
|
31
|
+
insert: string;
|
|
32
|
+
}
|
|
33
|
+
/** Replacement candidates and the current-line span they replace. */
|
|
34
|
+
export interface EditorWordReplacements {
|
|
35
|
+
line: number;
|
|
36
|
+
startCol: number;
|
|
37
|
+
endCol: number;
|
|
38
|
+
items: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/** Source location for one visual text segment passed to `decorateText`. */
|
|
41
|
+
export interface EditorTextDecorationContext {
|
|
42
|
+
line: number;
|
|
43
|
+
startCol: number;
|
|
44
|
+
endCol: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Optional prose assistance kept separate from command/file autocomplete.
|
|
48
|
+
* Hosts independently decide whether word completion and autocorrection are enabled.
|
|
49
|
+
*/
|
|
50
|
+
export interface EditorTextAssistProvider {
|
|
51
|
+
/** Return ghost-text suffix for the partial word at the cursor, or `null`. */
|
|
52
|
+
getWordCompletion?(lines: string[], cursorLine: number, cursorCol: number): string | null;
|
|
53
|
+
/** Return a correction after one single-character insertion, or `null`. */
|
|
54
|
+
tryAutocorrect?(lines: string[], cursorLine: number, cursorCol: number): EditorInlineReplacement | null | Promise<EditorInlineReplacement | null>;
|
|
55
|
+
/** Return replacement candidates for the misspelled word at the cursor. */
|
|
56
|
+
getWordReplacements?(lines: string[], cursorLine: number, cursorCol: number): EditorWordReplacements | null | Promise<EditorWordReplacements | null>;
|
|
57
|
+
}
|
|
58
|
+
export declare class Editor implements Component, Focusable {
|
|
59
|
+
#private;
|
|
60
|
+
/** Focusable interface - set by TUI when focus changes */
|
|
61
|
+
focused: boolean;
|
|
62
|
+
/** When set, replaces the normal cursor glyph at end-of-text with this ANSI-styled string. */
|
|
63
|
+
cursorOverride: string | undefined;
|
|
64
|
+
/** Display width of the cursorOverride glyph (needed because override may contain ANSI escapes). */
|
|
65
|
+
cursorOverrideWidth: number | undefined;
|
|
66
|
+
/** Optional hook that decorates displayed user text after source-text layout.
|
|
67
|
+
* Width-changing output is allowed on lines without the cursor; it is truncated
|
|
68
|
+
* to the content width rather than reflowed. Cursor glyphs and inline hints are excluded. */
|
|
69
|
+
decorateText: ((text: string, context: EditorTextDecorationContext) => string) | undefined;
|
|
70
|
+
borderColor: (str: string) => string;
|
|
71
|
+
onAutocompleteUpdate?: () => void;
|
|
72
|
+
/** Called after an async text-assist result mutates the document outside an input event, so hosts can schedule a repaint. */
|
|
73
|
+
onTextAssistApplied?: () => void;
|
|
74
|
+
/** Terminal height source for clamping the autocomplete dropdown. Hosts wire this to their Terminal's rows. */
|
|
75
|
+
viewportRowsProvider?: () => number;
|
|
76
|
+
/** Optional pattern matching atomic placeholder tokens (e.g. `[Image #1, 800x600]` or
|
|
77
|
+
* `[Paste #2, +30 lines]`) that the editor treats as indivisible: a backspace or forward-delete
|
|
78
|
+
* landing on any character of a token removes the whole token instead of corrupting it into
|
|
79
|
+
* stray text. MUST be a global regex; the editor recompiles a private copy so its `lastIndex`
|
|
80
|
+
* is never shared with the caller. */
|
|
81
|
+
atomicTokenPattern: RegExp | undefined;
|
|
82
|
+
onSubmit?: (text: string) => void | Promise<void>;
|
|
83
|
+
onAltEnter?: (text: string) => void;
|
|
84
|
+
onChange?: (text: string) => void;
|
|
85
|
+
/** Called for a "marker-sized" paste — the point where the editor would otherwise collapse it
|
|
86
|
+
* into a `[Paste #N]` token (> 10 lines or > 1000 characters). Return `true` to intercept:
|
|
87
|
+
* the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
|
|
88
|
+
* "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
|
|
89
|
+
* via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
|
|
90
|
+
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count. */
|
|
91
|
+
onLargePaste?: (text: string, lineCount: number) => boolean;
|
|
92
|
+
onAutocompleteCancel?: () => void;
|
|
93
|
+
disableSubmit: boolean;
|
|
94
|
+
constructor(theme: EditorTheme);
|
|
95
|
+
setTheme(theme: EditorTheme): void;
|
|
96
|
+
setAutocompleteProvider(provider: AutocompleteProvider): void;
|
|
97
|
+
/** Install prose assistance without changing command/file autocomplete. */
|
|
98
|
+
setTextAssistProvider(provider: EditorTextAssistProvider | undefined): void;
|
|
99
|
+
/**
|
|
100
|
+
* Set custom content for the top border (e.g., status line).
|
|
101
|
+
* Pass undefined to use the default plain border.
|
|
102
|
+
*
|
|
103
|
+
* Eager: the passed value is cached and reused every frame. Callers that
|
|
104
|
+
* mutate status upstream must recompute and call this again. Prefer
|
|
105
|
+
* {@link setTopBorderProvider} for high-frequency updates — it collapses
|
|
106
|
+
* per-event rebuilds to one per painted frame.
|
|
107
|
+
*/
|
|
108
|
+
setTopBorder(content: EditorTopBorder | undefined): void;
|
|
109
|
+
/**
|
|
110
|
+
* Install a lazy provider invoked once per editor render with the current
|
|
111
|
+
* `availableWidth`. Overrides any eager content set via {@link setTopBorder}
|
|
112
|
+
* — pass `undefined` to detach and fall back to the eager slot.
|
|
113
|
+
*
|
|
114
|
+
* Use this when the top border derives from state that mutates far faster
|
|
115
|
+
* than the render cadence (session events, streaming, subagent updates).
|
|
116
|
+
* The TUI already throttles renders, so a provider is invoked exactly once
|
|
117
|
+
* per frame and does no work between paints. */
|
|
118
|
+
setTopBorderProvider(provider: ((availableWidth: number) => EditorTopBorder | undefined) | undefined): void;
|
|
119
|
+
/**
|
|
120
|
+
* Show or hide the editor border chrome.
|
|
121
|
+
*/
|
|
122
|
+
setBorderVisible(borderVisible: boolean): void;
|
|
123
|
+
setPromptGutter(promptGutter: string | undefined): void;
|
|
124
|
+
getBorderStyle(): EditorBorderStyle;
|
|
125
|
+
setBorderStyle(style: EditorBorderStyle): void;
|
|
126
|
+
/** True while the autocomplete/slash-command menu is open below the editor. */
|
|
127
|
+
isAutocompleteActive(): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Get the available width for top border content given a total terminal width.
|
|
130
|
+
* Accounts for the border characters and horizontal padding when visible.
|
|
131
|
+
*/
|
|
132
|
+
getTopBorderAvailableWidth(terminalWidth: number): number;
|
|
133
|
+
/**
|
|
134
|
+
* Use the real terminal cursor instead of rendering a cursor glyph.
|
|
135
|
+
*/
|
|
136
|
+
setUseTerminalCursor(useTerminalCursor: boolean): void;
|
|
137
|
+
/** Render a dedicated bottom border so terminal-local IME preedit cannot shift editor chrome. */
|
|
138
|
+
setImeSafeCursorLayout(enabled: boolean): void;
|
|
139
|
+
getUseTerminalCursor(): boolean;
|
|
140
|
+
setMaxHeight(maxHeight: number | undefined): void;
|
|
141
|
+
/** Enable/disable the right-border scrollbar. Only shown when content overflows. */
|
|
142
|
+
setScrollbarVisible(visible: boolean): void;
|
|
143
|
+
setPaddingX(paddingX: number): void;
|
|
144
|
+
getAutocompleteMaxVisible(): number;
|
|
145
|
+
setAutocompleteMaxVisible(maxVisible: number): void;
|
|
146
|
+
/** Loads persistent prompts for navigation and enables future persistence. */
|
|
147
|
+
setHistoryStorage(storage: HistoryStorage): void;
|
|
148
|
+
/**
|
|
149
|
+
* Add a prompt to history for up/down arrow navigation.
|
|
150
|
+
* Called after successful submission.
|
|
151
|
+
*/
|
|
152
|
+
addToHistory(text: string): void;
|
|
153
|
+
invalidate(): void;
|
|
154
|
+
render(width: number): readonly string[];
|
|
155
|
+
handleInput(data: string): void;
|
|
156
|
+
getText(): string;
|
|
157
|
+
/** Whether the buffer text equals `value`, without `getText()`'s full join —
|
|
158
|
+
* O(1) for the hot per-keystroke probes against short single-line values. */
|
|
159
|
+
textEquals(value: string): boolean;
|
|
160
|
+
/** Register `label` as a collapsed atom expanding to `expansion` on submit, without inserting
|
|
161
|
+
* it — for hosts that re-collapse restored draft text via {@link setText}. */
|
|
162
|
+
registerAtom(label: string, expansion: string): void;
|
|
163
|
+
/** Insert `label` (plus a trailing space) at the cursor and register it as an atom expanding
|
|
164
|
+
* to `expansion` on submit. Pair with {@link atomicTokenPattern} so the label deletes as a
|
|
165
|
+
* unit. */
|
|
166
|
+
insertAtom(label: string, expansion: string): void;
|
|
167
|
+
/** Drop every registered atom expansion (draft cleared or replaced by the host). */
|
|
168
|
+
clearAtoms(): void;
|
|
169
|
+
/**
|
|
170
|
+
* Get text with paste markers expanded to their actual content.
|
|
171
|
+
* Use this when you need the full content (e.g., for external editor).
|
|
172
|
+
*/
|
|
173
|
+
getExpandedText(): string;
|
|
174
|
+
getLines(): string[];
|
|
175
|
+
getCursor(): {
|
|
176
|
+
line: number;
|
|
177
|
+
col: number;
|
|
178
|
+
};
|
|
179
|
+
moveToLineStart(): void;
|
|
180
|
+
moveToLineEnd(): void;
|
|
181
|
+
moveToMessageStart(): void;
|
|
182
|
+
moveToMessageEnd(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Undo the last meaningful edit while ignoring transient text that is still present at the cursor.
|
|
185
|
+
* Used for command-like autocomplete actions whose typed trigger should not count as the edit being undone.
|
|
186
|
+
*/
|
|
187
|
+
undoPastTransientText(transientText: string): void;
|
|
188
|
+
setText(text: string): void;
|
|
189
|
+
submit(): void;
|
|
190
|
+
/** Insert text at the current cursor position */
|
|
191
|
+
insertText(text: string): void;
|
|
192
|
+
/** Delete up to `count` characters immediately before the cursor on the current line.
|
|
193
|
+
* Used to "track back" the auto-repeat spaces that the space-hold push-to-talk gesture
|
|
194
|
+
* optimistically inserts before it recognizes the hold. Capped at the cursor column so it
|
|
195
|
+
* never crosses a line boundary or under-runs the line. */
|
|
196
|
+
deleteBeforeCursor(count: number): void;
|
|
197
|
+
/** Show or replace a volatile speech-to-text preview at the cursor. The text is
|
|
198
|
+
* inserted with undo suspended so a long live dictation never floods the undo
|
|
199
|
+
* stack; finalize it with {@link commitVolatileText} or drop it with
|
|
200
|
+
* {@link clearVolatileText}. Newlines are allowed. */
|
|
201
|
+
setVolatileText(text: string): void;
|
|
202
|
+
/** Remove the current volatile preview without committing it. */
|
|
203
|
+
clearVolatileText(): void;
|
|
204
|
+
/** Drop any volatile preview, then insert `text` as a single undoable edit. */
|
|
205
|
+
commitVolatileText(text: string): void;
|
|
206
|
+
/** Apply terminal paste semantics to text from non-bracketed paste transports. */
|
|
207
|
+
pasteText(text: string): void;
|
|
208
|
+
/** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
|
|
209
|
+
* {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
|
|
210
|
+
* this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
|
|
211
|
+
insertPaste(content: string): void;
|
|
212
|
+
isShowingAutocomplete(): boolean;
|
|
213
|
+
}
|