cleanup
parent
29aab3223c
commit
f9385bcc63
|
|
@ -728,8 +728,8 @@ const reg = (api: TuiApi, input: ReturnType<typeof cfg>) => {
|
|||
const tui = async (input: TuiPluginInput, options?: Record<string, unknown>) => {
|
||||
if (options?.enabled === false) return
|
||||
|
||||
await input.api.theme.install("../themes/mytheme.json")
|
||||
input.api.theme.set("mytheme")
|
||||
await input.api.theme.install("./smoke-theme.json")
|
||||
input.api.theme.set("smoke-theme")
|
||||
|
||||
const value = cfg(options)
|
||||
const route = names(value)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
smoke-theme.json
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"theme": "mytheme",
|
||||
"theme": "smoke-theme",
|
||||
"plugin": [
|
||||
[
|
||||
"./plugins/tui-smoke.tsx",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
type TuiPlugin as TuiPluginFn,
|
||||
type TuiPluginInput,
|
||||
type TuiTheme,
|
||||
type TuiSlotContext,
|
||||
type TuiSlotMap,
|
||||
type TuiSlots,
|
||||
type SlotMode,
|
||||
type TuiApi,
|
||||
} from "@opencode-ai/plugin/tui"
|
||||
import { createSlot, createSolidSlotRegistry, type JSX, type SolidPlugin } from "@opentui/solid"
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
|
|
@ -66,82 +66,75 @@ function isTheme(value: unknown) {
|
|||
return true
|
||||
}
|
||||
|
||||
function localThemeDir(file: string) {
|
||||
function localDir(file: string) {
|
||||
const dir = path.dirname(file)
|
||||
if (path.basename(dir) === ".opencode") return path.join(dir, "themes")
|
||||
return path.join(dir, ".opencode", "themes")
|
||||
}
|
||||
|
||||
function themeDir(meta?: TuiConfig.PluginMeta) {
|
||||
if (meta?.scope === "local") return localThemeDir(meta.source)
|
||||
function scopeDir(meta: TuiConfig.PluginMeta) {
|
||||
if (meta.scope === "local") return localDir(meta.source)
|
||||
return path.join(Global.Path.config, "themes")
|
||||
}
|
||||
|
||||
function pluginDir(spec: string, target: string) {
|
||||
function pluginRoot(spec: string, target: string) {
|
||||
if (spec.startsWith("file://")) return path.dirname(fileURLToPath(spec))
|
||||
if (target.startsWith("file://")) return path.dirname(fileURLToPath(target))
|
||||
return target
|
||||
}
|
||||
|
||||
function themePath(root: string, filepath: string) {
|
||||
if (filepath.startsWith("file://")) return fileURLToPath(filepath)
|
||||
if (path.isAbsolute(filepath)) return filepath
|
||||
return path.resolve(root, filepath)
|
||||
function source(root: string, file: string) {
|
||||
if (file.startsWith("file://")) return fileURLToPath(file)
|
||||
if (path.isAbsolute(file)) return file
|
||||
return path.resolve(root, file)
|
||||
}
|
||||
|
||||
function themeName(filepath: string) {
|
||||
return path.basename(filepath, path.extname(filepath))
|
||||
function name(file: string) {
|
||||
return path.basename(file, path.extname(file))
|
||||
}
|
||||
|
||||
function themeApi(
|
||||
api: TuiApi<JSX.Element>,
|
||||
options: {
|
||||
root: string
|
||||
meta?: TuiConfig.PluginMeta
|
||||
},
|
||||
) {
|
||||
return {
|
||||
get current() {
|
||||
return api.theme.current
|
||||
},
|
||||
get selected() {
|
||||
return api.theme.selected
|
||||
},
|
||||
mode() {
|
||||
return api.theme.mode()
|
||||
},
|
||||
get ready() {
|
||||
return api.theme.ready
|
||||
},
|
||||
has(name: string) {
|
||||
return api.theme.has(name)
|
||||
},
|
||||
set(name: string) {
|
||||
return api.theme.set(name)
|
||||
},
|
||||
async install(filepath: string) {
|
||||
const source = themePath(options.root, filepath)
|
||||
const name = themeName(source)
|
||||
if (hasTheme(name)) return
|
||||
|
||||
const text = await Bun.file(source)
|
||||
.text()
|
||||
.catch((error) => {
|
||||
throw new Error(`failed to read theme at ${source}: ${error}`)
|
||||
})
|
||||
const data = JSON.parse(text)
|
||||
if (!isTheme(data)) {
|
||||
throw new Error(`invalid theme at ${source}`)
|
||||
}
|
||||
|
||||
const dest = path.join(themeDir(options.meta), `${name}.json`)
|
||||
if (!(await Filesystem.exists(dest))) {
|
||||
await Filesystem.write(dest, text)
|
||||
}
|
||||
|
||||
addTheme(name, data)
|
||||
},
|
||||
function meta(config: TuiConfig.Info, item: Config.PluginSpec) {
|
||||
const key = Config.getPluginName(item)
|
||||
const value = config.plugin_meta?.[key]
|
||||
if (!value) {
|
||||
throw new Error(`missing plugin metadata for ${key}`)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function install(meta: TuiConfig.PluginMeta, root: string): TuiTheme["install"] {
|
||||
return async (file) => {
|
||||
const src = source(root, file)
|
||||
const theme = name(src)
|
||||
if (hasTheme(theme)) return
|
||||
|
||||
const text = await Bun.file(src)
|
||||
.text()
|
||||
.catch((error) => {
|
||||
throw new Error(`failed to read theme at ${src}: ${error}`)
|
||||
})
|
||||
const data = JSON.parse(text)
|
||||
if (!isTheme(data)) {
|
||||
throw new Error(`invalid theme at ${src}`)
|
||||
}
|
||||
|
||||
const dest = path.join(scopeDir(meta), `${theme}.json`)
|
||||
if (!(await Filesystem.exists(dest))) {
|
||||
await Filesystem.write(dest, text)
|
||||
}
|
||||
|
||||
addTheme(theme, data)
|
||||
}
|
||||
}
|
||||
|
||||
function themeApi(theme: TuiTheme, add: TuiTheme["install"]): TuiTheme {
|
||||
return Object.create(theme, {
|
||||
install: {
|
||||
value: add,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export namespace TuiPlugin {
|
||||
|
|
@ -211,7 +204,7 @@ export namespace TuiPlugin {
|
|||
|
||||
const loadOne = async (item: (typeof plugins)[number], retry = false) => {
|
||||
const spec = Config.pluginSpecifier(item)
|
||||
const meta = config.plugin_meta?.[Config.getPluginName(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 })
|
||||
|
|
@ -219,6 +212,9 @@ export namespace TuiPlugin {
|
|||
})
|
||||
if (!target) return false
|
||||
|
||||
const root = pluginRoot(spec, target)
|
||||
const add = install(level, root)
|
||||
|
||||
const mod = await import(target).catch((error) => {
|
||||
log.error("failed to load tui plugin", { path: spec, retry, error })
|
||||
return
|
||||
|
|
@ -240,7 +236,6 @@ export namespace TuiPlugin {
|
|||
|
||||
const tuiPlugin = getTuiPlugin(entry)
|
||||
if (!tuiPlugin) continue
|
||||
const root = pluginDir(spec, target)
|
||||
await tuiPlugin(
|
||||
{
|
||||
...input,
|
||||
|
|
@ -249,7 +244,7 @@ export namespace TuiPlugin {
|
|||
route: input.api.route,
|
||||
ui: input.api.ui,
|
||||
keybind: input.api.keybind,
|
||||
theme: themeApi(input.api, { root, meta }),
|
||||
theme: themeApi(input.api.theme, add),
|
||||
},
|
||||
},
|
||||
Config.pluginOptions(item),
|
||||
|
|
|
|||
|
|
@ -64,48 +64,44 @@ test("loads plugin theme API with scoped theme installation", async () => {
|
|||
|
||||
await Bun.write(
|
||||
localPluginPath,
|
||||
[
|
||||
"export default async (_input, options) => {",
|
||||
" if (!options?.fn_marker) return",
|
||||
" await Bun.write(options.fn_marker, 'called')",
|
||||
"}",
|
||||
"",
|
||||
"export const object_plugin = {",
|
||||
" tui: async (input, options) => {",
|
||||
" if (!options?.marker) return",
|
||||
" const before = input.api.theme.has(options.theme_name)",
|
||||
" const set_missing = input.api.theme.set(options.theme_name)",
|
||||
" await input.api.theme.install(options.theme_path)",
|
||||
" const after = input.api.theme.has(options.theme_name)",
|
||||
" const set_installed = input.api.theme.set(options.theme_name)",
|
||||
" const first = await Bun.file(options.dest).text()",
|
||||
" await Bun.write(options.source, JSON.stringify({ theme: { primary: '#fefefe' } }, null, 2))",
|
||||
" await input.api.theme.install(options.theme_path)",
|
||||
" const second = await Bun.file(options.dest).text()",
|
||||
" await Bun.write(",
|
||||
" options.marker,",
|
||||
" JSON.stringify({ before, set_missing, after, set_installed, selected: input.api.theme.selected, same: first === second }),",
|
||||
" )",
|
||||
" },",
|
||||
"}",
|
||||
"",
|
||||
].join("\n"),
|
||||
`export default async (_input, options) => {
|
||||
if (!options?.fn_marker) return
|
||||
await Bun.write(options.fn_marker, "called")
|
||||
}
|
||||
|
||||
export const object_plugin = {
|
||||
tui: async (input, options) => {
|
||||
if (!options?.marker) return
|
||||
const before = input.api.theme.has(options.theme_name)
|
||||
const set_missing = input.api.theme.set(options.theme_name)
|
||||
await input.api.theme.install(options.theme_path)
|
||||
const after = input.api.theme.has(options.theme_name)
|
||||
const set_installed = input.api.theme.set(options.theme_name)
|
||||
const first = await Bun.file(options.dest).text()
|
||||
await Bun.write(options.source, JSON.stringify({ theme: { primary: "#fefefe" } }, null, 2))
|
||||
await input.api.theme.install(options.theme_path)
|
||||
const second = await Bun.file(options.dest).text()
|
||||
await Bun.write(
|
||||
options.marker,
|
||||
JSON.stringify({ before, set_missing, after, set_installed, selected: input.api.theme.selected, same: first === second }),
|
||||
)
|
||||
},
|
||||
}
|
||||
`,
|
||||
)
|
||||
|
||||
await Bun.write(
|
||||
globalPluginPath,
|
||||
[
|
||||
"export default {",
|
||||
" tui: async (input, options) => {",
|
||||
" if (!options?.marker) return",
|
||||
" await input.api.theme.install(options.theme_path)",
|
||||
" const has = input.api.theme.has(options.theme_name)",
|
||||
" const set_installed = input.api.theme.set(options.theme_name)",
|
||||
" await Bun.write(options.marker, JSON.stringify({ has, set_installed, selected: input.api.theme.selected }))",
|
||||
" },",
|
||||
"}",
|
||||
"",
|
||||
].join("\n"),
|
||||
`export default {
|
||||
tui: async (input, options) => {
|
||||
if (!options?.marker) return
|
||||
await input.api.theme.install(options.theme_path)
|
||||
const has = input.api.theme.has(options.theme_name)
|
||||
const set_installed = input.api.theme.set(options.theme_name)
|
||||
await Bun.write(options.marker, JSON.stringify({ has, set_installed, selected: input.api.theme.selected }))
|
||||
},
|
||||
}
|
||||
`,
|
||||
)
|
||||
|
||||
await Bun.write(
|
||||
|
|
|
|||
Loading…
Reference in New Issue