Parcourir la source

fix(tui): preserve legacy host compatibility

Dax Raad il y a 4 semaines
Parent
commit
b2bdab24e6
3 fichiers modifiés avec 59 ajouts et 15 suppressions
  1. 13 7
      packages/plugin/src/tui.ts
  2. 45 6
      packages/tui/src/app.tsx
  3. 1 2
      packages/tui/src/plugin/runtime.tsx

+ 13 - 7
packages/plugin/src/tui.ts

@@ -24,7 +24,7 @@ import {
   type SequenceBindingLike,
 } from "@opentui/keymap/extras"
 import type { JSX, SolidPlugin } from "@opentui/solid"
-import type { PluginOptions } from "./index.js"
+import type { Config as PluginConfig, PluginOptions } from "./index.js"
 
 export type { CliRenderer, KeyEvent, Renderable, SlotMode } from "@opentui/core"
 export { stringifyKeySequence, stringifyKeyStroke } from "@opentui/keymap"
@@ -408,13 +408,16 @@ type TuiAttentionConfigView = {
 }
 
 type TuiConfigView = {
-  theme?: {
-    name?: string
-    mode?: "system" | "dark" | "light"
-  }
+  $schema?: string
+  theme?: string | { name?: string; mode?: "system" | "dark" | "light" }
+  plugin?: PluginConfig["plugin"]
   plugins?: ReadonlyArray<string | { package: string; options?: Record<string, any> }>
-  leader: { timeout: number }
+  plugin_enabled?: Record<string, boolean>
+  leader?: { timeout: number }
+  leader_timeout?: number
   scroll?: { speed?: number; acceleration?: boolean }
+  scroll_speed?: number
+  scroll_acceleration?: { enabled: boolean }
   attention: TuiAttentionConfigView
   diffs?: {
     wrap?: "word" | "none"
@@ -422,8 +425,11 @@ type TuiConfigView = {
     single?: boolean
     view?: "auto" | "split" | "unified"
   }
+  diff_style?: "auto" | "stacked"
   terminal?: { title?: boolean }
-  prompt?: { editor?: boolean; paste?: "compact" | "full" }
+  prompt?:
+    | { editor?: boolean; paste?: "compact" | "full" }
+    | { max_height?: number; max_width?: number | "auto" }
   session?: {
     sidebar?: "auto" | "hide"
     scrollbar?: boolean

+ 45 - 6
packages/tui/src/app.tsx

@@ -76,6 +76,7 @@ import { ArgsProvider, useArgs, type Args } from "./context/args"
 import open from "open"
 import { PromptRefProvider, usePromptRef } from "./context/prompt"
 import { TuiConfig, TuiConfigProvider, useTuiConfig } from "./config"
+import { TuiConfigV1 } from "./config/v1"
 import { createTuiApiAdapters } from "./plugin/adapters"
 import { createTuiApi } from "./plugin/api"
 import { createPluginRuntime, PluginRuntimeProvider, usePluginRuntime, type TuiPluginHost } from "./plugin/runtime"
@@ -153,7 +154,7 @@ export type TuiInput = {
     reload?: () => Promise<void>
   }
   args: Args
-  config: TuiConfig.Interface
+  config: TuiConfig.Interface | TuiConfigV1.Resolved
   onSnapshot?: () => Promise<string[]>
   pluginHost: TuiPluginHost
   terminalHandoff?: () => Promise<
@@ -199,11 +200,43 @@ function isVersionGreater(left: string, right: string) {
   return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true }) > 0
 }
 
+function fromV1(config: TuiConfigV1.Resolved): TuiConfig.Info {
+  return {
+    theme: config.theme ? { name: config.theme } : undefined,
+    plugins: config.plugin?.map((plugin) =>
+      typeof plugin === "string" ? plugin : { package: plugin[0], options: plugin[1] },
+    ),
+    leader: { timeout: config.leader_timeout },
+    scroll:
+      config.scroll_speed === undefined && config.scroll_acceleration === undefined
+        ? undefined
+        : { speed: config.scroll_speed, acceleration: config.scroll_acceleration?.enabled },
+    attention: config.attention,
+    diffs: config.diff_style === undefined ? undefined : { view: config.diff_style === "stacked" ? "unified" : "auto" },
+    mouse: config.mouse,
+  }
+}
+
+function isConfigInterface(config: TuiConfig.Interface | TuiConfigV1.Resolved): config is TuiConfig.Interface {
+  return "get" in config && typeof config.get === "function" && "update" in config && typeof config.update === "function"
+}
+
 export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
   const log = input.log ?? (() => {})
   const global = yield* Global.Service
-  const configInfo = yield* Effect.tryPromise(() => input.config.get())
-  const config = TuiConfig.resolve(configInfo, { terminalSuspend: process.platform !== "win32" })
+  const configInput = input.config
+  const loaded = yield* Effect.gen(function* () {
+    if (isConfigInterface(configInput)) {
+      return {
+        service: configInput,
+        info: yield* Effect.tryPromise(() => configInput.get()),
+        legacy: undefined,
+      }
+    }
+    return { service: undefined, info: fromV1(configInput), legacy: configInput }
+  })
+  const config = TuiConfig.resolve(loaded.info, { terminalSuspend: process.platform !== "win32" })
+  if (loaded.legacy) config.keybinds = loaded.legacy.keybinds
   const options = { baseUrl: input.server.endpoint.url, headers: Service.headers(input.server.endpoint) }
   const api = OpenCode.make(options)
   const directory = yield* Effect.tryPromise(() => api.file.list({ location: { directory: process.cwd() } })).pipe(
@@ -340,7 +373,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
                               <ArgsProvider {...input.args}>
                                 <TuiConfigProvider
                                   config={config}
-                                  service={input.config}
+                                  service={loaded.service}
                                   options={{ terminalSuspend: process.platform !== "win32" }}
                                 >
                                   <KVProvider>
@@ -378,6 +411,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
                                                                       <App
                                                                         onSnapshot={input.onSnapshot}
                                                                         pluginHost={input.pluginHost}
+                                                                        pluginConfig={loaded.legacy ?? config}
                                                                         pair={
                                                                           input.server.endpoint.auth
                                                                             ? input.server.endpoint.auth
@@ -435,7 +469,12 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
   })
 })
 
