disambiguate

actual-tui-plugins
Sebastian Herrlinger 2026-03-06 23:23:02 +01:00
parent e8959babcc
commit 6d183f5739
5 changed files with 37 additions and 38 deletions

View File

@ -231,15 +231,15 @@ function App() {
const keybind = useKeybind()
const sdk = useSDK()
const toast = useToast()
const t = useTheme()
const { theme, mode, setMode } = t
const themeState = useTheme()
const { theme, mode, setMode } = themeState
const sync = useSync()
const exit = useExit()
const promptRef = usePromptRef()
const routes = new Map<string, { key: symbol; render: TuiRouteDefinition["render"] }[]>()
const [rev, setRev] = createSignal(0)
const view = (name: string) => {
rev()
const [routeRev, setRouteRev] = createSignal(0)
const routeView = (name: string) => {
routeRev()
return routes.get(name)?.at(-1)?.render
}
@ -260,7 +260,7 @@ function App() {
list.push({ key, render: item.render })
routes.set(item.name, list)
}
setRev((x) => x + 1)
setRouteRev((x) => x + 1)
return () => {
for (const item of input) {
const list = routes.get(item.name)
@ -271,7 +271,7 @@ function App() {
)
if (!routes.get(item.name)?.length) routes.delete(item.name)
}
setRev((x) => x + 1)
setRouteRev((x) => x + 1)
}
},
navigate(name, params) {
@ -393,22 +393,22 @@ function App() {
return theme
},
get selected() {
return t.selected
return themeState.selected
},
has(name) {
return t.has(name)
return themeState.has(name)
},
set(name) {
return t.set(name)
return themeState.set(name)
},
async install(_jsonPath) {
throw new Error("theme.install is only available in plugin context")
},
mode() {
return t.mode()
return themeState.mode()
},
get ready() {
return t.ready
return themeState.ready
},
},
}
@ -957,7 +957,7 @@ function App() {
const plugin = createMemo(() => {
if (route.data.type !== "plugin") return
const render = view(route.data.id)
const render = routeView(route.data.id)
if (!render) return <PluginRouteMissing id={route.data.id} onHome={() => route.navigate({ type: "home" })} />
return render({ params: route.data.data })
})

View File

@ -72,8 +72,8 @@ function localDir(file: string) {
return path.join(dir, ".opencode", "themes")
}
function scopeDir(meta: TuiConfig.PluginMeta) {
if (meta.scope === "local") return localDir(meta.source)
function scopeDir(pluginMeta: TuiConfig.PluginMeta) {
if (pluginMeta.scope === "local") return localDir(pluginMeta.source)
return path.join(Global.Path.config, "themes")
}
@ -93,7 +93,7 @@ function themeName(file: string) {
return path.basename(file, path.extname(file))
}
function meta(config: TuiConfig.Info, item: Config.PluginSpec) {
function getPluginMeta(config: TuiConfig.Info, item: Config.PluginSpec) {
const key = Config.getPluginName(item)
const value = config.plugin_meta?.[key]
if (!value) {
@ -194,7 +194,6 @@ export namespace TuiPlugin {
const loadOne = async (item: (typeof plugins)[number], retry = false) => {
const spec = Config.pluginSpecifier(item)
const level = meta(config, item)
log.info("loading tui plugin", { path: spec, retry })
const target = await resolvePluginTarget(spec).catch((error) => {
log.error("failed to resolve tui plugin", { path: spec, retry, error })
@ -203,7 +202,7 @@ export namespace TuiPlugin {
if (!target) return false
const root = pluginRoot(spec, target)
const install = makeInstallFn(level, root)
const install = makeInstallFn(getPluginMeta(config, item), root)
const mod = await import(target).catch((error) => {
log.error("failed to load tui plugin", { path: spec, retry, error })

View File

@ -30,12 +30,12 @@ export namespace TuiConfig {
plugin_meta?: Record<string, PluginMeta>
}
function scope(file: string): PluginMeta["scope"] {
function pluginScope(file: string): PluginMeta["scope"] {
if (Instance.containsPath(file)) return "local"
return "global"
}
function dedupePlugin(list: PluginEntry[]) {
function dedupePlugins(list: PluginEntry[]) {
const seen = new Set<string>()
const result: PluginEntry[] = []
for (const item of list.toReversed()) {
@ -73,18 +73,18 @@ export namespace TuiConfig {
: await ConfigPaths.projectFiles("tui", Instance.directory, Instance.worktree)
let result: Info = {}
const plugin: PluginEntry[] = []
const pluginEntries: PluginEntry[] = []
const apply = async (file: string) => {
const mergeFile = async (file: string) => {
const data = await loadFile(file)
result = mergeInfo(result, data)
if (!data.plugin?.length) return
const level = scope(file)
const sourceScope = pluginScope(file)
for (const item of data.plugin) {
plugin.push({
pluginEntries.push({
item,
meta: {
scope: level,
scope: sourceScope,
source: file,
},
})
@ -92,32 +92,32 @@ export namespace TuiConfig {
}
for (const file of ConfigPaths.fileInDirectory(Global.Path.config, "tui")) {
await apply(file)
await mergeFile(file)
}
if (custom) {
await apply(custom)
await mergeFile(custom)
log.debug("loaded custom tui config", { path: custom })
}
for (const file of projectFiles) {
await apply(file)
await mergeFile(file)
}
for (const dir of unique(directories)) {
if (!dir.endsWith(".opencode") && dir !== Flag.OPENCODE_CONFIG_DIR) continue
for (const file of ConfigPaths.fileInDirectory(dir, "tui")) {
await apply(file)
await mergeFile(file)
}
}
if (existsSync(managed)) {
for (const file of ConfigPaths.fileInDirectory(managed, "tui")) {
await apply(file)
await mergeFile(file)
}
}
const merged = dedupePlugin(plugin)
const merged = dedupePlugins(pluginEntries)
result.keybinds = Config.Keybinds.parse(result.keybinds ?? {})
result.plugin = merged.map((item) => item.item)
result.plugin_meta = merged.length

View File

@ -54,7 +54,7 @@ export namespace Plugin {
plugins = [...BUILTIN, ...plugins]
}
async function resolve(spec: string) {
async function resolvePlugin(spec: string) {
const parsed = parsePluginSpecifier(spec)
const target = await resolvePluginTarget(spec, parsed).catch((err) => {
const cause = err instanceof Error ? err.cause : err
@ -87,9 +87,9 @@ export namespace Plugin {
// ignore old codex plugin since it is supported first party now
if (spec.includes("opencode-openai-codex-auth") || spec.includes("opencode-copilot-auth")) continue
log.info("loading plugin", { path: spec })
const path = await resolve(spec)
if (!path) continue
const mod = await import(path).catch((err) => {
const target = await resolvePlugin(spec)
if (!target) continue
const mod = await import(target).catch((err) => {
const message = err instanceof Error ? err.message : String(err)
log.error("failed to load plugin", { path: spec, error: message })
Bus.publish(Session.Event.Error, {

View File

@ -1,9 +1,9 @@
import { BunProc } from "@/bun"
export function parsePluginSpecifier(spec: string) {
const at = spec.lastIndexOf("@")
const pkg = at > 0 ? spec.substring(0, at) : spec
const version = at > 0 ? spec.substring(at + 1) : "latest"
const lastAt = spec.lastIndexOf("@")
const pkg = lastAt > 0 ? spec.substring(0, lastAt) : spec
const version = lastAt > 0 ? spec.substring(lastAt + 1) : "latest"
return { pkg, version }
}