diff --git a/packages/coding-agent/src/modes/interactive/components/hook-editor.ts b/packages/coding-agent/src/modes/interactive/components/hook-editor.ts index 6486f140..c07a634c 100644 --- a/packages/coding-agent/src/modes/interactive/components/hook-editor.ts +++ b/packages/coding-agent/src/modes/interactive/components/hook-editor.ts @@ -7,7 +7,7 @@ import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { Container, Editor, matchesKey, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; +import { Container, Editor, getEditorKeybindings, matchesKey, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; import { getEditorTheme, theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -67,13 +67,14 @@ export class HookEditorComponent extends Container { return; } + const kb = getEditorKeybindings(); // Escape or Ctrl+C to cancel - if (matchesKey(keyData, "escape") || matchesKey(keyData, "ctrl+c")) { + if (kb.matches(keyData, "selectCancel")) { this.onCancelCallback(); return; } - // Ctrl+G for external editor + // Ctrl+G for external editor (keep matchesKey for this app-specific action) if (matchesKey(keyData, "ctrl+g")) { this.openExternalEditor(); return; diff --git a/packages/coding-agent/src/modes/interactive/components/hook-input.ts b/packages/coding-agent/src/modes/interactive/components/hook-input.ts index e4705c00..0f56fc49 100644 --- a/packages/coding-agent/src/modes/interactive/components/hook-input.ts +++ b/packages/coding-agent/src/modes/interactive/components/hook-input.ts @@ -2,7 +2,7 @@ * Simple text input component for hooks. */ -import { Container, Input, matchesKey, Spacer, Text } from "@mariozechner/pi-tui"; +import { Container, getEditorKeybindings, Input, Spacer, Text } from "@mariozechner/pi-tui"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -46,14 +46,15 @@ export class HookInputComponent extends Container { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Enter - if (matchesKey(keyData, "enter") || keyData === "\n") { + if (kb.matches(keyData, "selectConfirm") || keyData === "\n") { this.onSubmitCallback(this.input.getValue()); return; } // Escape or Ctrl+C to cancel - if (matchesKey(keyData, "escape") || matchesKey(keyData, "ctrl+c")) { + if (kb.matches(keyData, "selectCancel")) { this.onCancelCallback(); return; } diff --git a/packages/coding-agent/src/modes/interactive/components/hook-selector.ts b/packages/coding-agent/src/modes/interactive/components/hook-selector.ts index 9a35cd14..756d463a 100644 --- a/packages/coding-agent/src/modes/interactive/components/hook-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/hook-selector.ts @@ -3,7 +3,7 @@ * Displays a list of string options with keyboard navigation. */ -import { Container, matchesKey, Spacer, Text } from "@mariozechner/pi-tui"; +import { Container, getEditorKeybindings, Spacer, Text } from "@mariozechner/pi-tui"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -66,25 +66,26 @@ export class HookSelectorComponent extends Container { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Up arrow or k - if (matchesKey(keyData, "up") || keyData === "k") { + if (kb.matches(keyData, "selectUp") || keyData === "k") { this.selectedIndex = Math.max(0, this.selectedIndex - 1); this.updateList(); } // Down arrow or j - else if (matchesKey(keyData, "down") || keyData === "j") { + else if (kb.matches(keyData, "selectDown") || keyData === "j") { this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1); this.updateList(); } // Enter - else if (matchesKey(keyData, "enter") || keyData === "\n") { + else if (kb.matches(keyData, "selectConfirm") || keyData === "\n") { const selected = this.options[this.selectedIndex]; if (selected) { this.onSelectCallback(selected); } } // Escape or Ctrl+C - else if (matchesKey(keyData, "escape") || matchesKey(keyData, "ctrl+c")) { + else if (kb.matches(keyData, "selectCancel")) { this.onCancelCallback(); } } diff --git a/packages/coding-agent/src/modes/interactive/components/model-selector.ts b/packages/coding-agent/src/modes/interactive/components/model-selector.ts index 553fa285..4b1e5778 100644 --- a/packages/coding-agent/src/modes/interactive/components/model-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/model-selector.ts @@ -1,5 +1,5 @@ import { type Model, modelsAreEqual } from "@mariozechner/pi-ai"; -import { Container, Input, matchesKey, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; +import { Container, getEditorKeybindings, Input, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; import type { ModelRegistry } from "../../../core/model-registry.js"; import type { SettingsManager } from "../../../core/settings-manager.js"; import { fuzzyFilter } from "../../../utils/fuzzy.js"; @@ -205,27 +205,28 @@ export class ModelSelectorComponent extends Container { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Up arrow - wrap to bottom when at top - if (matchesKey(keyData, "up")) { + if (kb.matches(keyData, "selectUp")) { if (this.filteredModels.length === 0) return; this.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1; this.updateList(); } // Down arrow - wrap to top when at bottom - else if (matchesKey(keyData, "down")) { + else if (kb.matches(keyData, "selectDown")) { if (this.filteredModels.length === 0) return; this.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1; this.updateList(); } // Enter - else if (matchesKey(keyData, "enter")) { + else if (kb.matches(keyData, "selectConfirm")) { const selectedModel = this.filteredModels[this.selectedIndex]; if (selectedModel) { this.handleSelect(selectedModel.model); } } // Escape or Ctrl+C - else if (matchesKey(keyData, "escape") || matchesKey(keyData, "ctrl+c")) { + else if (kb.matches(keyData, "selectCancel")) { this.onCancelCallback(); } // Pass everything else to search input diff --git a/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts b/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts index fcee684c..5b29281d 100644 --- a/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts @@ -1,5 +1,5 @@ import { getOAuthProviders, type OAuthProviderInfo } from "@mariozechner/pi-ai"; -import { Container, matchesKey, Spacer, TruncatedText } from "@mariozechner/pi-tui"; +import { Container, getEditorKeybindings, Spacer, TruncatedText } from "@mariozechner/pi-tui"; import type { AuthStorage } from "../../../core/auth-storage.js"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -95,25 +95,26 @@ export class OAuthSelectorComponent extends Container { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Up arrow - if (matchesKey(keyData, "up")) { + if (kb.matches(keyData, "selectUp")) { this.selectedIndex = Math.max(0, this.selectedIndex - 1); this.updateList(); } // Down arrow - else if (matchesKey(keyData, "down")) { + else if (kb.matches(keyData, "selectDown")) { this.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1); this.updateList(); } // Enter - else if (matchesKey(keyData, "enter")) { + else if (kb.matches(keyData, "selectConfirm")) { const selectedProvider = this.allProviders[this.selectedIndex]; if (selectedProvider?.available) { this.onSelectCallback(selectedProvider.id); } } // Escape or Ctrl+C - else if (matchesKey(keyData, "escape") || matchesKey(keyData, "ctrl+c")) { + else if (kb.matches(keyData, "selectCancel")) { this.onCancelCallback(); } } diff --git a/packages/coding-agent/src/modes/interactive/components/session-selector.ts b/packages/coding-agent/src/modes/interactive/components/session-selector.ts index 4913fa51..317c72a5 100644 --- a/packages/coding-agent/src/modes/interactive/components/session-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/session-selector.ts @@ -1,4 +1,12 @@ -import { type Component, Container, Input, matchesKey, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui"; +import { + type Component, + Container, + getEditorKeybindings, + Input, + Spacer, + Text, + truncateToWidth, +} from "@mariozechner/pi-tui"; import type { SessionInfo } from "../../../core/session-manager.js"; import { fuzzyFilter } from "../../../utils/fuzzy.js"; import { theme } from "../theme/theme.js"; @@ -114,31 +122,28 @@ class SessionList implements Component { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Up arrow - if (matchesKey(keyData, "up")) { + if (kb.matches(keyData, "selectUp")) { this.selectedIndex = Math.max(0, this.selectedIndex - 1); } // Down arrow - else if (matchesKey(keyData, "down")) { + else if (kb.matches(keyData, "selectDown")) { this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1); } // Enter - else if (matchesKey(keyData, "enter")) { + else if (kb.matches(keyData, "selectConfirm")) { const selected = this.filteredSessions[this.selectedIndex]; if (selected && this.onSelect) { this.onSelect(selected.path); } } // Escape - cancel - else if (matchesKey(keyData, "escape")) { + else if (kb.matches(keyData, "selectCancel")) { if (this.onCancel) { this.onCancel(); } } - // Ctrl+C - exit - else if (matchesKey(keyData, "ctrl+c")) { - this.onExit(); - } // Pass everything else to search input else { this.searchInput.handleInput(keyData); diff --git a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts index 402cd3e3..6ad99c1a 100644 --- a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts @@ -1,6 +1,7 @@ import { type Component, Container, + getEditorKeybindings, Input, matchesKey, Spacer, @@ -655,30 +656,29 @@ class TreeList implements Component { } handleInput(keyData: string): void { - if (matchesKey(keyData, "up")) { + const kb = getEditorKeybindings(); + if (kb.matches(keyData, "selectUp")) { this.selectedIndex = this.selectedIndex === 0 ? this.filteredNodes.length - 1 : this.selectedIndex - 1; - } else if (matchesKey(keyData, "down")) { + } else if (kb.matches(keyData, "selectDown")) { this.selectedIndex = this.selectedIndex === this.filteredNodes.length - 1 ? 0 : this.selectedIndex + 1; - } else if (matchesKey(keyData, "left")) { + } else if (kb.matches(keyData, "cursorLeft")) { // Page up this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines); - } else if (matchesKey(keyData, "right")) { + } else if (kb.matches(keyData, "cursorRight")) { // Page down this.selectedIndex = Math.min(this.filteredNodes.length - 1, this.selectedIndex + this.maxVisibleLines); - } else if (matchesKey(keyData, "enter")) { + } else if (kb.matches(keyData, "selectConfirm")) { const selected = this.filteredNodes[this.selectedIndex]; if (selected && this.onSelect) { this.onSelect(selected.node.entry.id); } - } else if (matchesKey(keyData, "escape")) { + } else if (kb.matches(keyData, "selectCancel")) { if (this.searchQuery) { this.searchQuery = ""; this.applyFilter(); } else { this.onCancel?.(); } - } else if (matchesKey(keyData, "ctrl+c")) { - this.onCancel?.(); } else if (matchesKey(keyData, "shift+ctrl+o")) { // Cycle filter backwards const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"]; @@ -691,7 +691,7 @@ class TreeList implements Component { const currentIndex = modes.indexOf(this.filterMode); this.filterMode = modes[(currentIndex + 1) % modes.length]; this.applyFilter(); - } else if (matchesKey(keyData, "backspace")) { + } else if (kb.matches(keyData, "deleteCharBackward")) { if (this.searchQuery.length > 0) { this.searchQuery = this.searchQuery.slice(0, -1); this.applyFilter(); @@ -759,10 +759,11 @@ class LabelInput implements Component { } handleInput(keyData: string): void { - if (matchesKey(keyData, "enter")) { + const kb = getEditorKeybindings(); + if (kb.matches(keyData, "selectConfirm")) { const value = this.input.getValue().trim(); this.onSubmit?.(this.entryId, value || undefined); - } else if (matchesKey(keyData, "escape")) { + } else if (kb.matches(keyData, "selectCancel")) { this.onCancel?.(); } else { this.input.handleInput(keyData); diff --git a/packages/coding-agent/src/modes/interactive/components/user-message-selector.ts b/packages/coding-agent/src/modes/interactive/components/user-message-selector.ts index 6b97c6e9..c38de14a 100644 --- a/packages/coding-agent/src/modes/interactive/components/user-message-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/user-message-selector.ts @@ -1,4 +1,4 @@ -import { type Component, Container, matchesKey, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui"; +import { type Component, Container, getEditorKeybindings, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -78,29 +78,24 @@ class UserMessageList implements Component { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Up arrow - go to previous (older) message, wrap to bottom when at top - if (matchesKey(keyData, "up")) { + if (kb.matches(keyData, "selectUp")) { this.selectedIndex = this.selectedIndex === 0 ? this.messages.length - 1 : this.selectedIndex - 1; } // Down arrow - go to next (newer) message, wrap to top when at bottom - else if (matchesKey(keyData, "down")) { + else if (kb.matches(keyData, "selectDown")) { this.selectedIndex = this.selectedIndex === this.messages.length - 1 ? 0 : this.selectedIndex + 1; } // Enter - select message and branch - else if (matchesKey(keyData, "enter")) { + else if (kb.matches(keyData, "selectConfirm")) { const selected = this.messages[this.selectedIndex]; if (selected && this.onSelect) { this.onSelect(selected.id); } } // Escape - cancel - else if (matchesKey(keyData, "escape")) { - if (this.onCancel) { - this.onCancel(); - } - } - // Ctrl+C - cancel - else if (matchesKey(keyData, "ctrl+c")) { + else if (kb.matches(keyData, "selectCancel")) { if (this.onCancel) { this.onCancel(); } diff --git a/packages/tui/src/components/cancellable-loader.ts b/packages/tui/src/components/cancellable-loader.ts index f8c76193..506b763d 100644 --- a/packages/tui/src/components/cancellable-loader.ts +++ b/packages/tui/src/components/cancellable-loader.ts @@ -1,4 +1,4 @@ -import { matchesKey } from "../keys.js"; +import { getEditorKeybindings } from "../keybindings.js"; import { Loader } from "./loader.js"; /** @@ -27,7 +27,8 @@ export class CancellableLoader extends Loader { } handleInput(data: string): void { - if (matchesKey(data, "escape")) { + const kb = getEditorKeybindings(); + if (kb.matches(data, "selectCancel")) { this.abortController.abort(); this.onAbort?.(); } diff --git a/packages/tui/src/components/select-list.ts b/packages/tui/src/components/select-list.ts index 31489187..06352ac4 100644 --- a/packages/tui/src/components/select-list.ts +++ b/packages/tui/src/components/select-list.ts @@ -1,4 +1,4 @@ -import { matchesKey } from "../keys.js"; +import { getEditorKeybindings } from "../keybindings.js"; import type { Component } from "../tui.js"; import { truncateToWidth } from "../utils.js"; @@ -145,25 +145,26 @@ export class SelectList implements Component { } handleInput(keyData: string): void { + const kb = getEditorKeybindings(); // Up arrow - wrap to bottom when at top - if (matchesKey(keyData, "up")) { + if (kb.matches(keyData, "selectUp")) { this.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1; this.notifySelectionChange(); } // Down arrow - wrap to top when at bottom - else if (matchesKey(keyData, "down")) { + else if (kb.matches(keyData, "selectDown")) { this.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1; this.notifySelectionChange(); } // Enter - else if (matchesKey(keyData, "enter")) { + else if (kb.matches(keyData, "selectConfirm")) { const selectedItem = this.filteredItems[this.selectedIndex]; if (selectedItem && this.onSelect) { this.onSelect(selectedItem); } } // Escape or Ctrl+C - else if (matchesKey(keyData, "escape") || matchesKey(keyData, "ctrl+c")) { + else if (kb.matches(keyData, "selectCancel")) { if (this.onCancel) { this.onCancel(); } diff --git a/packages/tui/src/components/settings-list.ts b/packages/tui/src/components/settings-list.ts index 2452a3ea..051b6502 100644 --- a/packages/tui/src/components/settings-list.ts +++ b/packages/tui/src/components/settings-list.ts @@ -1,4 +1,4 @@ -import { matchesKey } from "../keys.js"; +import { getEditorKeybindings } from "../keybindings.js"; import type { Component } from "../tui.js"; import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils.js"; @@ -145,13 +145,14 @@ export class SettingsList implements Component { } // Main list input handling - if (matchesKey(data, "up")) { + const kb = getEditorKeybindings(); + if (kb.matches(data, "selectUp")) { this.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1; - } else if (matchesKey(data, "down")) { + } else if (kb.matches(data, "selectDown")) { this.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1; - } else if (matchesKey(data, "enter") || data === " ") { + } else if (kb.matches(data, "selectConfirm") || data === " ") { this.activateItem(); - } else if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { + } else if (kb.matches(data, "selectCancel")) { this.onCancel(); } }