@sayknow-cli/tui 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +8 -9
  3. package/src/components/settings-list.ts +12 -0
  4. package/src/terminal.ts +42 -2
  5. package/src/tui.ts +38 -4
  6. package/dist/types/autocomplete.d.ts +0 -82
  7. package/dist/types/bracketed-paste.d.ts +0 -26
  8. package/dist/types/components/box.d.ts +0 -20
  9. package/dist/types/components/cancellable-loader.d.ts +0 -21
  10. package/dist/types/components/editor.d.ts +0 -112
  11. package/dist/types/components/image.d.ts +0 -16
  12. package/dist/types/components/input.d.ts +0 -16
  13. package/dist/types/components/loader.d.ts +0 -14
  14. package/dist/types/components/markdown.d.ts +0 -64
  15. package/dist/types/components/select-list.d.ts +0 -46
  16. package/dist/types/components/settings-list.d.ts +0 -39
  17. package/dist/types/components/spacer.d.ts +0 -11
  18. package/dist/types/components/tab-bar.d.ts +0 -56
  19. package/dist/types/components/text.d.ts +0 -13
  20. package/dist/types/components/truncated-text.d.ts +0 -10
  21. package/dist/types/editor-component.d.ts +0 -36
  22. package/dist/types/fuzzy.d.ts +0 -15
  23. package/dist/types/index.d.ts +0 -26
  24. package/dist/types/keybindings.d.ts +0 -201
  25. package/dist/types/keys.d.ts +0 -208
  26. package/dist/types/kill-ring.d.ts +0 -27
  27. package/dist/types/metrics.d.ts +0 -85
  28. package/dist/types/stdin-buffer.d.ts +0 -50
  29. package/dist/types/symbols.d.ts +0 -23
  30. package/dist/types/terminal-capabilities.d.ts +0 -75
  31. package/dist/types/terminal.d.ts +0 -80
  32. package/dist/types/ttyid.d.ts +0 -9
  33. package/dist/types/tui.d.ts +0 -182
  34. package/dist/types/utils.d.ts +0 -75
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.7.8] - 2026-06-30
6
+
7
+ ### Fixed
8
+
9
+ - The render loop now isolates a component whose `render()` throws: the failure is logged once and replaced with a `[render error: <Name>]` fallback line instead of escaping the frame and tripping the process-level fail-fast `uncaughtException` exit. Previously any unguarded renderer fault (e.g. a tool renderer fed an undefined field) crashed the whole app on whatever triggered the next frame — a keystroke, resize, or command such as `/background` (#1291).
10
+
11
+ - Resolved terminal dimensions from the live TTY window size before stream defaults so wide Windows Terminal/PowerShell sessions render against the actual viewport width (#1239).
12
+
5
13
  ## [0.7.4] - 2026-06-27
6
14
 
7
15
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/tui",
4
- "version": "0.3.4",
4
+ "version": "0.3.6",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://github.com/jaybeyond/Sayknow_CLI",
7
7
  "author": "jaybeyond",
@@ -27,7 +27,7 @@
27
27
  "cli"
28
28
  ],
29
29
  "main": "./src/index.ts",
30
- "types": "./dist/types/index.d.ts",
30
+ "types": "./src/index.ts",
31
31
  "scripts": {
32
32
  "check": "biome check . && bun run check:types",
33
33
  "check:types": "tsgo -p tsconfig.json --noEmit",
@@ -38,8 +38,8 @@
38
38
  "fmt": "biome format --write ."
39
39
  },
40
40
  "dependencies": {
41
- "@sayknow-cli/natives": "0.3.4",
42
- "@sayknow-cli/utils": "0.3.4",
41
+ "@sayknow-cli/natives": "0.3.6",
42
+ "@sayknow-cli/utils": "0.3.6",
43
43
  "lru-cache": "11.3.6",
44
44
  "marked": "^18.0.3"
45
45
  },
@@ -53,20 +53,19 @@
53
53
  "files": [
54
54
  "src",
55
55
  "README.md",
56
- "CHANGELOG.md",
57
- "dist/types"
56
+ "CHANGELOG.md"
58
57
  ],
59
58
  "exports": {
60
59
  ".": {
61
- "types": "./dist/types/index.d.ts",
60
+ "types": "./src/index.ts",
62
61
  "import": "./src/index.ts"
63
62
  },
64
63
  "./*": {
65
- "types": "./dist/types/*.d.ts",
64
+ "types": "./src/*.ts",
66
65
  "import": "./src/*.ts"
67
66
  },
68
67
  "./components/*": {
69
- "types": "./dist/types/components/*.d.ts",
68
+ "types": "./src/components/*.ts",
70
69
  "import": "./src/components/*.ts"
71
70
  },
72
71
  "./*.js": "./src/*.ts"
@@ -32,6 +32,7 @@ export class SettingsList implements Component {
32
32
  #maxVisible: number;
33
33
  #onChange: (id: string, newValue: string) => void;
34
34
  #onCancel: () => void;
35
+ #onSelectionChange?: (item: SettingItem | undefined) => void;
35
36
 
36
37
  // Submenu state
37
38
  #submenuComponent: Component | null = null;
@@ -43,12 +44,15 @@ export class SettingsList implements Component {
43
44
  theme: SettingsListTheme,
44
45
  onChange: (id: string, newValue: string) => void,
45
46
  onCancel: () => void,
47
+ onSelectionChange?: (item: SettingItem | undefined) => void,
46
48
  ) {
47
49
  this.#items = items;
48
50
  this.#maxVisible = maxVisible;
49
51
  this.#theme = theme;
50
52
  this.#onChange = onChange;
51
53
  this.#onCancel = onCancel;
54
+ this.#onSelectionChange = onSelectionChange;
55
+ this.#notifySelectionChange();
52
56
  }
53
57
 
54
58
  /** Update an item's currentValue */
@@ -73,12 +77,17 @@ export class SettingsList implements Component {
73
77
  } else if (this.#selectedIndex >= this.#items.length) {
74
78
  this.#selectedIndex = this.#items.length - 1;
75
79
  }
80
+ this.#notifySelectionChange();
76
81
  }
77
82
 
78
83
  invalidate(): void {
79
84
  this.#submenuComponent?.invalidate?.();
80
85
  }
81
86
 
87
+ #notifySelectionChange(): void {
88
+ this.#onSelectionChange?.(this.#items[this.#selectedIndex]);
89
+ }
90
+
82
91
  render(width: number): string[] {
83
92
  // If submenu is active, render it instead
84
93
  if (this.#submenuComponent) {
@@ -167,8 +176,10 @@ export class SettingsList implements Component {
167
176
  const kb = getKeybindings();
168
177
  if (kb.matches(data, "tui.select.up")) {
169
178
  this.#selectedIndex = this.#selectedIndex === 0 ? this.#items.length - 1 : this.#selectedIndex - 1;
179
+ this.#notifySelectionChange();
170
180
  } else if (kb.matches(data, "tui.select.down")) {
171
181
  this.#selectedIndex = this.#selectedIndex === this.#items.length - 1 ? 0 : this.#selectedIndex + 1;
182
+ this.#notifySelectionChange();
172
183
  } else if (kb.matches(data, "tui.select.confirm") || data === " " || data === "\n") {
173
184
  this.#activateItem();
174
185
  } else if (kb.matches(data, "tui.select.cancel")) {
@@ -206,6 +217,7 @@ export class SettingsList implements Component {
206
217
  if (this.#submenuItemIndex !== null) {
207
218
  this.#selectedIndex = this.#submenuItemIndex;
208
219
  this.#submenuItemIndex = null;
220
+ this.#notifySelectionChange();
209
221
  }
210
222
  }
211
223
  }
package/src/terminal.ts CHANGED
@@ -164,6 +164,46 @@ export interface Terminal {
164
164
  get appearance(): TerminalAppearance | undefined;
165
165
  }
166
166
 
167
+ interface TerminalSizeStream {
168
+ columns?: number;
169
+ rows?: number;
170
+ getWindowSize?: () => [number, number] | number[];
171
+ }
172
+
173
+ function positiveDimension(value: unknown): number | undefined {
174
+ if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
175
+ const dimension = Math.trunc(value);
176
+ return dimension > 0 ? dimension : undefined;
177
+ }
178
+
179
+ export function resolveTerminalColumns(
180
+ stream: TerminalSizeStream = process.stdout,
181
+ envColumns: string | undefined = Bun.env.COLUMNS,
182
+ ): number {
183
+ try {
184
+ const windowSize = stream.getWindowSize?.();
185
+ const liveColumns = positiveDimension(windowSize?.[0]);
186
+ if (liveColumns !== undefined) return liveColumns;
187
+ } catch {
188
+ // Fall back below when the stream cannot report a live TTY size.
189
+ }
190
+ return positiveDimension(stream.columns) ?? positiveDimension(Number(envColumns)) ?? 80;
191
+ }
192
+
193
+ export function resolveTerminalRows(
194
+ stream: TerminalSizeStream = process.stdout,
195
+ envRows: string | undefined = Bun.env.LINES,
196
+ ): number {
197
+ try {
198
+ const windowSize = stream.getWindowSize?.();
199
+ const liveRows = positiveDimension(windowSize?.[1]);
200
+ if (liveRows !== undefined) return liveRows;
201
+ } catch {
202
+ // Fall back below when the stream cannot report a live TTY size.
203
+ }
204
+ return positiveDimension(stream.rows) ?? positiveDimension(Number(envRows)) ?? 24;
205
+ }
206
+
167
207
  function isWindowsSubsystemForLinux(): boolean {
168
208
  return process.platform === "linux" && (!!$env.WSL_DISTRO_NAME || !!$env.WSL_INTEROP);
169
209
  }
@@ -789,11 +829,11 @@ export class ProcessTerminal implements Terminal {
789
829
  }
790
830
 
791
831
  get columns(): number {
792
- return process.stdout.columns || Number(Bun.env.COLUMNS) || 80;
832
+ return resolveTerminalColumns();
793
833
  }
794
834
 
795
835
  get rows(): number {
796
- return process.stdout.rows || Number(Bun.env.LINES) || 24;
836
+ return resolveTerminalRows();
797
837
  }
798
838
 
799
839
  moveBy(lines: number): void {
package/src/tui.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import { performance } from "node:perf_hooks";
7
- import { $flag, getDebugLogPath } from "@sayknow-cli/utils";
7
+ import { $flag, getDebugLogPath, logger } from "@sayknow-cli/utils";
8
8
  import { getKeybindings } from "./keybindings";
9
9
  import { isKeyRelease } from "./keys";
10
10
  import { renderMetrics } from "./metrics";
@@ -257,7 +257,7 @@ export class Container implements Component {
257
257
  width = Math.max(1, width);
258
258
  const lines: string[] = [];
259
259
  for (const child of this.children) {
260
- const childLines = child.render(width);
260
+ const childLines = safeRenderComponent(child, width, "container-child");
261
261
  for (let i = 0; i < childLines.length; i++) {
262
262
  lines.push(childLines[i]);
263
263
  }
@@ -266,6 +266,40 @@ export class Container implements Component {
266
266
  }
267
267
  }
268
268
 
269
+ const MAX_REPORTED_RENDER_ERRORS = 200;
270
+ const reportedRenderErrors = new Set<string>();
271
+
272
+ /**
273
+ * Render a component's lines without letting a thrown error escape the frame.
274
+ *
275
+ * The TUI render loop ({@link TUI.#doRender}) runs inside a `nextTick`/`setTimeout`
276
+ * with no try/catch, and the process installs a fail-fast `uncaughtException`
277
+ * handler that exits. So a single component whose `render()` throws (e.g. a tool
278
+ * renderer fed an optional/undefined field) used to take down the whole app —
279
+ * fatal on whatever happened to trigger the frame (a keystroke, resize, or a
280
+ * command such as `/background`). Isolate the failure: log it once, emit a
281
+ * visible fallback line, and keep rendering the rest of the tree.
282
+ */
283
+ function safeRenderComponent(component: Component, width: number, where: string): string[] {
284
+ try {
285
+ return component.render(width);
286
+ } catch (err) {
287
+ const name = component?.constructor?.name ?? "Component";
288
+ const key = `${where}:${name}:${err instanceof Error ? err.message : String(err)}`;
289
+ if (!reportedRenderErrors.has(key)) {
290
+ if (reportedRenderErrors.size >= MAX_REPORTED_RENDER_ERRORS) reportedRenderErrors.clear();
291
+ reportedRenderErrors.add(key);
292
+ logger.error("Component render failed; emitting fallback line", {
293
+ where,
294
+ component: name,
295
+ error: err instanceof Error ? err.message : String(err),
296
+ stack: err instanceof Error ? err.stack : undefined,
297
+ });
298
+ }
299
+ return [`[render error: ${name}]`];
300
+ }
301
+ }
302
+
269
303
  type LineNormalizationCacheEntry = {
270
304
  normalized: string;
271
305
  terminated: string;
@@ -1071,7 +1105,7 @@ export class TUI extends Container {
1071
1105
  const { width, maxHeight } = this.#resolveOverlayLayout(options, 0, termWidth, termHeight);
1072
1106
 
1073
1107
  // Render component at calculated width
1074
- let overlayLines = component.render(width);
1108
+ let overlayLines = safeRenderComponent(component, width, "overlay");
1075
1109
 
1076
1110
  // Apply maxHeight if specified
1077
1111
  if (maxHeight !== undefined && overlayLines.length > maxHeight) {
@@ -1302,7 +1336,7 @@ export class TUI extends Container {
1302
1336
 
1303
1337
  let pinnedLineCount = 0;
1304
1338
  for (let i = pinnedStart; i < this.children.length; i++) {
1305
- pinnedLineCount += this.children[i].render(this.terminal.columns).length;
1339
+ pinnedLineCount += safeRenderComponent(this.children[i], this.terminal.columns, "pinned").length;
1306
1340
  }
1307
1341
 
1308
1342
  const blankRows = height - lines.length;
@@ -1,82 +0,0 @@
1
- export declare function getSlashCommandMatchRank(query: string, commandName: string): number;
2
- export interface AutocompleteItem {
3
- value: string;
4
- label: string;
5
- description?: string;
6
- /** Dim hint text shown inline after cursor when this item is selected */
7
- hint?: string;
8
- }
9
- type Awaitable<T> = T | Promise<T>;
10
- export interface SlashCommand {
11
- name: string;
12
- description?: string;
13
- argumentHint?: string;
14
- /**
15
- * Higher values surface first in autocomplete, ahead of fuzzy-score ordering.
16
- * Use this to pin first-class commands (e.g. bundled SKC skills) to the top.
17
- */
18
- priority?: number;
19
- getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
20
- /** Return inline hint text for the current argument state (shown as dim ghost text after cursor) */
21
- getInlineHint?(argumentText: string): string | null;
22
- }
23
- export interface AutocompleteProvider {
24
- /** Get autocomplete suggestions for current text/cursor position */
25
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
26
- items: AutocompleteItem[];
27
- prefix: string;
28
- } | null>;
29
- /** Apply the selected item and return new text + cursor position */
30
- applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
31
- lines: string[];
32
- cursorLine: number;
33
- cursorCol: number;
34
- onApplied?: () => void;
35
- };
36
- /** Get inline hint text to show as dim ghost text after the cursor */
37
- getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
38
- /** Synchronously try to complete a slash command at the start of a line (no async I/O). */
39
- /** Returns matched items and the full prefix, or null if not applicable. */
40
- trySyncSlashCompletion?(textBeforeCursor: string): {
41
- items: AutocompleteItem[];
42
- prefix: string;
43
- } | null;
44
- /**
45
- * Synchronously try to expand text immediately before the cursor (no async I/O).
46
- * Called after every single-character insert. Implementations MUST cheaply
47
- * early-return when the trailing context cannot trigger them.
48
- * Returns the number of characters to delete immediately before the cursor
49
- * and the literal string to insert in their place, or null to leave the
50
- * buffer untouched.
51
- */
52
- trySyncInlineReplace?(textBeforeCursor: string): {
53
- replaceLen: number;
54
- insert: string;
55
- } | null;
56
- }
57
- export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
58
- #private;
59
- constructor(commands?: (SlashCommand | AutocompleteItem)[], basePath?: string);
60
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
61
- items: AutocompleteItem[];
62
- prefix: string;
63
- } | null>;
64
- applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
65
- lines: string[];
66
- cursorLine: number;
67
- cursorCol: number;
68
- };
69
- invalidateDirCache(dir?: string): void;
70
- getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
71
- items: AutocompleteItem[];
72
- prefix: string;
73
- } | null>;
74
- shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
75
- /** Get inline hint text for slash commands with subcommand hints */
76
- getInlineHint(lines: string[], cursorLine: number, cursorCol: number): string | null;
77
- trySyncSlashCompletion(textBeforeCursor: string): {
78
- items: AutocompleteItem[];
79
- prefix: string;
80
- } | null;
81
- }
82
- export {};
@@ -1,26 +0,0 @@
1
- export type PasteResult = {
2
- handled: false;
3
- } | {
4
- handled: true;
5
- pasteContent?: string;
6
- remaining: string;
7
- };
8
- /**
9
- * Handles bracketed paste mode buffering for terminal input components.
10
- *
11
- * Bracketed paste mode wraps pasted content between start (\x1b[200~) and
12
- * end (\x1b[201~) markers, which may arrive split across multiple chunks.
13
- * This class buffers incoming data and assembles complete paste payloads.
14
- */
15
- export declare class BracketedPasteHandler {
16
- #private;
17
- /**
18
- * Process incoming terminal data for bracketed paste sequences.
19
- *
20
- * @returns `{ handled: false }` if the data contains no paste sequence and
21
- * should be processed normally. `{ handled: true }` if the data was
22
- * consumed by paste buffering — `pasteContent` is set when a complete
23
- * paste has been assembled; omitted when still buffering.
24
- */
25
- process(data: string): PasteResult;
26
- }
@@ -1,20 +0,0 @@
1
- import type { Component } from "../tui";
2
- /**
3
- * Box component - a container that applies padding and background to all children
4
- */
5
- export declare class Box implements Component {
6
- #private;
7
- children: Component[];
8
- constructor(paddingX?: number, paddingY?: number, bgFn?: (text: string) => string);
9
- addChild(component: Component): void;
10
- removeChild(component: Component): void;
11
- /** Remove a child without disposing it (for detach-then-readd reuse). */
12
- detachChild(component: Component): void;
13
- clear(): void;
14
- /** Remove all children without disposing them (for detach-then-readd reuse). */
15
- detachAll(): void;
16
- dispose(): void;
17
- setBgFn(bgFn?: (text: string) => string): void;
18
- invalidate(): void;
19
- render(width: number): string[];
20
- }
@@ -1,21 +0,0 @@
1
- import { Loader } from "./loader";
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
- }
@@ -1,112 +0,0 @@
1
- import type { AutocompleteProvider } from "../autocomplete";
2
- import type { SymbolTheme } from "../symbols";
3
- import { type Component, type Focusable } from "../tui";
4
- import { type SelectListTheme } from "./select-list";
5
- export interface EditorTheme {
6
- borderColor: (str: string) => string;
7
- selectList: SelectListTheme;
8
- symbols: SymbolTheme;
9
- editorPaddingX?: number;
10
- /** Style function for inline hint/ghost text (dim text after cursor) */
11
- hintStyle?: (text: string) => string;
12
- }
13
- export interface EditorTopBorder {
14
- /** The status content (already styled) */
15
- content: string;
16
- /** Visible width of the content */
17
- width: number;
18
- }
19
- export type EditorBorderStyle = "round" | "sharp";
20
- interface HistoryEntry {
21
- prompt: string;
22
- }
23
- interface HistoryStorage {
24
- add(prompt: string, cwd?: string): Promise<void>;
25
- getRecent(limit: number, cwd?: string): HistoryEntry[];
26
- }
27
- export declare class Editor implements Component, Focusable {
28
- #private;
29
- /** Focusable interface - set by TUI when focus changes */
30
- focused: boolean;
31
- /** When set, replaces the normal cursor glyph at end-of-text with this ANSI-styled string. */
32
- cursorOverride: string | undefined;
33
- /** Display width of the cursorOverride glyph (needed because override may contain ANSI escapes). */
34
- cursorOverrideWidth: number | undefined;
35
- borderColor: (str: string) => string;
36
- onAutocompleteUpdate?: () => void;
37
- onSubmit?: (text: string) => void;
38
- onAltEnter?: (text: string) => void;
39
- onChange?: (text: string) => void;
40
- onAutocompleteCancel?: () => void;
41
- onTabDeclined?: (text: string) => void;
42
- disableSubmit: boolean;
43
- constructor(theme: EditorTheme);
44
- setAutocompleteProvider(provider: AutocompleteProvider): void;
45
- getAutocompleteProvider(): AutocompleteProvider | undefined;
46
- /** Whether the autocomplete dropdown is currently open. */
47
- isAutocompleteOpen(): boolean;
48
- /**
49
- * Set custom content for the top border (e.g., status line).
50
- * Pass undefined to use the default plain border.
51
- */
52
- setTopBorder(content: EditorTopBorder | undefined): void;
53
- /**
54
- * Show or hide the editor border chrome.
55
- */
56
- setBorderVisible(borderVisible: boolean): void;
57
- setBorderStyle(borderStyle: EditorBorderStyle): void;
58
- setClosedBorderBox(closedBorderBox: boolean): void;
59
- setPromptGutter(promptGutter: string | undefined): void;
60
- setInputPrefix(inputPrefix: string | undefined): void;
61
- setPlaceholder(placeholder: string | undefined): void;
62
- /**
63
- * Get the available width for top border content given a total terminal width.
64
- * Accounts for the border characters and horizontal padding when visible.
65
- */
66
- getTopBorderAvailableWidth(terminalWidth: number): number;
67
- /**
68
- * Use the real terminal cursor instead of rendering a cursor glyph.
69
- */
70
- setUseTerminalCursor(useTerminalCursor: boolean): void;
71
- getUseTerminalCursor(): boolean;
72
- setMaxHeight(maxHeight: number | undefined): void;
73
- setPaddingX(paddingX: number): void;
74
- getAutocompleteMaxVisible(): number;
75
- setAutocompleteMaxVisible(maxVisible: number): void;
76
- setHistoryStorage(storage: HistoryStorage): void;
77
- /**
78
- * Add a prompt to history for up/down arrow navigation.
79
- * Called after successful submission.
80
- */
81
- addToHistory(text: string): void;
82
- invalidate(): void;
83
- render(width: number): string[];
84
- handleInput(data: string): void;
85
- /** Test-only seam: current wrap-cache entry count (memory-bound assertions). */
86
- get wrappedLineCacheSize(): number;
87
- getText(): string;
88
- /**
89
- * Get text with paste markers expanded to their actual content.
90
- * Use this when you need the full content (e.g., for external editor).
91
- */
92
- getExpandedText(): string;
93
- getLines(): string[];
94
- getCursor(): {
95
- line: number;
96
- col: number;
97
- };
98
- moveToLineStart(): void;
99
- moveToLineEnd(): void;
100
- moveToMessageStart(): void;
101
- moveToMessageEnd(): void;
102
- /**
103
- * Undo the last meaningful edit while ignoring transient text that is still present at the cursor.
104
- * Used for command-like autocomplete actions whose typed trigger should not count as the edit being undone.
105
- */
106
- undoPastTransientText(transientText: string): void;
107
- setText(text: string): void;
108
- /** Insert text at the current cursor position */
109
- insertText(text: string): void;
110
- isShowingAutocomplete(): boolean;
111
- }
112
- export {};
@@ -1,16 +0,0 @@
1
- import { type ImageDimensions } from "../terminal-capabilities";
2
- import type { Component } from "../tui";
3
- export interface ImageTheme {
4
- fallbackColor: (str: string) => string;
5
- }
6
- export interface ImageOptions {
7
- maxWidthCells?: number;
8
- maxHeightCells?: number;
9
- filename?: string;
10
- }
11
- export declare class Image implements Component {
12
- #private;
13
- constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
14
- invalidate(): void;
15
- render(width: number): string[];
16
- }
@@ -1,16 +0,0 @@
1
- import { type Component, type Focusable } from "../tui";
2
- /**
3
- * Input component - single-line text input with horizontal scrolling
4
- */
5
- export declare class Input implements Component, Focusable {
6
- #private;
7
- onSubmit?: (value: string) => void;
8
- onEscape?: () => void;
9
- /** Focusable interface - set by TUI when focus changes */
10
- focused: boolean;
11
- getValue(): string;
12
- setValue(value: string): void;
13
- handleInput(data: string): void;
14
- invalidate(): void;
15
- render(width: number): string[];
16
- }
@@ -1,14 +0,0 @@
1
- import type { TUI } from "../tui";
2
- import { Text } from "./text";
3
- export declare class Loader extends Text {
4
- #private;
5
- private spinnerColorFn;
6
- private messageColorFn;
7
- private message;
8
- constructor(ui: TUI, spinnerColorFn: (str: string) => string, messageColorFn: (str: string) => string, message?: string, spinnerFrames?: string[]);
9
- render(width: number): string[];
10
- start(): void;
11
- stop(): void;
12
- dispose(): void;
13
- setMessage(message: string): void;
14
- }
@@ -1,64 +0,0 @@
1
- import type { SymbolTheme } from "../symbols";
2
- import type { Component } from "../tui";
3
- /** Test/diagnostic seam: number of synchronous highlight invocations since the last reset. */
4
- export declare function getMarkdownHighlightCallCount(): number;
5
- export declare function resetMarkdownHighlightCallCount(): void;
6
- /** Drop all L2 cache entries. Call on theme change to prevent stale styled output. */
7
- export declare function clearRenderCache(): void;
8
- /**
9
- * Default text styling for markdown content.
10
- * Applied to all text unless overridden by markdown formatting.
11
- */
12
- export interface DefaultTextStyle {
13
- /** Foreground color function */
14
- color?: (text: string) => string;
15
- /** Background color function */
16
- bgColor?: (text: string) => string;
17
- /** Bold text */
18
- bold?: boolean;
19
- /** Italic text */
20
- italic?: boolean;
21
- /** Strikethrough text */
22
- strikethrough?: boolean;
23
- /** Underline text */
24
- underline?: boolean;
25
- }
26
- /**
27
- * Theme functions for markdown elements.
28
- * Each function takes text and returns styled text with ANSI codes.
29
- */
30
- export interface MarkdownTheme {
31
- heading: (text: string) => string;
32
- link: (text: string) => string;
33
- linkUrl: (text: string) => string;
34
- code: (text: string) => string;
35
- codeBlock: (text: string) => string;
36
- codeBlockBorder: (text: string) => string;
37
- quote: (text: string) => string;
38
- quoteBorder: (text: string) => string;
39
- hr: (text: string) => string;
40
- listBullet: (text: string) => string;
41
- bold: (text: string) => string;
42
- italic: (text: string) => string;
43
- strikethrough: (text: string) => string;
44
- underline: (text: string) => string;
45
- highlightCode?: (code: string, lang?: string) => string[];
46
- /**
47
- * Resolve a mermaid ASCII rendering by fenced block source text.
48
- * Return null to fall back to fenced code rendering.
49
- */
50
- resolveMermaidAscii?: (source: string) => string | null;
51
- symbols: SymbolTheme;
52
- }
53
- export declare class Markdown implements Component {
54
- #private;
55
- constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
56
- setText(text: string): void;
57
- invalidate(): void;
58
- render(width: number): string[];
59
- }
60
- /**
61
- * Render inline markdown (bold, italic, code, links, strikethrough) to a styled string.
62
- * Unlike the full Markdown component, this produces a single line with no block-level elements.
63
- */
64
- export declare function renderInlineMarkdown(text: string, mdTheme: MarkdownTheme, baseColor?: (t: string) => string): string;