Download packages/coding-agent/src/modes/interactive/components/extension-selector.ts from SaylorTwift/pi: direct link, hf CLI and curl.
- Browser
- Download file 3.37 kB
-
https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/src/modes/interactive/components/extension-selector.ts
- Command line
-
hf download hf://SaylorTwift/pi/packages/coding-agent/src/modes/interactive/components/extension-selector.ts
-
curl -L -o extension-selector.ts https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/src/modes/interactive/components/extension-selector.ts
3.37 kB
| /** | |
| * Generic selector component for extensions. | |
| * Displays a list of string options with keyboard navigation. | |
| */ | |
| import { Container, getKeybindings, Spacer, Text, type TUI } from "@earendil-works/pi-tui"; | |
| import { theme } from "../theme/theme.ts"; | |
| import { CountdownTimer } from "./countdown-timer.ts"; | |
| import { DynamicBorder } from "./dynamic-border.ts"; | |
| import { keyHint, rawKeyHint } from "./keybinding-hints.ts"; | |
| export interface ExtensionSelectorOptions { | |
| tui?: TUI; | |
| timeout?: number; | |
| onToggleToolsExpanded?: () => void; | |
| } | |
| export class ExtensionSelectorComponent extends Container { | |
| private options: string[]; | |
| private selectedIndex = 0; | |
| private listContainer: Container; | |
| private onSelectCallback: (option: string) => void; | |
| private onCancelCallback: () => void; | |
| private titleText: Text; | |
| private baseTitle: string; | |
| private countdown: CountdownTimer | undefined; | |
| private onToggleToolsExpanded: (() => void) | undefined; | |
| constructor( | |
| title: string, | |
| options: string[], | |
| onSelect: (option: string) => void, | |
| onCancel: () => void, | |
| opts?: ExtensionSelectorOptions, | |
| ) { | |
| super(); | |
| this.options = options; | |
| this.onSelectCallback = onSelect; | |
| this.onCancelCallback = onCancel; | |
| this.onToggleToolsExpanded = opts?.onToggleToolsExpanded; | |
| this.baseTitle = title; | |
| this.addChild(new DynamicBorder()); | |
| this.addChild(new Spacer(1)); | |
| this.titleText = new Text(theme.fg("accent", theme.bold(title)), 1, 0); | |
| this.addChild(this.titleText); | |
| this.addChild(new Spacer(1)); | |
| if (opts?.timeout && opts.timeout > 0 && opts.tui) { | |
| this.countdown = new CountdownTimer( | |
| opts.timeout, | |
| opts.tui, | |
| (s) => this.titleText.setText(theme.fg("accent", theme.bold(`${this.baseTitle} (${s}s)`))), | |
| () => this.onCancelCallback(), | |
| ); | |
| } | |
| this.listContainer = new Container(); | |
| this.addChild(this.listContainer); | |
| this.addChild(new Spacer(1)); | |
| this.addChild( | |
| new Text( | |
| rawKeyHint("↑↓", "navigate") + | |
| " " + | |
| keyHint("tui.select.confirm", "select") + | |
| " " + | |
| keyHint("tui.select.cancel", "cancel"), | |
| 1, | |
| 0, | |
| ), | |
| ); | |
| this.addChild(new Spacer(1)); | |
| this.addChild(new DynamicBorder()); | |
| this.updateList(); | |
| } | |
| private updateList(): void { | |
| this.listContainer.clear(); | |
| for (let i = 0; i < this.options.length; i++) { | |
| const isSelected = i === this.selectedIndex; | |
| const text = isSelected | |
| ? theme.fg("accent", "→ ") + theme.fg("accent", this.options[i]) | |
| : ` ${theme.fg("text", this.options[i])}`; | |
| this.listContainer.addChild(new Text(text, 1, 0)); | |
| } | |
| } | |
| handleInput(keyData: string): void { | |
| const kb = getKeybindings(); | |
| if (kb.matches(keyData, "app.tools.expand")) { | |
| this.onToggleToolsExpanded?.(); | |
| } else if (kb.matches(keyData, "tui.select.up") || keyData === "k") { | |
| this.selectedIndex = Math.max(0, this.selectedIndex - 1); | |
| this.updateList(); | |
| } else if (kb.matches(keyData, "tui.select.down") || keyData === "j") { | |
| this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1); | |
| this.updateList(); | |
| } else if (kb.matches(keyData, "tui.select.confirm") || keyData === "\n") { | |
| const selected = this.options[this.selectedIndex]; | |
| if (selected) this.onSelectCallback(selected); | |
| } else if (kb.matches(keyData, "tui.select.cancel")) { | |
| this.onCancelCallback(); | |
| } | |
| } | |
| dispose(): void { | |
| this.countdown?.dispose(); | |
| } | |
| } | |