introduce opentui keymap as sole key/cmd engine (#26053)

This commit is contained in:
Sebastian
2026-05-07 20:35:31 +02:00
committed by GitHub
parent 474e311f6f
commit 98f5e6e713
67 changed files with 3858 additions and 2977 deletions

View File

@@ -1,10 +1,27 @@
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
import { createMemo, Show } from "solid-js"
import { Tips } from "./tips-view"
import { useBindings } from "../../keymap"
const id = "internal:home-tips"
function View(props: { show: boolean; connected: boolean }) {
function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connected: boolean }) {
useBindings(() => ({
commands: [
{
name: "tips.toggle",
title: props.hidden ? "Show tips" : "Hide tips",
category: "System",
namespace: "palette",
run() {
props.api.kv.set("tips_hidden", !props.api.kv.get("tips_hidden", false))
props.api.ui.dialog.clear()
},
},
],
bindings: props.api.tuiConfig.keymap.sections.home_tips,
}))
return (
<box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}>
<Show when={props.show}>
@@ -15,20 +32,6 @@ function View(props: { show: boolean; connected: boolean }) {
}
const tui: TuiPlugin = async (api) => {
api.command.register(() => [
{
title: api.kv.get("tips_hidden", false) ? "Show tips" : "Hide tips",
value: "tips.toggle",
keybind: "tips_toggle",
category: "System",
hidden: api.route.current.name !== "home",
onSelect() {
api.kv.set("tips_hidden", !api.kv.get("tips_hidden", false))
api.ui.dialog.clear()
},
},
])
api.slots.register({
order: 100,
slots: {
@@ -41,7 +44,7 @@ const tui: TuiPlugin = async (api) => {
),
)
const show = createMemo(() => (!first() || !connected()) && !hidden())
return <View show={show()} connected={connected()} />
return <View api={api} hidden={hidden()} show={show()} connected={connected()} />
},
},
})

View File

@@ -1,14 +1,11 @@
import { Keybind } from "@/util/keybind"
import type { TuiPlugin, TuiPluginApi, TuiPluginModule, TuiPluginStatus } from "@opencode-ai/plugin/tui"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
import { useTerminalDimensions } from "@opentui/solid"
import { fileURLToPath } from "url"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import { Show, createEffect, createMemo, createSignal } from "solid-js"
import { useBindings } from "../../keymap"
const id = "internal:plugin-manager"
const key = Keybind.parse("space").at(0)
const add = Keybind.parse("shift+i").at(0)
const tab = Keybind.parse("tab").at(0)
function state(api: TuiPluginApi, item: TuiPluginStatus) {
if (!item.enabled) {
@@ -41,13 +38,10 @@ function Install(props: { api: TuiPluginApi }) {
const [global, setGlobal] = createSignal(false)
const [busy, setBusy] = createSignal(false)
useKeyboard((evt) => {
if (evt.name !== "tab") return
evt.preventDefault()
evt.stopPropagation()
if (busy()) return
setGlobal((x) => !x)
})
useBindings(() => ({
enabled: !busy(),
bindings: [{ key: "tab", cmd: () => setGlobal((value) => !value) }],
}))
return (
<props.api.ui.DialogPrompt
@@ -62,7 +56,7 @@ function Install(props: { api: TuiPluginApi }) {
{global() ? "global" : "local"}
</text>
<Show when={!busy()}>
<text fg={props.api.theme.current.textMuted}>({Keybind.toString(tab)} toggle)</text>
<text fg={props.api.theme.current.textMuted}>(tab toggle)</text>
</Show>
</box>
)}
@@ -209,10 +203,10 @@ function View(props: { api: TuiPluginApi }) {
options={rows()}
current={cur()}
onMove={(item) => setCur(item.value)}
keybind={[
actions={[
{
title: "toggle",
keybind: key,
command: "dialog.action.toggle",
disabled: lock(),
onTrigger: (item) => {
setCur(item.value)
@@ -221,13 +215,14 @@ function View(props: { api: TuiPluginApi }) {
},
{
title: "install",
keybind: add,
command: "plugin.dialog.install",
disabled: lock(),
onTrigger: () => {
showInstall(props.api)
},
},
]}
bindings={props.api.tuiConfig.keymap.pick("plugins", ["plugin.dialog.install"])}
onSelect={(item) => {
setCur(item.value)
flip(item.value)
@@ -241,25 +236,29 @@ function show(api: TuiPluginApi) {
}
const tui: TuiPlugin = async (api) => {
api.command.register(() => [
{
title: "Plugins",
value: "plugins.list",
keybind: "plugin_manager",
category: "System",
onSelect() {
show(api)
api.keymap.registerLayer({
commands: [
{
name: "plugins.list",
title: "Plugins",
category: "System",
namespace: "palette",
run() {
show(api)
},
},
},
{
title: "Install plugin",
value: "plugins.install",
category: "System",
onSelect() {
showInstall(api)
{
name: "plugins.install",
title: "Install plugin",
category: "System",
namespace: "palette",
run() {
showInstall(api)
},
},
},
])
],
bindings: api.tuiConfig.keymap.omit("plugins", ["plugin.dialog.install"]),
})
}
const plugin: TuiPluginModule & { id: string } = {

View File

@@ -4,8 +4,9 @@ import { SplitBorder } from "@tui/component/border"
import { Spinner } from "@tui/component/spinner"
import { useTheme } from "@tui/context/theme"
import { useLocal } from "@tui/context/local"
import { useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
import { TextAttributes, type BoxRenderable, type SyntaxStyle } from "@opentui/core"
import { useBindings } from "../../keymap"
import { Locale } from "@/util/locale"
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
import path from "path"
@@ -53,12 +54,16 @@ function View(props: { api: TuiPluginApi; sessionID: string }) {
void sync.session.message.sync(props.sessionID)
})
useKeyboard((event) => {
if (event.name !== "escape") return
event.preventDefault()
event.stopPropagation()
props.api.route.navigate("session", { sessionID: props.sessionID })
})
useBindings(() => ({
bindings: [
{
key: "escape",
cmd() {
props.api.route.navigate("session", { sessionID: props.sessionID })
},
},
],
}))
return (
<box width={dimensions().width} height={dimensions().height} backgroundColor={theme.background}>
@@ -1113,21 +1118,24 @@ const tui: TuiPlugin = async (api) => {
},
])
api.command.register(() => [
{
title: "View v2 session messages",
value: route,
category: "Debug",
suggested: api.route.current.name === "session",
enabled: api.route.current.name === "session",
onSelect() {
const sessionID = currentSessionID(api)
if (!sessionID) return
api.route.navigate(route, { sessionID })
api.ui.dialog.clear()
api.keymap.registerLayer({
commands: [
{
name: route,
title: "View v2 session messages",
category: "Debug",
namespace: "palette",
suggested: () => api.route.current.name === "session",
enabled: () => api.route.current.name === "session",
run() {
const sessionID = currentSessionID(api)
if (!sessionID) return
api.route.navigate(route, { sessionID })
api.ui.dialog.clear()
},
},
},
])
],
})
}
const plugin: TuiPluginModule & { id: string } = {