@prometheus-ai/tui 0.5.3 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/types/autocomplete.d.ts +3 -1
  2. package/dist/types/components/box.d.ts +1 -1
  3. package/dist/types/components/editor.d.ts +35 -2
  4. package/dist/types/components/image.d.ts +22 -3
  5. package/dist/types/components/input.d.ts +6 -1
  6. package/dist/types/components/loader.d.ts +9 -2
  7. package/dist/types/components/markdown.d.ts +3 -1
  8. package/dist/types/components/scroll-view.d.ts +23 -1
  9. package/dist/types/components/select-list.d.ts +19 -1
  10. package/dist/types/components/settings-list.d.ts +87 -7
  11. package/dist/types/components/spacer.d.ts +1 -1
  12. package/dist/types/components/tab-bar.d.ts +37 -4
  13. package/dist/types/components/text.d.ts +2 -2
  14. package/dist/types/components/truncated-text.d.ts +1 -1
  15. package/dist/types/fuzzy.d.ts +10 -1
  16. package/dist/types/index.d.ts +1 -0
  17. package/dist/types/keybindings.d.ts +5 -3
  18. package/dist/types/keys.d.ts +1 -1
  19. package/dist/types/kill-ring.d.ts +0 -7
  20. package/dist/types/kitty-graphics.d.ts +16 -31
  21. package/dist/types/loop-watchdog.d.ts +39 -0
  22. package/dist/types/mouse.d.ts +41 -0
  23. package/dist/types/stdin-buffer.d.ts +17 -0
  24. package/dist/types/terminal-capabilities.d.ts +74 -18
  25. package/dist/types/terminal.d.ts +34 -36
  26. package/dist/types/tui.d.ts +191 -79
  27. package/dist/types/utils.d.ts +5 -2
  28. package/package.json +4 -4
  29. package/src/autocomplete.ts +79 -65
  30. package/src/components/box.ts +43 -63
  31. package/src/components/editor.ts +471 -136
  32. package/src/components/image.ts +85 -9
  33. package/src/components/input.ts +12 -3
  34. package/src/components/loader.ts +35 -21
  35. package/src/components/markdown.ts +174 -53
  36. package/src/components/scroll-view.ts +63 -2
  37. package/src/components/select-list.ts +233 -38
  38. package/src/components/settings-list.ts +626 -64
  39. package/src/components/spacer.ts +9 -5
  40. package/src/components/tab-bar.ts +153 -28
  41. package/src/components/text.ts +6 -2
  42. package/src/components/truncated-text.ts +10 -2
  43. package/src/fuzzy.ts +214 -59
  44. package/src/index.ts +3 -1
  45. package/src/keybindings.ts +72 -14
  46. package/src/keys.ts +1 -1
  47. package/src/kill-ring.ts +5 -0
  48. package/src/kitty-graphics.ts +2 -101
  49. package/src/loop-watchdog.ts +106 -0
  50. package/src/mouse.ts +55 -0
  51. package/src/stdin-buffer.ts +291 -81
  52. package/src/terminal-capabilities.ts +206 -168
  53. package/src/terminal.ts +367 -110
  54. package/src/tui.ts +2102 -1729
  55. package/src/utils.ts +92 -60
