Sfoglia il codice sorgente

fix(tui): when diff viewer closes always return to last route (#28903)

James Long 2 mesi fa
parent
commit
bfb2d8dc76

+ 15 - 3
packages/opencode/src/cli/cmd/tui/feature-plugins/system/diff-viewer.tsx

@@ -1,5 +1,5 @@
 /** @jsxImportSource @opentui/solid */
-import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
+import type { TuiPlugin, TuiPluginApi, TuiRouteCurrent } from "@opencode-ai/plugin/tui"
 import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
 import { TextAttributes, type BorderSides, type BoxRenderable, type ScrollBoxRenderable } from "@opentui/core"
 import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
@@ -80,7 +80,12 @@ function DiffViewer(props: { api: TuiPluginApi }) {
   const theme = () => props.api.theme.current
   const params = () =>
     ("params" in props.api.route.current ? props.api.route.current.params : undefined) as
-      | { mode?: DiffMode; sessionID?: string; messageID?: string }
+      | {
+          mode?: DiffMode
+          sessionID?: string
+          messageID?: string
+          returnRoute?: TuiRouteCurrent
+        }
       | undefined
   const mode = () => params()?.mode ?? "git"
   const diffInput = createMemo(() => ({
@@ -369,8 +374,13 @@ function DiffViewer(props: { api: TuiPluginApi }) {
       title: "Close diff viewer",
       category: "VCS",
       run() {
+        const returnRoute = params()?.returnRoute
         props.api.ui.dialog.clear()
-        props.api.route.navigate("home")
+
+        props.api.route.navigate(
+          returnRoute?.name ?? "home",
+          returnRoute && "params" in returnRoute ? returnRoute.params : undefined,
+        )
       },
     },
     {
@@ -619,6 +629,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
               mode: option.value,
               sessionID: params()?.sessionID,
               messageID: params()?.messageID,
+              returnRoute: params()?.returnRoute,
             })
           },
         }))}
@@ -933,6 +944,7 @@ const tui: TuiPlugin = async (api) => {
           api.route.navigate(ROUTE, {
             mode: "git",
             sessionID: "params" in api.route.current ? api.route.current.params?.sessionID : undefined,
+            returnRoute: api.route.current,
           })
           api.ui.dialog.clear()
         },

+ 106 - 0
packages/opencode/test/cli/tui/diff-viewer.test.tsx

@@ -0,0 +1,106 @@
+/** @jsxImportSource @opentui/solid */
+import { expect, test } from "bun:test"
+import path from "path"
+import { mkdir } from "fs/promises"
+import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
+import { testRender, useRenderer } from "@opentui/solid"
+import { Global } from "@opencode-ai/core/global"
+import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
+import { KVProvider } from "../../../src/cli/cmd/tui/context/kv"
+import { ThemeProvider } from "../../../src/cli/cmd/tui/context/theme"
+import { TuiConfigProvider } from "../../../src/cli/cmd/tui/context/tui-config"
+import { OpencodeKeymapProvider } from "../../../src/cli/cmd/tui/keymap"
+import diffViewerPlugin from "../../../src/cli/cmd/tui/feature-plugins/system/diff-viewer"
+import { createTuiPluginApi } from "../../fixture/tui-plugin"
+import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
+
+test("closing the diff viewer returns to the route it opened from", async () => {
+  const startRoute: TuiRouteCurrent = { name: "session", params: { sessionID: "session-1" } }
+  const commands = new Map<string, NonNullable<Parameters<TuiPluginApi["keymap"]["registerLayer"]>[0]["commands"]>[number]>()
+  let current = startRoute
+  let renderDiff: TuiRouteDefinition["render"] | undefined
+  await mkdir(Global.Path.state, { recursive: true })
+  await Bun.write(path.join(Global.Path.state, "kv.json"), "{}")
+
+  function Harness() {
+    const renderer = useRenderer()
+    const keymap = createDefaultOpenTuiKeymap(renderer)
+    const registerLayer = keymap.registerLayer.bind(keymap)
+    keymap.registerLayer = (layer) => {
+      layer.commands?.forEach((command) => commands.set(command.name, command))
+      return registerLayer(layer)
+    }
+    const base = createTuiPluginApi({
+      keymap,
+      client: {
+        vcs: { diff: async () => ({ data: [] }) },
+        session: { diff: async () => ({ data: [] }) },
+      } as unknown as TuiPluginApi["client"],
+    })
+    const api = {
+      ...base,
+      route: {
+        register(routes) {
+          renderDiff = routes.find((route) => route.name === "diff")?.render
+          return () => {}
+        },
+        navigate(name, params) {
+          current = params ? { name, params } : { name }
+        },
+        get current() {
+          return current
+        },
+      },
+    } satisfies TuiPluginApi
+
+    void diffViewerPlugin.tui(api, undefined, pluginMeta)
+    commands.get("diff.open")?.run?.({} as never)
+
+    return (
+      <OpencodeKeymapProvider keymap={keymap}>
+        <TuiConfigProvider config={createTuiResolvedConfig()}>
+          <KVProvider>
+            <ThemeProvider mode="dark">{renderDiff?.({ params: "params" in current ? current.params : undefined })}</ThemeProvider>
+          </KVProvider>
+        </TuiConfigProvider>
+      </OpencodeKeymapProvider>
+    )
+  }
+
+  const app = await testRender(() => <Harness />, { width: 80, height: 20 })
+  try {
+    await waitForCommand(app, commands, "diff.close")
+    expect(current).toEqual({ name: "diff", params: { mode: "git", sessionID: "session-1", returnRoute: startRoute } })
+
+    expect(commands.has("diff.close")).toBe(true)
+    commands.get("diff.close")!.run?.({} as never)
+    expect(current).toEqual(startRoute)
+  } finally {
+    app.renderer.destroy()
+  }
+})
+
+async function waitForCommand(
+  app: Awaited<ReturnType<typeof testRender>>,
+  commands: Map<string, unknown>,
+  command: string,
+) {
+  for (let attempt = 0; attempt < 10; attempt++) {
+    await app.renderOnce()
+    if (commands.has(command)) return
+    await new Promise((resolve) => setTimeout(resolve, 25))
+  }
+}
+
+const pluginMeta = {
+  id: "diff-viewer",
+  source: "internal",
+  spec: "diff-viewer",
+  target: "diff-viewer",
+  first_time: 0,
+  last_time: 0,
+  time_changed: 0,
+  load_count: 1,
+  fingerprint: "test",
+  state: "same",
+} satisfies TuiPluginMeta