mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 06:45:22 +00:00
plugin manager ui
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import type { TuiPlugin, TuiPluginStatus } from "@opencode-ai/plugin/tui"
|
||||
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
|
||||
import { createMemo, createSignal } from "solid-js"
|
||||
|
||||
type Api = Parameters<TuiPlugin>[0]
|
||||
|
||||
const id = "internal:plugin-manager"
|
||||
const key = Keybind.parse("space").at(0)
|
||||
|
||||
function state(api: Api, item: TuiPluginStatus) {
|
||||
return (
|
||||
<span style={{ fg: item.active ? api.theme.current.success : api.theme.current.error }}>
|
||||
{item.active ? "active" : "inactive"}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function row(api: Api, item: TuiPluginStatus): DialogSelectOption<string> {
|
||||
return {
|
||||
title: item.name,
|
||||
value: item.id,
|
||||
category: item.source === "internal" ? "Internal" : "External",
|
||||
description: item.source === "internal" ? "Built-in" : item.spec,
|
||||
footer: state(api, item),
|
||||
disabled: item.id === id,
|
||||
}
|
||||
}
|
||||
|
||||
function View(props: { api: Api }) {
|
||||
const [list, setList] = createSignal(props.api.plugins.list())
|
||||
const [cur, setCur] = createSignal<string | undefined>()
|
||||
const [lock, setLock] = createSignal(false)
|
||||
const rows = createMemo(() =>
|
||||
[...list()]
|
||||
.sort((a, b) => {
|
||||
const x = a.source === "internal" ? 1 : 0
|
||||
const y = b.source === "internal" ? 1 : 0
|
||||
if (x !== y) return x - y
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
.map((item) => row(props.api, item)),
|
||||
)
|
||||
|
||||
const flip = (x: string) => {
|
||||
if (lock()) return
|
||||
const item = list().find((entry) => entry.id === x)
|
||||
if (!item) return
|
||||
setLock(true)
|
||||
const task = item.active ? props.api.plugins.deactivate(x) : props.api.plugins.activate(x)
|
||||
task
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
props.api.ui.toast({
|
||||
variant: "error",
|
||||
message: `Failed to update plugin ${item.name}`,
|
||||
})
|
||||
}
|
||||
setList(props.api.plugins.list())
|
||||
})
|
||||
.finally(() => {
|
||||
setLock(false)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogSelect
|
||||
title="Plugins"
|
||||
options={rows()}
|
||||
current={cur()}
|
||||
onMove={(item) => setCur(item.value)}
|
||||
keybind={[
|
||||
{
|
||||
title: "toggle",
|
||||
keybind: key,
|
||||
disabled: lock(),
|
||||
onTrigger: (item) => {
|
||||
setCur(item.value)
|
||||
flip(item.value)
|
||||
},
|
||||
},
|
||||
]}
|
||||
onSelect={(item) => {
|
||||
setCur(item.value)
|
||||
flip(item.value)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function show(api: Api) {
|
||||
api.ui.dialog.replace(() => <View api={api} />)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.command.register(() => [
|
||||
{
|
||||
title: "Plugins",
|
||||
value: "plugins.list",
|
||||
keybind: "<leader>p",
|
||||
category: "System",
|
||||
onSelect() {
|
||||
show(api)
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
export default {
|
||||
id,
|
||||
tui,
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import * as SidebarLsp from "../feature-plugins/sidebar/lsp"
|
||||
import * as SidebarTodo from "../feature-plugins/sidebar/todo"
|
||||
import * as SidebarFiles from "../feature-plugins/sidebar/files"
|
||||
import * as SidebarFooter from "../feature-plugins/sidebar/footer"
|
||||
import * as PluginManager from "../feature-plugins/system/plugins"
|
||||
|
||||
export type InternalTuiPlugin = {
|
||||
name: string
|
||||
@@ -46,4 +47,8 @@ export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [
|
||||
name: "sidebar-footer",
|
||||
module: SidebarFooter,
|
||||
},
|
||||
{
|
||||
name: "plugin-manager",
|
||||
module: PluginManager,
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user