@@ -8,6 +8,7 @@ export interface AutocompleteItem {
8
8
  type Awaitable<T> = T | Promise<T>;
9
9
  export interface SlashCommand {
10
10
  name: string;
11
+ aliases?: string[];
11
12
  description?: string;
12
13
  argumentHint?: string;
13
14
  getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
@@ -48,9 +49,10 @@ export interface AutocompleteProvider {
48
49
  insert: string;
49
50
  } | null;
50
51
  }
52
+ type CommandEntry = SlashCommand | AutocompleteItem;
51
53
  export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
52
54
  #private;
53
- constructor(commands?: (SlashCommand | AutocompleteItem)[], basePath?: string);
55
+ constructor(commands?: CommandEntry[], basePath?: string);
54
56
  getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
55
57
  items: AutocompleteItem[];
56
58
  prefix: string;
@@ -13,5 +13,5 @@ export declare class Box implements Component {
13
13
  setPaddingY(paddingY: number): void;
14
14
  setBgFn(bgFn?: (text: string) => string): void;
15
15
  invalidate(): void;
16
- render(width: number): string[];
16
+ render(width: number): readonly string[];
17
17
  }
@@ -37,9 +37,22 @@ export declare class Editor implements Component, Focusable {
37
37
  decorateText: ((text: string) => string) | undefined;
38
38
  borderColor: (str: string) => string;
39
39
  onAutocompleteUpdate?: () => void;
40
- onSubmit?: (text: string) => void;
40
+ /** Optional pattern matching atomic placeholder tokens (e.g. `[Image #1, 800x600]` or
41
+ * `[Paste #2, +30 lines]`) that the editor treats as indivisible: a backspace or forward-delete
42
+ * landing on any character of a token removes the whole token instead of corrupting it into
43
+ * stray text. MUST be a global regex; the editor recompiles a private copy so its `lastIndex`
44
+ * is never shared with the caller. */
45
+ atomicTokenPattern: RegExp | undefined;
46
+ onSubmit?: (text: string) => void | Promise<void>;
41
47
  onAltEnter?: (text: string) => void;
42
48
  onChange?: (text: string) => void;
49
+ /** Called for a "marker-sized" paste — the point where the editor would otherwise collapse it
50
+ * into a `[Paste #N]` token (> 10 lines or > 1000 characters). Return `true` to intercept:
51
+ * the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
52
+ * "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
53
+ * via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
54
+ * default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count. */
55
+ onLargePaste?: (text: string, lineCount: number) => boolean;
43
56
  onAutocompleteCancel?: () => void;
44
57
  disableSubmit: boolean;
45
58
  constructor(theme: EditorTheme);
@@ -75,7 +88,7 @@ export declare class Editor implements Component, Focusable {
75
88
  */
76
89
  addToHistory(text: string): void;
77
90
  invalidate(): void;
78
- render(width: number): string[];
91
+ render(width: number): readonly string[];
79
92
  handleInput(data: string): void;
80
93
  getText(): string;
81
94
  /**
@@ -100,6 +113,26 @@ export declare class Editor implements Component, Focusable {
100
113
  setText(text: string): void;
101
114
  /** Insert text at the current cursor position */
102
115
  insertText(text: string): void;
116
+ /** Delete up to `count` characters immediately before the cursor on the current line.
117
+ * Used to "track back" the auto-repeat spaces that the space-hold push-to-talk gesture
118
+ * optimistically inserts before it recognizes the hold. Capped at the cursor column so it
119
+ * never crosses a line boundary or under-runs the line. */
120
+ deleteBeforeCursor(count: number): void;
121
+ /** Show or replace a volatile speech-to-text preview at the cursor. The text is
122
+ * inserted with undo suspended so a long live dictation never floods the undo
123
+ * stack; finalize it with {@link commitVolatileText} or drop it with
124
+ * {@link clearVolatileText}. Newlines are allowed. */
125
+ setVolatileText(text: string): void;
126
+ /** Remove the current volatile preview without committing it. */
127
+ clearVolatileText(): void;
128
+ /** Drop any volatile preview, then insert `text` as a single undoable edit. */
129
+ commitVolatileText(text: string): void;
130
+ /** Apply terminal paste semantics to text from non-bracketed paste transports. */
131
+ pasteText(text: string): void;
132
+ /** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
133
+ * {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
134
+ * this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
135
+ insertPaste(content: string): void;
103
136
  isShowingAutocomplete(): boolean;
104
137
  }
105
138
  export {};
@@ -48,12 +48,22 @@ export declare class ImageBudget {
48
48
  * gets a fresh id every call.
49
49
  */
50
50
  acquireId(key?: string): number;
51
- /** Begin a render pass. Called by the renderer before composing the frame. */
52
- beginPass(): void;
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;
53
59
  /**
54
60
  * Record an image in display order and report whether it must render its text
55
61
  * fallback this frame. Called by every {@link Image} during render — including
56
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.
57
67
  */
58
68
  observe(imageId: number): boolean;
59
69
  /**
@@ -73,6 +83,15 @@ export declare class ImageBudget {
73
83
  * repeated call (e.g. a width-change re-render) never re-sends the data.
74
84
  */
75
85
  enqueueTransmit(imageId: number, sequence: string): void;
86
+ /** Whether a frame has image data queued but not yet written to the terminal. */
87
+ hasPendingTransmits(): boolean;
88
+ /**
89
+ * True when the budget has nothing in flight: no live images observed on
90
+ * the last pass, no queued transmits, no pending purges, and no stricter
91
+ * threshold left to apply. A component-scoped frame may skip the observe
92
+ * pass only then — a partial tree walk would under-count display order.
93
+ */
94
+ get quiescent(): boolean;
76
95
  /** Transmit sequences to write before this frame's placements; clears the queue. */
77
96
  takeTransmits(): readonly string[];
78
97
  }
@@ -80,5 +99,5 @@ export declare class Image implements Component {
80
99
  #private;
81
100
  constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
82
101
  invalidate(): void;
83
- render(width: number): string[];
102
+ render(width: number): readonly string[];
84
103
  }
@@ -4,6 +4,8 @@ import { type Component, type Focusable } from "../tui";
4
4
  */
5
5
  export declare class Input implements Component, Focusable {
6
6
  #private;
7
+ /** Rendered before the editable area; set to "" for chrome-less embedding. */
8
+ prompt: string;
7
9
  onSubmit?: (value: string) => void;
8
10
  onEscape?: () => void;
9
11
  /** Focusable interface - set by TUI when focus changes */
@@ -13,6 +15,9 @@ export declare class Input implements Component, Focusable {
13
15
  setUseTerminalCursor(useTerminalCursor: boolean): void;
14
16
  getUseTerminalCursor(): boolean;
15
17
  handleInput(data: string): void;
18
+ /** Apply terminal paste semantics to text from non-bracketed paste transports
19
+ * (e.g. kitty's OSC 5522 enhanced clipboard read). Mirrors `Editor.pasteText`. */
20
+ pasteText(text: string): void;
16
21
  invalidate(): void;
17
- render(width: number): string[];
22
+ render(width: number): readonly string[];
18
23
  }
@@ -1,13 +1,20 @@
1
1
  import type { TUI } from "../tui";
2
2
  import { Text } from "./text";
3
+ type ColorFn = (str: string) => string;
4
+ export type LoaderMessageColorFn = ColorFn & {
5
+ readonly animated?: true;
6
+ };
3
7
  export declare class Loader extends Text {
4
8
  #private;
5
9
  private spinnerColorFn;
6
10
  private messageColorFn;
7
11
  private message;
8
- constructor(ui: TUI, spinnerColorFn: (str: string) => string, messageColorFn: (str: string) => string, message?: string, spinnerFrames?: string[]);
9
- render(width: number): string[];
12
+ constructor(ui: TUI, spinnerColorFn: ColorFn, messageColorFn: LoaderMessageColorFn, message?: string, spinnerFrames?: string[]);
13
+ render(width: number): readonly string[];
10
14
  start(): void;
11
15
  stop(): void;
16
+ /** Lifecycle teardown: stop the animation timer. Idempotent. */
17
+ dispose(): void;
12
18
  setMessage(message: string): void;
13
19
  }
20
+ export {};
@@ -52,7 +52,9 @@ export declare class Markdown implements Component {
52
52
  constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
53
53
  setText(text: string): void;
54
54
  invalidate(): void;
55
- render(width: number): string[];
55
+ get transientRenderCache(): boolean;
56
+ set transientRenderCache(value: boolean);
57
+ render(width: number): readonly string[];
56
58
  }
57
59
  /**
58
60
  * Render inline markdown (bold, italic, code, links, strikethrough) to a styled string.
@@ -1,4 +1,5 @@
1
1
  import type { Component } from "../tui";
2
+ import { Ellipsis } from "../utils";
2
3
  type ScrollbarMode = "auto" | "always" | "never";
3
4
  export interface ScrollViewTheme {
4
5
  track?: (text: string) => string;
@@ -13,6 +14,18 @@ export interface ScrollViewOptions {
13
14
  theme?: ScrollViewTheme;
14
15
  trackChar?: string;
15
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;
16
29
  }
17
30
  /**
18
31
  * Fixed-height viewport over pre-rendered lines, with optional right-edge scrollbar.
@@ -34,7 +47,16 @@ export declare class ScrollView implements Component {
34
47
  page(delta: number): void;
35
48
  scrollToTop(): void;
36
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;
37
59
  invalidate(): void;
38
- render(width: number): string[];
60
+ render(width: number): readonly string[];
39
61
  }
40
62
  export {};
@@ -14,6 +14,8 @@ export interface SelectListTheme {
14
14
  scrollInfo: (text: string) => string;
15
15
  noMatch: (text: string) => string;
16
16
  symbols: SymbolTheme;
17
+ /** Hover band applied to the full row under the mouse pointer. */
18
+ hovered?: (text: string) => string;
17
19
  }
18
20
  export interface SelectListTruncatePrimaryContext {
19
21
  text: string;
@@ -28,6 +30,14 @@ export interface SelectListLayoutOptions {
28
30
  truncatePrimary?: (context: SelectListTruncatePrimaryContext) => string;
29
31
  /** Enable type-to-filter search when the item count exceeds maxVisible. Defaults to true. */
30
32
  overflowSearch?: boolean;
33
+ /**
34
+ * Wrap long descriptions onto continuation rows indented under the
35
+ * description column instead of truncating. Defaults to false so existing
36
+ * single-line consumers are unaffected. Navigation remains item-to-item;
37
+ * the scrollbar tracks visual rows so the thumb stays correct when items
38
+ * wrap unevenly.
39
+ */
40
+ wrapDescription?: boolean;
31
41
  }
32
42
  export declare class SelectList implements Component {
33
43
  #private;
@@ -41,8 +51,16 @@ export declare class SelectList implements Component {
41
51
  constructor(items: ReadonlyArray<SelectItem>, maxVisible: number, theme: SelectListTheme, layout?: SelectListLayoutOptions);
42
52
  setFilter(filter: string): void;
43
53
  setSelectedIndex(index: number): void;
54
+ /** Resolve a 0-based rendered-line index to a filtered-item index. */
55
+ hitTest(line: number): number | undefined;
56
+ /** Highlight the item under the pointer (null clears). */
57
+ setHoverIndex(index: number | null): void;
58
+ /** Move the selection one step for a wheel notch. */
59
+ handleWheel(delta: -1 | 1): void;
60
+ /** Mouse click: select the item under the pointer and confirm it. */
61
+ clickItem(index: number): void;
44
62
  invalidate(): void;
45
- render(width: number): string[];
63
+ render(width: number): readonly string[];
46
64
  handleInput(keyData: string): void;
47
65
  getSelectedItem(): SelectItem | null;
48
66
  }
@@ -1,3 +1,4 @@
1
+ import type { SgrMouseEvent } from "../mouse";
1
2
  import type { Component } from "../tui";
2
3
  export interface SettingItem {
3
4
  /** Unique identifier for this setting */
@@ -14,6 +15,8 @@ export interface SettingItem {
14
15
  submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
15
16
  /** True when the displayed setting differs from its default value. */
16
17
  changed?: boolean;
18
+ /** Render as a non-interactive section heading. Skipped by navigation and search. */
19
+ heading?: boolean;
17
20
  }
18
21
  export interface SettingsListTheme {
19
22
  label: (text: string, selected: boolean, changed: boolean) => string;
@@ -21,21 +24,98 @@ export interface SettingsListTheme {
21
24
  description: (text: string) => string;
22
25
  cursor: string;
23
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;
24
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;
25
60
  export declare class SettingsList implements Component {
26
61
  #private;
27
- constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void);
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
+ /** Highlight the item under the pointer (null clears). */
86
+ setHoverItem(id: string | null): void;
87
+ /**
88
+ * Resolve a pointer position against the last rendered frame. `line` is the
89
+ * 0-based content-line index within this component's render output, `col`
90
+ * the 0-based column. Sidebar rows resolve to the section's first item.
91
+ */
92
+ hitTest(line: number, col: number): string | undefined;
93
+ /**
94
+ * Like {@link hitTest}, but only rows the pointer is visually on: sidebar
95
+ * jump targets are excluded so hovering section names does not light up
96
+ * pane rows.
97
+ */
98
+ hoverTest(line: number, col: number): string | undefined;
99
+ /**
100
+ * Route a mouse event into an open submenu (coordinates are local to this
101
+ * list's rendered lines). Returns false when no submenu is open; submenus
102
+ * that do not implement {@link MouseRoutable} consume the event silently.
103
+ */
104
+ routeSubmenuMouse(event: SgrMouseEvent, line: number, col: number): boolean;
105
+ getSearchQuery(): string;
106
+ hasSearchQuery(): boolean;
107
+ clearSearch(): void;
28
108
  /** Update an item's currentValue */
29
109
  updateValue(id: string, newValue: string): void;
30
110
  /**
31
- * Replace the entire items array. Selection is preserved when the prior
32
- * index is still valid, otherwise clamped to the last item (or 0 if the
33
- * list is now empty). An open submenu is left untouched its lifetime
34
- * is bounded by its own done callback, and `#closeSubmenu` re-clamps the
35
- * restored index against the new list on the way out.
111
+ * Replace the entire items array. Selection is preserved by item id when
112
+ * the previous selection still survives the active filter, otherwise
113
+ * clamped to the last filtered item (or 0 if there are no matches).
114
+ * An open submenu is left untouched its lifetime is bounded by its own
115
+ * done callback, and `#closeSubmenu` re-resolves the restored item on exit.
36
116
  */
37
117
  setItems(items: SettingItem[]): void;
38
118
  invalidate(): void;
39
- render(width: number): string[];
119
+ render(width: number): readonly string[];
40
120
  handleInput(data: string): void;
41
121
  }
@@ -7,5 +7,5 @@ export declare class Spacer implements Component {
7
7
  constructor(lines?: number);
8
8
  setLines(lines: number): void;
9
9
  invalidate(): void;
10
- render(_width: number): string[];
10
+ render(_width: number): readonly string[];
11
11
  }
@@ -5,6 +5,10 @@ export interface Tab {
5
5
  id: string;
6
6
  /** Display label shown in the tab bar */
7
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;
8
12
  }
9
13
  /** Theme for styling the tab bar */
10
14
  export interface TabBarTheme {
@@ -16,6 +20,10 @@ export interface TabBarTheme {
16
20
  inactiveTab: (text: string) => string;
17
21
  /** Style for the hint text (e.g., "(tab to cycle)") */
18
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;
19
27
  }
20
28
  /**
21
29
  * Horizontal tab bar component.
@@ -34,6 +42,8 @@ export declare class TabBar implements Component {
34
42
  #private;
35
43
  /** Callback fired when the active tab changes */
36
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;
37
47
  constructor(label: string, tabs: Tab[], theme: TabBarTheme, initialIndex?: number);
38
48
  /** Get the currently active tab */
39
49
  getActiveTab(): Tab;
@@ -41,9 +51,19 @@ export declare class TabBar implements Component {
41
51
  getActiveIndex(): number;
42
52
  /** Set the active tab by index (clamped to valid range) */
43
53
  setActiveIndex(index: number): void;
44
- /** Move to the next tab (wraps to first tab after last) */
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) */
45
65
  nextTab(): void;
46
- /** Move to the previous tab (wraps to last tab before first) */
66
+ /** Move to the previous non-muted tab (wraps to last tab before first) */
47
67
  prevTab(): void;
48
68
  invalidate(): void;
49
69
  /**
@@ -51,6 +71,19 @@ export declare class TabBar implements Component {
51
71
  * @returns true if the input was handled, false otherwise
52
72
  */
53
73
  handleInput(data: string): boolean;
54
- /** Render the tab bar, wrapping to multiple lines if needed */
55
- render(width: number): string[];
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;
56
89
  }
@@ -6,8 +6,8 @@ export declare class Text implements Component {
6
6
  #private;
7
7
  constructor(text?: string, paddingX?: number, paddingY?: number, customBgFn?: (text: string) => string);
8
8
  getText(): string;
9
- setText(text: string): void;
9
+ setText(text: string): boolean;
10
10
  setCustomBgFn(customBgFn?: (text: string) => string): void;
11
11
  invalidate(): void;
12
- render(width: number): string[];
12
+ render(width: number): readonly string[];
13
13
  }
@@ -6,5 +6,5 @@ export declare class TruncatedText implements Component {
6
6
  #private;
7
7
  constructor(text: string, paddingX?: number, paddingY?: number);
8
8
  invalidate(): void;
9
- render(width: number): string[];
9
+ render(width: number): readonly string[];
10
10
  }
@@ -1,15 +1,24 @@
1
1
  /**
2
2
  * Fuzzy matching utilities.
3
- * Matches if all query characters appear in order (not necessarily consecutive).
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
+ *
4
8
  * Lower score = better match.
5
9
  */
6
10
  export interface FuzzyMatch {
7
11
  matches: boolean;
8
12
  score: number;
9
13
  }
14
+ export interface FuzzyFilterResult<T> {
15
+ item: T;
16
+ score: number;
17
+ }
10
18
  export declare function fuzzyMatch(query: string, text: string): FuzzyMatch;
11
19
  /**
12
20
  * Filter and sort items by fuzzy match quality (best matches first).
13
21
  * Supports space-separated tokens: all tokens must match.
14
22
  */
23
+ export declare function fuzzyRank<T>(items: T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[];
15
24
  export declare function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[];
@@ -19,6 +19,7 @@ export * from "./fuzzy";
19
19
  export * from "./keybindings";
20
20
  export * from "./keys";
21
21
  export * from "./kitty-graphics";
22
+ export * from "./mouse";
22
23
  export * from "./stdin-buffer";
23
24
  export type * from "./symbols";
24
25
  export * from "./terminal";
@@ -102,11 +102,11 @@ export declare const TUI_KEYBINDINGS: {
102
102
  readonly description: "Delete character forward";
103
103
  };
104
104
  readonly "tui.editor.deleteWordBackward": {
105
- readonly defaultKeys: ["ctrl+w", "alt+backspace", "ctrl+backspace"];
105
+ readonly defaultKeys: ["ctrl+w", "alt+backspace", "ctrl+backspace", "super+alt+backspace"];
106
106
  readonly description: "Delete word backward";
107
107
  };
108
108
  readonly "tui.editor.deleteWordForward": {
109
- readonly defaultKeys: ["alt+delete", "alt+d"];
109
+ readonly defaultKeys: ["alt+delete", "alt+d", "super+alt+delete", "super+alt+d"];
110
110
  readonly description: "Delete word forward";
111
111
  };
112
112
  readonly "tui.editor.deleteToLineStart": {
@@ -130,7 +130,7 @@ export declare const TUI_KEYBINDINGS: {
130
130
  readonly description: "Undo";
131
131
  };
132
132
  readonly "tui.input.newLine": {
133
- readonly defaultKeys: "shift+enter";
133
+ readonly defaultKeys: ["shift+enter", "ctrl+j"];
134
134
  readonly description: "Insert newline";
135
135
  };
136
136
  readonly "tui.input.submit": {
@@ -174,6 +174,8 @@ export interface KeybindingConflict {
174
174
  key: KeyId;
175
175
  keybindings: string[];
176
176
  }
177
+ export declare function canonicalKeyId(key: string): string;
178
+ export declare function addKeyAliases(keys: Set<string>, key: KeyId): void;
177
179
  export declare class KeybindingsManager {
178
180
  #private;
179
181
  constructor(definitions: KeybindingDefinitions, userBindings?: KeybindingsConfig);
@@ -62,7 +62,7 @@ export type KeyId = BaseKey | ModifiedKeyId<BaseKey>;
62
62
  * modifier methods return precisely-typed concatenations (e.g. `Key.ctrl("c")`
63
63
  * is `"ctrl+c"`, not just `string`). This mirrors the upstream
64
64
  * `@mariozechner/pi-tui` `Key` export verbatim so plugins built against any
65
- * scope alias (`@mariozechner`, `@earendil-works`, `@prometheus`) keep working
65
+ * scope alias (`@mariozechner`, `@earendil-works`, `@Prometheus`) keep working
66
66
  * once the specifier shim remaps them to this package.
67
67
  */
68
68
  export declare const Key: {
@@ -1,10 +1,3 @@
1
- /**
2
- * Ring buffer for Emacs-style kill/yank operations.
3
- *
4
- * Tracks killed (deleted) text entries. Consecutive kills can accumulate
5
- * into a single entry. Supports yank (paste most recent) and yank-pop
6
- * (cycle through older entries).
7
- */
8
1
  export declare class KillRing {
9
2
  #private;
10
3
  /**