diff --git a/.opencode/plugins/tui-smoke.tsx b/.opencode/plugins/tui-smoke.tsx index 85d12343a3..29c4f2c6c2 100644 --- a/.opencode/plugins/tui-smoke.tsx +++ b/.opencode/plugins/tui-smoke.tsx @@ -3,6 +3,8 @@ import mytheme from "../themes/mytheme.json" with { type: "json" } import { useKeyboard, useTerminalDimensions } from "@opentui/solid" import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui" +const tabs = ["overview", "counter", "help"] + const pick = (value: unknown, fallback: string) => { if (typeof value !== "string") return fallback if (!value.trim()) return fallback @@ -26,20 +28,39 @@ const ui = { accent: "#5f87ff", } +const parse = (data: Record | undefined) => { + const tab = typeof data?.tab === "number" ? data.tab : 0 + const count = typeof data?.count === "number" ? data.count : 0 + const source = typeof data?.source === "string" ? data.source : "unknown" + return { + tab, + count, + source, + } +} + const active = (api: TuiApi, id: string) => { const route = api.route.data return route.type === "plugin" && route.id === id } +const merge = (api: TuiApi, patch: Record) => { + const route = api.route.data + if (route.type !== "plugin") return patch + return { ...(route.data ?? {}), ...patch } +} + const open = (api: TuiApi, input: ReturnType, source: string) => { console.log("[smoke] open", { route: input.route, source }) - api.route.plugin(input.route, { source }) + api.route.plugin(input.route, merge(api, { source })) api.dialog.clear() } -const Modal = (props: { api: TuiApi; input: ReturnType }) => { - const dim = useTerminalDimensions() +const patch = (api: TuiApi, input: ReturnType, value: Record) => { + api.route.plugin(input.route, merge(api, value)) +} +const Modal = (props: { api: TuiApi; input: ReturnType }) => { useKeyboard((evt) => { if (evt.defaultPrevented) return if (evt.name !== "return") return @@ -51,45 +72,27 @@ const Modal = (props: { api: TuiApi; input: ReturnType }) => { }) return ( - - - - - {props.input.label} modal - - Plugin commands and keybinds work without host internals - - {props.api.keybind.print(props.input.modal)} open modal · {props.api.keybind.print(props.input.screen)} open - screen - - enter opens screen · esc closes - - open(props.api, props.input, "modal")} - backgroundColor={ui.accent} - paddingLeft={1} - paddingRight={1} - > - open screen - - props.api.dialog.clear()} - backgroundColor={ui.border} - paddingLeft={1} - paddingRight={1} - > - cancel - - + + + {props.input.label} modal + + Plugin commands and keybinds work without host internals + + {props.api.keybind.print(props.input.modal)} open modal · {props.api.keybind.print(props.input.screen)} open + screen + + enter opens screen · esc closes + + open(props.api, props.input, "modal")} + backgroundColor={ui.accent} + paddingLeft={1} + paddingRight={1} + > + open screen + + props.api.dialog.clear()} backgroundColor={ui.border} paddingLeft={1} paddingRight={1}> + cancel @@ -98,9 +101,14 @@ const Modal = (props: { api: TuiApi; input: ReturnType }) => { const Screen = (props: { api: TuiApi; input: ReturnType; data?: Record }) => { const dim = useTerminalDimensions() + const value = parse(props.data) useKeyboard((evt) => { if (evt.defaultPrevented) return + if (!active(props.api, props.input.route)) return + + const state = parse(props.api.route.data.type === "plugin" ? props.api.route.data.data : undefined) + if (evt.name === "escape" || (evt.ctrl && evt.name === "h")) { console.log("[smoke] screen key", { key: evt.name, ctrl: evt.ctrl }) evt.preventDefault() @@ -109,6 +117,38 @@ const Screen = (props: { api: TuiApi; input: ReturnType; data?: Reco return } + if (evt.name === "left") { + console.log("[smoke] screen key", { key: evt.name }) + evt.preventDefault() + evt.stopPropagation() + patch(props.api, props.input, { tab: (state.tab - 1 + tabs.length) % tabs.length }) + return + } + + if (evt.name === "right") { + console.log("[smoke] screen key", { key: evt.name }) + evt.preventDefault() + evt.stopPropagation() + patch(props.api, props.input, { tab: (state.tab + 1) % tabs.length }) + return + } + + if (evt.name === "up" || (evt.ctrl && evt.name === "up")) { + console.log("[smoke] screen key", { key: evt.name, ctrl: evt.ctrl }) + evt.preventDefault() + evt.stopPropagation() + patch(props.api, props.input, { count: state.count + 1 }) + return + } + + if (evt.name === "down" || (evt.ctrl && evt.name === "down")) { + console.log("[smoke] screen key", { key: evt.name, ctrl: evt.ctrl }) + evt.preventDefault() + evt.stopPropagation() + patch(props.api, props.input, { count: state.count - 1 }) + return + } + if (evt.ctrl && evt.name === "m") { console.log("[smoke] screen key", { key: evt.name, ctrl: evt.ctrl }) evt.preventDefault() @@ -127,16 +167,80 @@ const Screen = (props: { api: TuiApi; input: ReturnType; data?: Reco paddingBottom={1} paddingLeft={2} paddingRight={2} - gap={1} > - - {props.input.label} screen - plugin route - - Route id: {props.input.route} - source: {String(props.data?.source ?? "unknown")} - esc or ctrl+h go home · ctrl+m opens modal - + + + {props.input.label} screen + plugin route + + esc or ctrl+h home + + + + {tabs.map((item, i) => { + const on = value.tab === i + return ( + patch(props.api, props.input, { tab: i })} + backgroundColor={on ? ui.accent : ui.border} + paddingLeft={1} + paddingRight={1} + > + {item} + + ) + })} + + + + {value.tab === 0 ? ( + + Route id: {props.input.route} + source: {value.source} + left/right switch tabs + + ) : null} + + {value.tab === 1 ? ( + + Counter: {value.count} + ctrl+up and ctrl+down change value + + patch(props.api, props.input, { count: value.count + 1 })} + backgroundColor={ui.border} + paddingLeft={1} + > + +1 + + patch(props.api, props.input, { count: value.count - 1 })} + backgroundColor={ui.border} + paddingLeft={1} + > + -1 + + + + ) : null} + + {value.tab === 2 ? ( + + ctrl+m opens modal + esc or ctrl+h returns home + + ) : null} + + + props.api.route.home()} backgroundColor={ui.border} paddingLeft={1} paddingRight={1}> go home