diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 5214b0c1a9a..948a67dccc8 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -19,7 +19,7 @@ import { DialogHelp } from "./ui/dialog-help" import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command" import { DialogAgent } from "@tui/component/dialog-agent" import { DialogSessionList } from "@tui/component/dialog-session-list" -import { KeybindProvider } from "@tui/context/keybind" +import { KeybindProvider, useKeybind } from "@tui/context/keybind" import { ThemeProvider, useTheme } from "@tui/context/theme" import { Home } from "@tui/routes/home" import { Session } from "@tui/routes/session" @@ -35,6 +35,7 @@ import { Provider } from "@/provider/provider" import { ArgsProvider, useArgs, type Args } from "./context/args" import open from "open" import { PromptRefProvider, usePromptRef } from "./context/prompt" +import { Keybind } from "@/util/keybind" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -178,6 +179,7 @@ function App() { const sync = useSync() const exit = useExit() const promptRef = usePromptRef() + const keybind = useKeybind() // Wire up console copy-to-clipboard via opentui's onCopySelection callback renderer.console.onCopySelection = async (text: string) => { @@ -499,6 +501,57 @@ function App() { }, ]) + // Handle custom command keybinds + useKeyboard((evt) => { + if (command.suspended()) return + if (dialog.stack.length > 0) return + if (evt.defaultPrevented) return + + const keybinds = sync.data.config.keybinds ?? {} + for (const [key, value] of Object.entries(keybinds)) { + if (!key.startsWith("/")) continue + if (!value) continue + + const commandName = key.slice(1) + const commandKeybinds = Keybind.parse(value) + const parsed = keybind.parse(evt) + + for (const kb of commandKeybinds) { + if (Keybind.match(kb, parsed)) { + evt.preventDefault() + + // Find the command to verify it exists + const cmd = sync.data.command.find((c) => c.name === commandName) + if (!cmd) { + toast.show({ + variant: "error", + message: `Command not found: ${commandName}`, + duration: 3000, + }) + return + } + + // Preserve existing prompt text as command arguments + const current = promptRef.current + if (current) { + const existingInput = current.current.input.trim() + const commandInput = existingInput + ? `/${commandName} ${existingInput}` + : `/${commandName}` + + current.set({ + input: commandInput, + parts: current.current.parts, + }) + current.submit() + } + + return + } + } + } + }) + createEffect(() => { const currentModel = local.model.current() if (!currentModel) return diff --git a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx index 4c82e594c3e..10766115a8c 100644 --- a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx @@ -15,7 +15,7 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex const keybinds = createMemo(() => { return pipe( sync.data.config.keybinds ?? {}, - mapValues((value) => Keybind.parse(value)), + mapValues((value) => (value ? Keybind.parse(value) : [])), ) }) const [store, setStore] = createStore({ diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 66f42e5a851..ac63427075e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -576,7 +576,7 @@ export namespace Config { terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"), tips_toggle: z.string().optional().default("h").describe("Toggle tips on home screen"), }) - .strict() + .catchall(z.string()) .meta({ ref: "KeybindsConfig", }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 5c4cc69423d..e795478da87 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1149,6 +1149,7 @@ export type KeybindsConfig = { * Toggle tips on home screen */ tips_toggle?: string + [key: string]: string | undefined } /**