From 45f90bc4b36231bd4c1781ce89d6742525ceb8ed Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 24 Sep 2025 04:30:29 -0400 Subject: [PATCH] tui: improve keybind handling and app exit cleanup --- .../src/cli/cmd/tui/context/keybind.tsx | 11 ++++++-- packages/opencode/src/cli/cmd/tui/session.tsx | 6 +++-- packages/opencode/src/cli/cmd/tui/tui.tsx | 25 ++++++------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx index 3785d2145..58fc438ea 100644 --- a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx @@ -6,7 +6,8 @@ import type { KeybindsConfig } from "@opencode-ai/sdk" import { createContext } from "solid-js" import type { ParsedKey } from "@opentui/core" import { createStore } from "solid-js/store" -import { useKeyboard } from "@opentui/solid" +import { useKeyboard, useRenderer } from "@opentui/solid" +import { Instance } from "../../../../project/instance" export function init() { const sync = useSync() @@ -22,13 +23,19 @@ export function init() { leader: false, }) - useKeyboard((evt) => { + const renderer = useRenderer() + useKeyboard(async (evt) => { if (result.match("leader", evt)) { setStore("leader", !store.leader) setTimeout(() => { setStore("leader", false) }, 2000) } + + if (result.match("app_exit", evt)) { + await Instance.disposeAll() + renderer.destroy() + } }) const result = { diff --git a/packages/opencode/src/cli/cmd/tui/session.tsx b/packages/opencode/src/cli/cmd/tui/session.tsx index 690b728f0..f051b10cc 100644 --- a/packages/opencode/src/cli/cmd/tui/session.tsx +++ b/packages/opencode/src/cli/cmd/tui/session.tsx @@ -27,6 +27,7 @@ import { useKeyboard, type BoxProps, type JSX } from "@opentui/solid" import { useSDK } from "./context/sdk" import { useCommandDialog } from "./component/dialog-command" import { Shimmer } from "./ui/shimmer" +import { useKeybind } from "./context/keybind" export function Session() { const route = useRouteData("session") @@ -39,9 +40,10 @@ export function Session() { createEffect(() => sync.session.sync(route.sessionID)) const sdk = useSDK() + const keybind = useKeybind() useKeyboard((evt) => { - if (evt.name === "pageup") scroll.scrollBy(-scroll.height / 2) - if (evt.name === "pagedown") scroll.scrollBy(scroll.height / 2) + if (keybind.match("messages_page_up", evt)) scroll.scrollBy(-scroll.height / 2) + if (keybind.match("messages_page_down", evt)) scroll.scrollBy(scroll.height / 2) if (evt.name === "escape") sdk.session.abort({ path: { diff --git a/packages/opencode/src/cli/cmd/tui/tui.tsx b/packages/opencode/src/cli/cmd/tui/tui.tsx index 96c295d80..31a43f58e 100644 --- a/packages/opencode/src/cli/cmd/tui/tui.tsx +++ b/packages/opencode/src/cli/cmd/tui/tui.tsx @@ -13,12 +13,10 @@ import { SyncProvider } from "./context/sync" import { LocalProvider, useLocal } from "./context/local" import { DialogModel } from "./component/dialog-model" import { Session } from "./session" -import { Instance } from "../../../project/instance" -import { EventLoop } from "../../../util/eventloop" import { CommandProvider, useCommandDialog } from "./component/dialog-command" import { DialogAgent } from "./component/dialog-agent" import { DialogSessionList } from "./component/dialog-session-list" -import { KeybindProvider } from "./context/keybind" +import { KeybindProvider, useKeybind } from "./context/keybind" export const TuiCommand = cmd({ command: "$0 [project]", @@ -67,15 +65,6 @@ export const TuiCommand = cmd({ handler: async () => { await render( () => { - const renderer = useRenderer() - useKeyboard(async (evt) => { - if (!evt.name) return - if (evt.name === "c" && evt.ctrl) { - await Instance.disposeAll() - renderer.destroy() - await EventLoop.wait() - } - }) return ( @@ -113,12 +102,16 @@ function App() { const dialog = useDialog() const local = useLocal() const command = useCommandDialog() + const keybind = useKeybind() useKeyboard(async (evt) => { - if (evt.name === "tab") { - local.agent.move(evt.shift ? -1 : 1) + if (keybind.match("agent_cycle", evt)) { + local.agent.move(1) return } + if (keybind.match("agent_cycle_reverse", evt)) { + local.agent.move(-1) + } if (evt.meta && evt.name === "t") { renderer.toggleDebugOverlay() @@ -129,10 +122,6 @@ function App() { renderer.console.toggle() return } - if (evt.meta && evt.name === "m") { - dialog.replace(() => ) - return - } }) createEffect(() => {