@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,62 @@
|
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
import { Ellipsis } from "../utils.js";
|
|
3
|
+
type ScrollbarMode = "auto" | "always" | "never";
|
|
4
|
+
export interface ScrollViewTheme {
|
|
5
|
+
track?: (text: string) => string;
|
|
6
|
+
thumb?: (text: string) => string;
|
|
7
|
+
}
|
|
8
|
+
export interface ScrollViewOptions {
|
|
9
|
+
height: number;
|
|
10
|
+
/** Defaults to "auto". "auto" reserves a scrollbar column only when content overflows. */
|
|
11
|
+
scrollbar?: ScrollbarMode | boolean;
|
|
12
|
+
/** Logical row count for pre-windowed line slices. Defaults to lines.length. */
|
|
13
|
+
totalRows?: number;
|
|
14
|
+
theme?: ScrollViewTheme;
|
|
15
|
+
trackChar?: string;
|
|
16
|
+
thumbChar?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Indicator appended when a row overflows `contentWidth`. Defaults to
|
|
19
|
+
* {@link Ellipsis.Unicode}. Pass {@link Ellipsis.Omit} when callers wrap
|
|
20
|
+
* lines to width themselves and only trailing padding can overflow (e.g.
|
|
21
|
+
* the plan-review overlay), so no stray `…` lands on every padded row.
|
|
22
|
+
*/
|
|
23
|
+
ellipsis?: Ellipsis;
|
|
24
|
+
/**
|
|
25
|
+
* Rows moved per keystroke when {@link ScrollView.handleScrollKey} sees a
|
|
26
|
+
* Shift+Arrow (the "scroll faster" affordance). Defaults to 5.
|
|
27
|
+
*/
|
|
28
|
+
fastScrollLines?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fixed-height viewport over pre-rendered lines, with optional right-edge scrollbar.
|
|
32
|
+
*
|
|
33
|
+
* ScrollView owns only the row offset. Callers remain responsible for producing
|
|
34
|
+
* already-wrapped logical lines appropriate for the current render width.
|
|
35
|
+
*/
|
|
36
|
+
export declare class ScrollView implements Component {
|
|
37
|
+
#private;
|
|
38
|
+
constructor(lines: readonly string[], options: ScrollViewOptions);
|
|
39
|
+
setLines(lines: readonly string[]): void;
|
|
40
|
+
setTotalRows(totalRows: number | undefined): void;
|
|
41
|
+
setHeight(height: number): void;
|
|
42
|
+
setScrollbar(scrollbar: ScrollViewOptions["scrollbar"]): void;
|
|
43
|
+
getScrollOffset(): number;
|
|
44
|
+
getMaxScrollOffset(): number;
|
|
45
|
+
setScrollOffset(offset: number): void;
|
|
46
|
+
scroll(delta: number): void;
|
|
47
|
+
page(delta: number): void;
|
|
48
|
+
scrollToTop(): void;
|
|
49
|
+
scrollToBottom(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Apply a standard navigation key to the viewport. Shift+Arrow scrolls by
|
|
52
|
+
* {@link ScrollViewOptions.fastScrollLines} (the "scroll faster" affordance);
|
|
53
|
+
* plain Arrow by one line; PageUp/PageDown by a page; Home/End to the ends.
|
|
54
|
+
* Returns true when the key was consumed, so callers can fall through to
|
|
55
|
+
* their own (e.g. vim-style) bindings. Generic on purpose: every ScrollView
|
|
56
|
+
* consumer gets the same scroll keys, including Shift-to-go-faster.
|
|
57
|
+
*/
|
|
58
|
+
handleScrollKey(data: string): boolean;
|
|
59
|
+
invalidate(): void;
|
|
60
|
+
render(width: number): readonly string[];
|
|
61
|
+
}
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type MouseRoutable, type SgrMouseEvent } from "../mouse.js";
|
|
2
|
+
import type { SymbolTheme } from "../symbols.js";
|
|
3
|
+
import type { Component } from "../tui.js";
|
|
4
|
+
export interface SelectItem {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Dim hint text shown inline after cursor when this item is selected */
|
|
9
|
+
hint?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SelectListTheme {
|
|
12
|
+
selectedPrefix: (text: string) => string;
|
|
13
|
+
selectedText: (text: string) => string;
|
|
14
|
+
description: (text: string) => string;
|
|
15
|
+
scrollInfo: (text: string) => string;
|
|
16
|
+
noMatch: (text: string) => string;
|
|
17
|
+
symbols: SymbolTheme;
|
|
18
|
+
/** Hover band applied to the full row under the mouse pointer. */
|
|
19
|
+
hovered?: (text: string) => string;
|
|
20
|
+
}
|
|
21
|
+
export interface SelectListTruncatePrimaryContext {
|
|
22
|
+
text: string;
|
|
23
|
+
maxWidth: number;
|
|
24
|
+
columnWidth: number;
|
|
25
|
+
item: SelectItem;
|
|
26
|
+
isSelected: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SelectListLayoutOptions {
|
|
29
|
+
minPrimaryColumnWidth?: number;
|
|
30
|
+
maxPrimaryColumnWidth?: number;
|
|
31
|
+
truncatePrimary?: (context: SelectListTruncatePrimaryContext) => string;
|
|
32
|
+
/** Enable type-to-filter search when the item count exceeds maxVisible. Defaults to true. */
|
|
33
|
+
overflowSearch?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Wrap long descriptions onto continuation rows indented under the
|
|
36
|
+
* description column instead of truncating. Defaults to false so existing
|
|
37
|
+
* single-line consumers are unaffected. Navigation remains item-to-item;
|
|
38
|
+
* the scrollbar tracks visual rows so the thumb stays correct when items
|
|
39
|
+
* wrap unevenly.
|
|
40
|
+
*/
|
|
41
|
+
wrapDescription?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare class SelectList implements Component, MouseRoutable {
|
|
44
|
+
#private;
|
|
45
|
+
private readonly items;
|
|
46
|
+
private readonly theme;
|
|
47
|
+
private readonly layout;
|
|
48
|
+
onSelect?: (item: SelectItem) => void;
|
|
49
|
+
onCancel?: () => void;
|
|
50
|
+
onSelectionChange?: (item: SelectItem) => void;
|
|
51
|
+
constructor(items: ReadonlyArray<SelectItem>, maxVisible: number, theme: SelectListTheme, layout?: SelectListLayoutOptions);
|
|
52
|
+
/** Refit the visible row budget (hosts clamp the list to available height). */
|
|
53
|
+
setMaxVisible(rows: number): void;
|
|
54
|
+
setFilter(filter: string): void;
|
|
55
|
+
setSelectedIndex(index: number): void;
|
|
56
|
+
/** Resolve a 0-based rendered-line index to a filtered-item index. */
|
|
57
|
+
hitTest(line: number): number | undefined;
|
|
58
|
+
/** Highlight the item under the pointer (null clears). */
|
|
59
|
+
setHoverIndex(index: number | null): void;
|
|
60
|
+
/** Move the selection one step for a wheel notch. */
|
|
61
|
+
handleWheel(delta: -1 | 1): void;
|
|
62
|
+
/** Mouse click: select the item under the pointer and confirm it. */
|
|
63
|
+
clickItem(index: number): void;
|
|
64
|
+
routeMouse(event: SgrMouseEvent, line: number, _col: number): void;
|
|
65
|
+
invalidate(): void;
|
|
66
|
+
render(width: number): readonly string[];
|
|
67
|
+
handleInput(keyData: string): void;
|
|
68
|
+
getSelectedItem(): SelectItem | null;
|
|
69
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { SgrMouseEvent } from "../mouse.js";
|
|
2
|
+
import type { Component } from "../tui.js";
|
|
3
|
+
export interface SettingItem {
|
|
4
|
+
/** Unique identifier for this setting */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Display label (left side) */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Optional description shown when selected */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Current value to display (right side) */
|
|
11
|
+
currentValue: string;
|
|
12
|
+
/** If provided, Enter/Space cycles through these values */
|
|
13
|
+
values?: string[];
|
|
14
|
+
/** If provided, Enter opens this submenu. Receives current value and done callback. */
|
|
15
|
+
submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
|
|
16
|
+
/** True when the displayed setting differs from its default value. */
|
|
17
|
+
changed?: boolean;
|
|
18
|
+
/** Render as a non-interactive section heading. Skipped by navigation and search. */
|
|
19
|
+
heading?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SettingsListTheme {
|
|
22
|
+
label: (text: string, selected: boolean, changed: boolean) => string;
|
|
23
|
+
value: (text: string, selected: boolean, changed: boolean) => string;
|
|
24
|
+
description: (text: string) => string;
|
|
25
|
+
cursor: string;
|
|
26
|
+
hint: (text: string) => string;
|
|
27
|
+
/** Style for section heading rows (dimmed when outside the active section). Falls back to `hint` when omitted. */
|
|
28
|
+
heading?: (text: string, dimmed: boolean) => string;
|
|
29
|
+
/** Style for sidebar section names in the split layout. Falls back to label/hint. */
|
|
30
|
+
section?: (text: string, active: boolean) => string;
|
|
31
|
+
/** Hover band applied to the full row under the mouse pointer. */
|
|
32
|
+
hovered?: (text: string) => string;
|
|
33
|
+
}
|
|
34
|
+
/** Optional behavior overrides for {@link SettingsList}. */
|
|
35
|
+
export interface SettingsListOptions {
|
|
36
|
+
/**
|
|
37
|
+
* "auto" (default) renders the section sidebar layout when headings exist
|
|
38
|
+
* and the width allows; "flat" always renders inline heading rows.
|
|
39
|
+
*/
|
|
40
|
+
layout?: "auto" | "flat";
|
|
41
|
+
/**
|
|
42
|
+
* When false, printable input is ignored (no internal type-to-filter) and
|
|
43
|
+
* the search status line is never rendered. Use when a parent component
|
|
44
|
+
* owns the query. Default true.
|
|
45
|
+
*/
|
|
46
|
+
typeToSearch?: boolean;
|
|
47
|
+
/** Text shown when the list has no items at all. */
|
|
48
|
+
emptyText?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Footer hint line (hint-styled, replaces the default navigation hint).
|
|
51
|
+
* An empty string removes the hint row and its leading blank entirely —
|
|
52
|
+
* use when the host renders its own footer.
|
|
53
|
+
*/
|
|
54
|
+
hint?: string;
|
|
55
|
+
/** Fixed split-sidebar width (columns incl. indent+gap); default derives from section names. */
|
|
56
|
+
sidebarWidth?: number;
|
|
57
|
+
}
|
|
58
|
+
/** Searchable text for a setting item: label, id, value, description, and cycle values. */
|
|
59
|
+
export declare function getSettingItemFilterText(item: SettingItem): string;
|
|
60
|
+
export declare class SettingsList implements Component {
|
|
61
|
+
#private;
|
|
62
|
+
/** Fired when the selected item changes (navigation, filtering, or setItems). */
|
|
63
|
+
onSelectionChange?: (item: SettingItem | undefined) => void;
|
|
64
|
+
constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void, options?: SettingsListOptions);
|
|
65
|
+
/** The currently selected item, or undefined when empty or on a heading. */
|
|
66
|
+
getSelectedItem(): SettingItem | undefined;
|
|
67
|
+
/** Move selection to the item with `id`. Returns false when it is not visible. */
|
|
68
|
+
selectItem(id: string): boolean;
|
|
69
|
+
/** True while keyboard focus is on the section headings instead of the setting rows. */
|
|
70
|
+
get sectionFocused(): boolean;
|
|
71
|
+
/** Whether section focus has anywhere to go: 2+ derived sections in the current view. */
|
|
72
|
+
hasSectionFocusTargets(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Toggle keyboard focus between section headings and setting rows. While
|
|
75
|
+
* focused, Up/Down jump whole sections and Enter/Esc return to the rows.
|
|
76
|
+
* Engages only when {@link hasSectionFocusTargets}; returns the new state.
|
|
77
|
+
*/
|
|
78
|
+
toggleSectionFocus(): boolean;
|
|
79
|
+
/** True while an item submenu owns input. */
|
|
80
|
+
hasOpenSubmenu(): boolean;
|
|
81
|
+
/** Resize the visible viewport (fullscreen hosts call this every render). */
|
|
82
|
+
setMaxVisible(rows: number): void;
|
|
83
|
+
/** Move the selection one step for a wheel notch. */
|
|
84
|
+
handleWheel(delta: -1 | 1): void;
|
|
85
|
+
/** Move the selection one step for a wheel notch if the pointer is within the settings pane. */
|
|
86
|
+
handleWheelAt(delta: -1 | 1, _line: number, col: number): boolean;
|
|
87
|
+
/** Highlight the item under the pointer (null clears). */
|
|
88
|
+
setHoverItem(id: string | null): void;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve a pointer position against the last rendered frame. `line` is the
|
|
91
|
+
* 0-based content-line index within this component's render output, `col`
|
|
92
|
+
* the 0-based column. Sidebar rows resolve to the section's first item.
|
|
93
|
+
*/
|
|
94
|
+
hitTest(line: number, col: number): string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Like {@link hitTest}, but only rows the pointer is visually on: sidebar
|
|
97
|
+
* jump targets are excluded so hovering section names does not light up
|
|
98
|
+
* pane rows.
|
|
99
|
+
*/
|
|
100
|
+
hoverTest(line: number, col: number): string | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Route a mouse event into an open submenu (coordinates are local to this
|
|
103
|
+
* list's rendered lines). Returns false when no submenu is open; submenus
|
|
104
|
+
* that do not implement {@link MouseRoutable} consume the event silently.
|
|
105
|
+
*/
|
|
106
|
+
routeSubmenuMouse(event: SgrMouseEvent, line: number, col: number): boolean;
|
|
107
|
+
getSearchQuery(): string;
|
|
108
|
+
hasSearchQuery(): boolean;
|
|
109
|
+
clearSearch(): void;
|
|
110
|
+
/** Update an item's currentValue */
|
|
111
|
+
updateValue(id: string, newValue: string): void;
|
|
112
|
+
/**
|
|
113
|
+
* Replace the entire items array. Selection is preserved by item id when
|
|
114
|
+
* the previous selection still survives the active filter, otherwise
|
|
115
|
+
* clamped to the last filtered item (or 0 if there are no matches).
|
|
116
|
+
* An open submenu is left untouched — its lifetime is bounded by its own
|
|
117
|
+
* done callback, and `#closeSubmenu` re-resolves the restored item on exit.
|
|
118
|
+
*/
|
|
119
|
+
setItems(items: SettingItem[]): void;
|
|
120
|
+
invalidate(): void;
|
|
121
|
+
render(width: number): readonly string[];
|
|
122
|
+
handleInput(data: string): void;
|
|
123
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
/**
|
|
3
|
+
* Spacer component that renders empty lines
|
|
4
|
+
*/
|
|
5
|
+
export declare class Spacer implements Component {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(lines?: number);
|
|
8
|
+
setLines(lines: number): void;
|
|
9
|
+
invalidate(): void;
|
|
10
|
+
render(_width: number): readonly string[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
/** Tab definition */
|
|
3
|
+
export interface Tab {
|
|
4
|
+
/** Unique identifier for the tab */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Display label shown in the tab bar */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Compact form (e.g. just the icon) used when the bar must shrink to fit one line. */
|
|
9
|
+
short?: string;
|
|
10
|
+
/** Render with the muted style and skip during keyboard navigation. */
|
|
11
|
+
muted?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Theme for styling the tab bar */
|
|
14
|
+
export interface TabBarTheme {
|
|
15
|
+
/** Style for the label prefix (e.g., "Settings:") */
|
|
16
|
+
label: (text: string) => string;
|
|
17
|
+
/** Style for the currently active tab */
|
|
18
|
+
activeTab: (text: string) => string;
|
|
19
|
+
/** Style for inactive tabs */
|
|
20
|
+
inactiveTab: (text: string) => string;
|
|
21
|
+
/** Style for the hint text (e.g., "(tab to cycle)") */
|
|
22
|
+
hint: (text: string) => string;
|
|
23
|
+
/** Style for muted tabs. Falls back to `inactiveTab` when omitted. */
|
|
24
|
+
mutedTab?: (text: string) => string;
|
|
25
|
+
/** Style for the tab under the mouse pointer. Falls back to `inactiveTab` when omitted. */
|
|
26
|
+
hoverTab?: (text: string) => string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Horizontal tab bar component.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const tabs = [
|
|
34
|
+
* { id: "config", label: "Config" },
|
|
35
|
+
* { id: "tools", label: "Tools" },
|
|
36
|
+
* ];
|
|
37
|
+
* const tabBar = new TabBar("Settings", tabs, theme);
|
|
38
|
+
* tabBar.onTabChange = (tab) => console.log(`Switched to ${tab.id}`);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class TabBar implements Component {
|
|
42
|
+
#private;
|
|
43
|
+
/** Callback fired when the active tab changes */
|
|
44
|
+
onTabChange?: (tab: Tab, index: number) => void;
|
|
45
|
+
/** Render the trailing "(tab to cycle)" hint. Disable when the host folds the hint into its own footer. */
|
|
46
|
+
showHint: boolean;
|
|
47
|
+
constructor(label: string, tabs: Tab[], theme: TabBarTheme, initialIndex?: number);
|
|
48
|
+
/** Get the currently active tab */
|
|
49
|
+
getActiveTab(): Tab;
|
|
50
|
+
/** Get the index of the currently active tab */
|
|
51
|
+
getActiveIndex(): number;
|
|
52
|
+
/** Set the active tab by index (clamped to valid range) */
|
|
53
|
+
setActiveIndex(index: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* Replace the tab set without firing onTabChange. The active tab is
|
|
56
|
+
* preserved by id when it survives the swap (or forced via `activeId`);
|
|
57
|
+
* otherwise the index is clamped.
|
|
58
|
+
*/
|
|
59
|
+
setTabs(tabs: Tab[], activeId?: string): void;
|
|
60
|
+
/** Set the active tab by id without firing onTabChange. Returns false when the id is unknown. */
|
|
61
|
+
setActiveById(id: string): boolean;
|
|
62
|
+
/** Activate the tab with `id`, firing onTabChange when it changes. Muted tabs are ignored. */
|
|
63
|
+
selectTab(id: string): boolean;
|
|
64
|
+
/** Move to the next non-muted tab (wraps to first tab after last) */
|
|
65
|
+
nextTab(): void;
|
|
66
|
+
/** Move to the previous non-muted tab (wraps to last tab before first) */
|
|
67
|
+
prevTab(): void;
|
|
68
|
+
invalidate(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Handle keyboard input for tab navigation.
|
|
71
|
+
* @returns true if the input was handled, false otherwise
|
|
72
|
+
*/
|
|
73
|
+
handleInput(data: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Render the tab bar. When the full labels overflow the width, tabs are
|
|
76
|
+
* collapsed to their `short` form one by one — starting with the tabs
|
|
77
|
+
* farthest from the active one — until the bar fits on a single line.
|
|
78
|
+
* Wrapping to multiple lines is the last resort.
|
|
79
|
+
*/
|
|
80
|
+
render(width: number): readonly string[];
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a pointer position against the last rendered frame. `line` is the
|
|
83
|
+
* 0-based line index within this component's render output, `col` the
|
|
84
|
+
* 0-based column.
|
|
85
|
+
*/
|
|
86
|
+
tabAt(line: number, col: number): Tab | undefined;
|
|
87
|
+
/** Highlight the tab under the pointer (null clears). */
|
|
88
|
+
setHoverTab(id: string | null): void;
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
/**
|
|
3
|
+
* Text component - displays multi-line text with word wrapping.
|
|
4
|
+
*
|
|
5
|
+
* Foreground colors may be supplied lazily via {@link setStyleFn} instead of
|
|
6
|
+
* baked into `text`: the styler runs at render time, so a caller that
|
|
7
|
+
* invalidates the component on a theme change (see the coding-agent's
|
|
8
|
+
* `onThemeChange` handler) re-resolves the color against the now-active theme
|
|
9
|
+
* rather than replaying the palette active when the component was constructed.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Text implements Component {
|
|
12
|
+
#private;
|
|
13
|
+
setIgnoreTight(ignore: boolean): this;
|
|
14
|
+
constructor(text?: string, paddingX?: number, paddingY?: number, customBgFn?: (text: string) => string);
|
|
15
|
+
getText(): string;
|
|
16
|
+
setText(text: string): boolean;
|
|
17
|
+
setCustomBgFn(customBgFn?: (text: string) => string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Supply a foreground styler applied to the text at render time (e.g. a
|
|
20
|
+
* theme color resolver). Unlike baking the color into `text`, the styler
|
|
21
|
+
* re-runs on every render, so invalidating the component after a theme
|
|
22
|
+
* change re-resolves the color against the active theme.
|
|
23
|
+
*/
|
|
24
|
+
setStyleFn(styleFn?: (text: string) => string): this;
|
|
25
|
+
invalidate(): void;
|
|
26
|
+
render(width: number): readonly string[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
/**
|
|
3
|
+
* Text component that truncates to fit viewport width
|
|
4
|
+
*/
|
|
5
|
+
export declare class TruncatedText implements Component {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(text: string, paddingX?: number, paddingY?: number);
|
|
8
|
+
invalidate(): void;
|
|
9
|
+
render(width: number): readonly string[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** DECSACE — select the rectangle change extent so DECCARA fills a rectangle. */
|
|
2
|
+
export declare const DECSACE_RECT = "\u001B[2*x";
|
|
3
|
+
/** DECSACE — restore the default (stream) change extent. */
|
|
4
|
+
export declare const DECSACE_DEFAULT = "\u001B[*x";
|
|
5
|
+
/**
|
|
6
|
+
* Encode a single DECCARA rectangle. `top`/`bottom` are 1-based inclusive screen
|
|
7
|
+
* rows, `left`/`right` 1-based inclusive columns, `sgr` the raw SGR parameter
|
|
8
|
+
* list to apply (e.g. `48;2;10;20;30`, `48;5;4`, `41`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function encodeDeccara(top: number, left: number, bottom: number, right: number, sgr: string): string;
|
|
11
|
+
/** Where to cut a fillable line and the background to paint over the remainder. */
|
|
12
|
+
export interface BgFillAnalysis {
|
|
13
|
+
/** Byte index where droppable trailing background padding begins (0 = whole line). */
|
|
14
|
+
cut: number;
|
|
15
|
+
/** 0-based column where the trailing padding begins (DECCARA left = leftCol + 1). */
|
|
16
|
+
leftCol: number;
|
|
17
|
+
/** SGR parameter list of the background covering the trailing region. */
|
|
18
|
+
bg: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Decide whether `line` (a final, width-fit, reset-terminated ANSI string) is a
|
|
22
|
+
* full-width background fill whose trailing padding can be replaced by a DECCARA
|
|
23
|
+
* rectangle. Returns `null` unless it can *prove* the dropped bytes are literal
|
|
24
|
+
* trailing spaces under a single, constant, non-default background span (or the
|
|
25
|
+
* entire row is background-styled spaces).
|
|
26
|
+
*
|
|
27
|
+
* Conservative by construction: any OSC sequence (hyperlinks/images), any
|
|
28
|
+
* non-SGR CSI, a partial row, an inconsistent or default trailing background, or
|
|
29
|
+
* a malformed escape all yield `null` so the caller keeps the exact original.
|
|
30
|
+
*/
|
|
31
|
+
export declare function analyzeBgFillLine(line: string, width: number): BgFillAnalysis | null;
|
|
32
|
+
/** Per-frame plan: the (possibly shortened) row strings and the DECCARA batch. */
|
|
33
|
+
export interface DeccaraPlan {
|
|
34
|
+
/** Row strings to write, parallel to the input. Optimized rows are shortened. */
|
|
35
|
+
texts: string[];
|
|
36
|
+
/** DECSACE-wrapped rectangle batch to emit after the rows, or `""` if none. */
|
|
37
|
+
sequence: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Plan DECCARA rectangles for a contiguous block of visible rows.
|
|
41
|
+
*
|
|
42
|
+
* `lines[k]` is the final ANSI string for screen row `firstScreenRow + k`
|
|
43
|
+
* (0-based). For each fillable row the trailing background padding is removed
|
|
44
|
+
* (the row's cells are cleared/erased by the caller, then repainted by the
|
|
45
|
+
* rectangle), and vertically adjacent rows with an identical left/right/bg span
|
|
46
|
+
* coalesce into one rectangle. Rectangles are emitted only when they save more
|
|
47
|
+
* bytes than they cost, so the result never exceeds the original byte count.
|
|
48
|
+
*/
|
|
49
|
+
export declare function planDeccaraFills(lines: string[], width: number, firstScreenRow?: number): DeccaraPlan;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TerminalId, TerminalNotification } from "./terminal-capabilities.js";
|
|
2
|
+
/** Resolved notifier binary used to fan a notification out to D-Bus. */
|
|
3
|
+
export type DesktopNotifierKind = "notify-send" | "gdbus";
|
|
4
|
+
export interface DesktopNotifier {
|
|
5
|
+
kind: DesktopNotifierKind;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Whether the current process can reach a freedesktop notification daemon:
|
|
10
|
+
* Linux platform plus either a session bus address in env or the
|
|
11
|
+
* systemd user-bus socket at `$XDG_RUNTIME_DIR/bus`. Caller is still responsible for
|
|
12
|
+
* resolving a delivery binary via {@link resolveDesktopNotifier}.
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasLinuxDesktopSession(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, fileExists?: (path: string) => boolean): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether `sendNotification` should also dispatch a D-Bus toast for this
|
|
17
|
+
* terminal. Returns true only when (1) the chosen `notifyProtocol` is BEL,
|
|
18
|
+
* which cannot carry arbitrary toast text, (2) the host exposes a Linux desktop
|
|
19
|
+
* session, and (3) the user has not opted out via `PI_NO_DESKTOP_NOTIFY=1`.
|
|
20
|
+
* Terminals that genuinely speak OSC 9 / OSC 99 pass
|
|
21
|
+
* `notifyProtocolIsBell=false` and are filtered before the D-Bus fallback can
|
|
22
|
+
* run. Pure helper for tests and the singleton path.
|
|
23
|
+
*/
|
|
24
|
+
export declare function shouldDeliverDesktopNotification(_terminalId: TerminalId, notifyProtocolIsBell: boolean, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): boolean;
|
|
25
|
+
/** Reset the cached notifier resolution. Tests only. */
|
|
26
|
+
export declare function resetDesktopNotifierCache(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Locate a libnotify-compatible delivery binary on `PATH`, preferring
|
|
29
|
+
* `notify-send` (one-shot, no marshalling) and falling back to `gdbus call`
|
|
30
|
+
* for hosts where libnotify is not installed but GLib is. Result is cached so
|
|
31
|
+
* repeated notifications do not hit `$which` again.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveDesktopNotifier(): DesktopNotifier | null;
|
|
34
|
+
/**
|
|
35
|
+
* Build the argv that delivers `message` through the resolved notifier. Pure
|
|
36
|
+
* helper so tests assert exact wire shape without spawning a child. Notes:
|
|
37
|
+
* - `notify-send` accepts title + body positionally and a numeric expire
|
|
38
|
+
* timeout (`-t`); urgency is a flag.
|
|
39
|
+
* - `gdbus call ... Notify` takes the freedesktop signature
|
|
40
|
+
* `s u s s s as a{sv} i`: app_name, replaces_id, app_icon, summary, body,
|
|
41
|
+
* actions, hints, expire_timeout. We feed hints with the urgency byte so
|
|
42
|
+
* the daemon classifies the toast identically to `notify-send`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildDesktopNotifyCommand(notifier: DesktopNotifier, message: string | TerminalNotification): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Fire-and-forget D-Bus desktop notification. Resolves a notifier, spawns it
|
|
47
|
+
* with stdio fully detached, and never throws — terminal notifications are
|
|
48
|
+
* best-effort and must not block the renderer or interleave bytes onto
|
|
49
|
+
* stdout. Caller is responsible for the gating check
|
|
50
|
+
* ({@link shouldDeliverDesktopNotification}).
|
|
51
|
+
*/
|
|
52
|
+
export declare function sendDesktopNotification(message: string | TerminalNotification): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AutocompleteProvider } from "./autocomplete.js";
|
|
2
|
+
import type { Component } from "./tui.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interface for custom editor components.
|
|
5
|
+
*
|
|
6
|
+
* This allows extensions to provide their own editor implementation
|
|
7
|
+
* (e.g., vim mode, emacs mode, custom keybindings) while maintaining
|
|
8
|
+
* compatibility with the core application.
|
|
9
|
+
*/
|
|
10
|
+
export interface EditorComponent extends Component {
|
|
11
|
+
/** Get the current text content */
|
|
12
|
+
getText(): string;
|
|
13
|
+
/** Set the text content */
|
|
14
|
+
setText(text: string): void;
|
|
15
|
+
/** Handle raw terminal input (key presses, paste sequences, etc.) */
|
|
16
|
+
handleInput(data: string): void;
|
|
17
|
+
/** Called when user submits (e.g., Enter key) */
|
|
18
|
+
onSubmit?: (text: string) => void;
|
|
19
|
+
/** Programmatically trigger submission (optional, e.g. for voice submit). */
|
|
20
|
+
submit?(): void;
|
|
21
|
+
/** Called when text changes */
|
|
22
|
+
onChange?: (text: string) => void;
|
|
23
|
+
/** Add text to history for up/down navigation */
|
|
24
|
+
addToHistory?(text: string): void;
|
|
25
|
+
/** Insert text at current cursor position */
|
|
26
|
+
insertTextAtCursor?(text: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Get text with any markers expanded (e.g., paste markers).
|
|
29
|
+
* Falls back to getText() if not implemented.
|
|
30
|
+
*/
|
|
31
|
+
getExpandedText?(): string;
|
|
32
|
+
/** Set the autocomplete provider */
|
|
33
|
+
setAutocompleteProvider?(provider: AutocompleteProvider): void;
|
|
34
|
+
/** Border color function */
|
|
35
|
+
borderColor?: (str: string) => string;
|
|
36
|
+
/** Set horizontal padding */
|
|
37
|
+
setPaddingX?(padding: number): void;
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy matching utilities.
|
|
3
|
+
*
|
|
4
|
+
* Matching is deliberately word-local for normal words. This keeps a query like
|
|
5
|
+
* "image provider" from matching a long setting description only because the
|
|
6
|
+
* letters i-m-a-g-e appear somewhere in order across unrelated words.
|
|
7
|
+
*
|
|
8
|
+
* Lower score = better match.
|
|
9
|
+
*/
|
|
10
|
+
export interface FuzzyMatch {
|
|
11
|
+
matches: boolean;
|
|
12
|
+
score: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FuzzyFilterResult<T> {
|
|
15
|
+
item: T;
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function fuzzyMatch(query: string, text: string): FuzzyMatch;
|
|
19
|
+
/**
|
|
20
|
+
* A text prepared once for repeated fuzzy matching.
|
|
21
|
+
*
|
|
22
|
+
* `fuzzyMatch` builds a search index per call; the module cache only admits
|
|
23
|
+
* texts up to {@link MAX_CACHED_TEXT_LEN}, so long corpora (session or
|
|
24
|
+
* transcript search) rebuild the index on every keystroke — the dominant cost
|
|
25
|
+
* when a selector re-filters a stable candidate list as the user types. Build
|
|
26
|
+
* one `FuzzyText` per candidate and call {@link match} per query instead; the
|
|
27
|
+
* index lives exactly as long as the caller's reference.
|
|
28
|
+
*/
|
|
29
|
+
export declare class FuzzyText {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(text: string);
|
|
32
|
+
/** Match `query` (space-separated tokens; all must match) against the prepared text. */
|
|
33
|
+
match(query: string): FuzzyMatch;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Filter and sort items by fuzzy match quality (best matches first).
|
|
37
|
+
* Supports space-separated tokens: all tokens must match.
|
|
38
|
+
*/
|
|
39
|
+
export declare function fuzzyRank<T>(items: T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[];
|
|
40
|
+
export declare function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[];
|
|
41
|
+
/**
|
|
42
|
+
* Clear the fuzzy search-index cache. Intended for tests/benchmarks so a fresh
|
|
43
|
+
* cold-start typing session can be measured on demand; not part of the supported
|
|
44
|
+
* TUI API.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare function resetFuzzyIndexCache(): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export * from "./autocomplete.js";
|
|
2
|
+
export * from "./components/box.js";
|
|
3
|
+
export * from "./components/cancellable-loader.js";
|
|
4
|
+
export * from "./components/editor.js";
|
|
5
|
+
export * from "./components/image.js";
|
|
6
|
+
export * from "./components/input.js";
|
|
7
|
+
export * from "./components/loader.js";
|
|
8
|
+
export * from "./components/markdown.js";
|
|
9
|
+
export * from "./components/scroll-view.js";
|
|
10
|
+
export * from "./components/select-list.js";
|
|
11
|
+
export * from "./components/settings-list.js";
|
|
12
|
+
export * from "./components/spacer.js";
|
|
13
|
+
export * from "./components/tab-bar.js";
|
|
14
|
+
export * from "./components/text.js";
|
|
15
|
+
export * from "./components/truncated-text.js";
|
|
16
|
+
export * from "./deccara.js";
|
|
17
|
+
export * from "./desktop-notify.js";
|
|
18
|
+
export type * from "./editor-component.js";
|
|
19
|
+
export * from "./fuzzy.js";
|
|
20
|
+
export * from "./keybindings.js";
|
|
21
|
+
export * from "./keys.js";
|
|
22
|
+
export * from "./kitty-graphics.js";
|
|
23
|
+
export * from "./latex-block.js";
|
|
24
|
+
export * from "./latex-to-unicode.js";
|
|
25
|
+
export * from "./mouse.js";
|
|
26
|
+
export * from "./stdin-buffer.js";
|
|
27
|
+
export type * from "./symbols.js";
|
|
28
|
+
export * from "./terminal.js";
|
|
29
|
+
export * from "./terminal-capabilities.js";
|
|
30
|
+
export * from "./ttyid.js";
|
|
31
|
+
export * from "./tui.js";
|
|
32
|
+
export * from "./utils.js";
|