From 23ebfa0aeee8330e103529bcceb6c35760141a78 Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Thu, 26 Mar 2026 23:39:06 +0100 Subject: [PATCH] refine plugin manager dialog --- .../tui/feature-plugins/system/plugins.tsx | 44 +++++++++++++++++-- .../opencode/src/cli/cmd/tui/ui/dialog.tsx | 13 ++++-- packages/opencode/test/fixture/tui-plugin.ts | 2 +- packages/plugin/src/tui.ts | 6 +-- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx index 82adf8ea0b..54d3bf019e 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx @@ -1,12 +1,18 @@ import { Keybind } from "@/util/keybind" import type { TuiPlugin, TuiPluginApi, TuiPluginStatus } from "@opencode-ai/plugin/tui" +import { useTerminalDimensions } from "@opentui/solid" +import { fileURLToPath } from "url" import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select" -import { createMemo, createSignal } from "solid-js" +import { createEffect, createMemo, createSignal } from "solid-js" const id = "internal:plugin-manager" const key = Keybind.parse("space").at(0) function state(api: TuiPluginApi, item: TuiPluginStatus) { + if (!item.enabled) { + return disabled + } + return ( {item.active ? "active" : "inactive"} @@ -14,21 +20,51 @@ function state(api: TuiPluginApi, item: TuiPluginStatus) { ) } -function row(api: TuiPluginApi, item: TuiPluginStatus): DialogSelectOption { +function source(spec: string) { + if (!spec.startsWith("file://")) return + return fileURLToPath(spec) +} + +function meta(item: TuiPluginStatus, width: number) { + if (item.source === "internal") { + if (width >= 120) return "Built-in plugin" + return "Built-in" + } + const next = source(item.spec) + if (next) return next + return item.spec +} + +function row(api: TuiPluginApi, item: TuiPluginStatus, width: number): DialogSelectOption { return { title: item.id, value: item.id, category: item.source === "internal" ? "Internal" : "External", - description: item.source === "internal" ? "Built-in" : item.spec, + description: meta(item, width), footer: state(api, item), disabled: item.id === id, } } function View(props: { api: TuiPluginApi }) { + const size = useTerminalDimensions() const [list, setList] = createSignal(props.api.plugins.list()) const [cur, setCur] = createSignal() const [lock, setLock] = createSignal(false) + + createEffect(() => { + const width = size().width + if (width >= 128) { + props.api.ui.dialog.setSize("xlarge") + return + } + if (width >= 96) { + props.api.ui.dialog.setSize("large") + return + } + props.api.ui.dialog.setSize("medium") + }) + const rows = createMemo(() => [...list()] .sort((a, b) => { @@ -37,7 +73,7 @@ function View(props: { api: TuiPluginApi }) { if (x !== y) return x - y return a.id.localeCompare(b.id) }) - .map((item) => row(props.api, item)), + .map((item) => row(props.api, item, size().width)), ) const flip = (x: string) => { diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx index e43d6afe56..11c43fe24c 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx @@ -9,7 +9,7 @@ import { Selection } from "@tui/util/selection" export function Dialog( props: ParentProps<{ - size?: "medium" | "large" + size?: "medium" | "large" | "xlarge" onClose: () => void }>, ) { @@ -18,6 +18,11 @@ export function Dialog( const renderer = useRenderer() let dismiss = false + const width = () => { + if (props.size === "xlarge") return 116 + if (props.size === "large") return 88 + return 60 + } return ( void }[], - size: "medium" as "medium" | "large", + size: "medium" as "medium" | "large" | "xlarge", }) const renderer = useRenderer() @@ -136,7 +141,7 @@ function init() { get size() { return store.size }, - setSize(size: "medium" | "large") { + setSize(size: "medium" | "large" | "xlarge") { setStore("size", size) }, } diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index bd8416a17f..33f6af9dfd 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -133,7 +133,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { set: opts.workspace?.set ?? (() => {}), } let depth = 0 - let size: "medium" | "large" = "medium" + let size: "medium" | "large" | "xlarge" = "medium" const has = opts.theme?.has ?? (() => false) let selected = opts.theme?.selected ?? "opencode" const key = { diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index 5083b14357..4bb3e7013b 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -75,7 +75,7 @@ export type TuiKeybindSet = { } export type TuiDialogProps = { - size?: "medium" | "large" + size?: "medium" | "large" | "xlarge" onClose: () => void children?: JSX.Element } @@ -83,8 +83,8 @@ export type TuiDialogProps = { export type TuiDialogStack = { replace: (render: () => JSX.Element, onClose?: () => void) => void clear: () => void - setSize: (size: "medium" | "large") => void - readonly size: "medium" | "large" + setSize: (size: "medium" | "large" | "xlarge") => void + readonly size: "medium" | "large" | "xlarge" readonly depth: number readonly open: boolean }