-function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPluginHost; pair?: DialogPairCredentials }) {
+function App(props: {
+  onSnapshot?: () => Promise<string[]>
+  pluginHost: TuiPluginHost
+  pluginConfig: any
+  pair?: DialogPairCredentials
+}) {
   const log = useLog({ component: "app" })
   const startup = useTuiStartup()
   const tuiConfig = useTuiConfig()
@@ -512,7 +551,7 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
   props.pluginHost
     .start({
       api,
-      config: tuiConfig,
+      config: props.pluginConfig,
       runtime: pluginRuntime,
       dispose: () => attention.dispose(),
     })

+ 1 - 2
packages/tui/src/plugin/runtime.tsx

@@ -4,7 +4,6 @@ import type {
   TuiPluginInstallResult,
   TuiPluginStatus,
 } from "@opencode-ai/plugin/tui"
-import type { TuiConfig } from "../config"
 import { createContext, createSignal, useContext, type JSX, type ParentProps } from "solid-js"
 import { createPluginRoutes } from "./api"
 import { createSlots, type HostSlots } from "./slots"
@@ -61,7 +60,7 @@ export type PluginRuntime = ReturnType<typeof createPluginRuntime>
 export type TuiPluginHost = {
   start(input: {
     api: TuiPluginApi
-    config: TuiConfig.Resolved
+    config: any
     runtime: PluginRuntime
     dispose?: () => void
   }): Promise<void>