mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 06:45:22 +00:00
338 lines
8.6 KiB
TypeScript
338 lines
8.6 KiB
TypeScript
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
|
import { RGBA, type CliRenderer } from "@opentui/core"
|
|
import { createPluginKeybind } from "../../src/cli/cmd/tui/context/plugin-keybinds"
|
|
import type { HostPluginApi } from "../../src/cli/cmd/tui/plugin/slots"
|
|
|
|
type Count = {
|
|
event_add: number
|
|
event_drop: number
|
|
route_add: number
|
|
route_drop: number
|
|
command_add: number
|
|
command_drop: number
|
|
}
|
|
|
|
function themeCurrent(): HostPluginApi["theme"]["current"] {
|
|
const a = RGBA.fromInts(0, 120, 240)
|
|
const b = RGBA.fromInts(120, 120, 120)
|
|
const c = RGBA.fromInts(230, 230, 230)
|
|
const d = RGBA.fromInts(120, 30, 30)
|
|
const e = RGBA.fromInts(140, 100, 40)
|
|
const f = RGBA.fromInts(20, 140, 80)
|
|
const g = RGBA.fromInts(20, 80, 160)
|
|
const h = RGBA.fromInts(40, 40, 40)
|
|
const i = RGBA.fromInts(60, 60, 60)
|
|
const j = RGBA.fromInts(80, 80, 80)
|
|
return {
|
|
primary: a,
|
|
secondary: b,
|
|
accent: a,
|
|
error: d,
|
|
warning: e,
|
|
success: f,
|
|
info: g,
|
|
text: c,
|
|
textMuted: b,
|
|
selectedListItemText: h,
|
|
background: h,
|
|
backgroundPanel: h,
|
|
backgroundElement: i,
|
|
backgroundMenu: i,
|
|
border: j,
|
|
borderActive: c,
|
|
borderSubtle: i,
|
|
diffAdded: f,
|
|
diffRemoved: d,
|
|
diffContext: b,
|
|
diffHunkHeader: b,
|
|
diffHighlightAdded: f,
|
|
diffHighlightRemoved: d,
|
|
diffAddedBg: h,
|
|
diffRemovedBg: h,
|
|
diffContextBg: h,
|
|
diffLineNumber: b,
|
|
diffAddedLineNumberBg: h,
|
|
diffRemovedLineNumberBg: h,
|
|
markdownText: c,
|
|
markdownHeading: c,
|
|
markdownLink: a,
|
|
markdownLinkText: g,
|
|
markdownCode: f,
|
|
markdownBlockQuote: e,
|
|
markdownEmph: e,
|
|
markdownStrong: c,
|
|
markdownHorizontalRule: b,
|
|
markdownListItem: a,
|
|
markdownListEnumeration: g,
|
|
markdownImage: a,
|
|
markdownImageText: g,
|
|
markdownCodeBlock: c,
|
|
syntaxComment: b,
|
|
syntaxKeyword: a,
|
|
syntaxFunction: g,
|
|
syntaxVariable: c,
|
|
syntaxString: f,
|
|
syntaxNumber: e,
|
|
syntaxType: a,
|
|
syntaxOperator: a,
|
|
syntaxPunctuation: c,
|
|
thinkingOpacity: 0.6,
|
|
}
|
|
}
|
|
|
|
type Opts = {
|
|
client?: HostPluginApi["client"] | (() => HostPluginApi["client"])
|
|
scopedClient?: HostPluginApi["scopedClient"]
|
|
workspace?: Partial<HostPluginApi["workspace"]>
|
|
renderer?: HostPluginApi["renderer"]
|
|
count?: Count
|
|
keybind?: Partial<HostPluginApi["keybind"]>
|
|
tuiConfig?: HostPluginApi["tuiConfig"]
|
|
app?: Partial<HostPluginApi["app"]>
|
|
state?: {
|
|
ready?: HostPluginApi["state"]["ready"]
|
|
config?: HostPluginApi["state"]["config"]
|
|
provider?: HostPluginApi["state"]["provider"]
|
|
path?: HostPluginApi["state"]["path"]
|
|
vcs?: HostPluginApi["state"]["vcs"]
|
|
workspace?: Partial<HostPluginApi["state"]["workspace"]>
|
|
session?: Partial<HostPluginApi["state"]["session"]>
|
|
part?: HostPluginApi["state"]["part"]
|
|
lsp?: HostPluginApi["state"]["lsp"]
|
|
mcp?: HostPluginApi["state"]["mcp"]
|
|
}
|
|
theme?: {
|
|
selected?: string
|
|
has?: HostPluginApi["theme"]["has"]
|
|
set?: HostPluginApi["theme"]["set"]
|
|
install?: HostPluginApi["theme"]["install"]
|
|
mode?: HostPluginApi["theme"]["mode"]
|
|
ready?: boolean
|
|
current?: HostPluginApi["theme"]["current"]
|
|
}
|
|
}
|
|
|
|
export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
|
const kv: Record<string, unknown> = {}
|
|
const count = opts.count
|
|
const ctrl = new AbortController()
|
|
const own = createOpencodeClient({
|
|
baseUrl: "http://localhost:4096",
|
|
})
|
|
const fallback = () => own
|
|
const read =
|
|
typeof opts.client === "function"
|
|
? opts.client
|
|
: opts.client
|
|
? () => opts.client as HostPluginApi["client"]
|
|
: fallback
|
|
const client = () => read()
|
|
const scopedClient = opts.scopedClient ?? ((_workspaceID?: string) => client())
|
|
const workspace: HostPluginApi["workspace"] = {
|
|
current: opts.workspace?.current ?? (() => undefined),
|
|
set: opts.workspace?.set ?? (() => {}),
|
|
}
|
|
let depth = 0
|
|
let size: "medium" | "large" | "xlarge" = "medium"
|
|
const has = opts.theme?.has ?? (() => false)
|
|
let selected = opts.theme?.selected ?? "opencode"
|
|
const key = {
|
|
match: opts.keybind?.match ?? (() => false),
|
|
print: opts.keybind?.print ?? ((name: string) => name),
|
|
}
|
|
const set =
|
|
opts.theme?.set ??
|
|
((name: string) => {
|
|
if (!has(name)) return false
|
|
selected = name
|
|
return true
|
|
})
|
|
const renderer: CliRenderer = opts.renderer ?? {
|
|
...Object.create(null),
|
|
once(this: CliRenderer) {
|
|
return this
|
|
},
|
|
}
|
|
|
|
function kvGet(name: string): unknown
|
|
function kvGet<Value>(name: string, fallback: Value): Value
|
|
function kvGet(name: string, fallback?: unknown) {
|
|
const value = kv[name]
|
|
if (value === undefined) return fallback
|
|
return value
|
|
}
|
|
|
|
return {
|
|
app: {
|
|
get version() {
|
|
return opts.app?.version ?? "0.0.0-test"
|
|
},
|
|
},
|
|
get client() {
|
|
return client()
|
|
},
|
|
scopedClient,
|
|
workspace,
|
|
event: {
|
|
on: () => {
|
|
if (count) count.event_add += 1
|
|
return () => {
|
|
if (!count) return
|
|
count.event_drop += 1
|
|
}
|
|
},
|
|
},
|
|
renderer,
|
|
slots: {
|
|
register: () => "fixture-slot",
|
|
},
|
|
plugins: {
|
|
list: () => [],
|
|
activate: async () => false,
|
|
deactivate: async () => false,
|
|
add: async () => false,
|
|
install: async () => ({
|
|
ok: false,
|
|
message: "not implemented in fixture",
|
|
}),
|
|
},
|
|
lifecycle: {
|
|
signal: ctrl.signal,
|
|
onDispose() {
|
|
return () => {}
|
|
},
|
|
},
|
|
command: {
|
|
register: () => {
|
|
if (count) count.command_add += 1
|
|
return () => {
|
|
if (!count) return
|
|
count.command_drop += 1
|
|
}
|
|
},
|
|
trigger: () => {},
|
|
show: () => {},
|
|
},
|
|
route: {
|
|
register: () => {
|
|
if (count) count.route_add += 1
|
|
return () => {
|
|
if (!count) return
|
|
count.route_drop += 1
|
|
}
|
|
},
|
|
navigate: () => {},
|
|
get current() {
|
|
return { name: "home" }
|
|
},
|
|
},
|
|
ui: {
|
|
Dialog: () => null,
|
|
DialogAlert: () => null,
|
|
DialogConfirm: () => null,
|
|
DialogPrompt: () => null,
|
|
DialogSelect: () => null,
|
|
Slot: () => null,
|
|
Prompt: () => null,
|
|
toast: () => {},
|
|
dialog: {
|
|
replace: () => {
|
|
depth = 1
|
|
},
|
|
clear: () => {
|
|
depth = 0
|
|
size = "medium"
|
|
},
|
|
setSize: (next) => {
|
|
size = next
|
|
},
|
|
get size() {
|
|
return size
|
|
},
|
|
get depth() {
|
|
return depth
|
|
},
|
|
get open() {
|
|
return depth > 0
|
|
},
|
|
},
|
|
},
|
|
keybind: {
|
|
...key,
|
|
create:
|
|
opts.keybind?.create ??
|
|
((defaults, over) => {
|
|
return createPluginKeybind(key, defaults, over)
|
|
}),
|
|
},
|
|
tuiConfig: opts.tuiConfig ?? {},
|
|
kv: {
|
|
get: kvGet,
|
|
set(name, value) {
|
|
kv[name] = value
|
|
},
|
|
get ready() {
|
|
return true
|
|
},
|
|
},
|
|
state: {
|
|
get ready() {
|
|
return opts.state?.ready ?? true
|
|
},
|
|
get config() {
|
|
return opts.state?.config ?? {}
|
|
},
|
|
get provider() {
|
|
return opts.state?.provider ?? []
|
|
},
|
|
get path() {
|
|
return opts.state?.path ?? { state: "", config: "", worktree: "", directory: "" }
|
|
},
|
|
get vcs() {
|
|
return opts.state?.vcs
|
|
},
|
|
workspace: {
|
|
list: opts.state?.workspace?.list ?? (() => []),
|
|
get: opts.state?.workspace?.get ?? (() => undefined),
|
|
},
|
|
session: {
|
|
count: opts.state?.session?.count ?? (() => 0),
|
|
diff: opts.state?.session?.diff ?? (() => []),
|
|
todo: opts.state?.session?.todo ?? (() => []),
|
|
messages: opts.state?.session?.messages ?? (() => []),
|
|
status: opts.state?.session?.status ?? (() => undefined),
|
|
permission: opts.state?.session?.permission ?? (() => []),
|
|
question: opts.state?.session?.question ?? (() => []),
|
|
},
|
|
part: opts.state?.part ?? (() => []),
|
|
lsp: opts.state?.lsp ?? (() => []),
|
|
mcp: opts.state?.mcp ?? (() => []),
|
|
},
|
|
theme: {
|
|
get current() {
|
|
return opts.theme?.current ?? themeCurrent()
|
|
},
|
|
get selected() {
|
|
return selected
|
|
},
|
|
has(name) {
|
|
return has(name)
|
|
},
|
|
set(name) {
|
|
return set(name)
|
|
},
|
|
async install(file) {
|
|
if (opts.theme?.install) return opts.theme.install(file)
|
|
throw new Error("base theme.install should not run")
|
|
},
|
|
mode() {
|
|
if (opts.theme?.mode) return opts.theme.mode()
|
|
return "dark"
|
|
},
|
|
get ready() {
|
|
return opts.theme?.ready ?? true
|
|
},
|
|
},
|
|
}
|
|
}
|