Parcourir la source

fix(tui): scope Zed editor context to containing workspaces (#25211)

Kit Langton il y a 4 mois
Parent
commit
4c70ea28d2

+ 10 - 3
packages/opencode/src/cli/cmd/tui/context/editor-zed.ts

@@ -189,13 +189,20 @@ export function resolveZedDbPath() {
     path.join(os.homedir(), ".local", "share", "zed", "db", "0-stable", "db.sqlite"),
   ].filter((item): item is string => Boolean(item))
 
-  return candidates.find((item) => Filesystem.stat(item)?.isFile())
+  return candidates.find((item) => isFile(item))
+}
+
+function isFile(item: string) {
+  try {
+    return Filesystem.stat(item)?.isFile() === true
+  } catch {
+    return false
+  }
 }
 
 function scoreZedWorkspace(workspacePaths: string | null, cwd: string) {
   return zedWorkspacePaths(workspacePaths).reduce((score, item) => {
-    if (pathContains(item, cwd)) return Math.max(score, 2)
-    if (pathContains(cwd, item)) return Math.max(score, 1)
+    if (pathContains(item, cwd)) return Math.max(score, path.resolve(item).length)
     return score
   }, 0)
 }

+ 86 - 2
packages/opencode/test/cli/tui/editor-context-zed.test.ts

@@ -1,7 +1,9 @@
 import { Database } from "bun:sqlite"
+import { mkdir, symlink } from "node:fs/promises"
+import os from "node:os"
 import path from "node:path"
-import { expect, test } from "bun:test"
-import { offsetToPosition, resolveZedSelection } from "../../../src/cli/cmd/tui/context/editor-zed"
+import { expect, spyOn, test } from "bun:test"
+import { offsetToPosition, resolveZedDbPath, resolveZedSelection } from "../../../src/cli/cmd/tui/context/editor-zed"
 import { tmpdir } from "../../fixture/fixture"
 
 type ZedFixtureOptions = {
@@ -66,6 +68,23 @@ test("offsetToPosition converts Zed offsets to 1-based editor positions", () =>
   })
 })
 
+test("resolveZedDbPath skips candidates that cannot be stated", async () => {
+  await using tmp = await tmpdir()
+  const loop = path.join(tmp.path, "loop")
+  await symlink(loop, loop)
+  const home = spyOn(os, "homedir").mockImplementation(() => tmp.path)
+  const previous = process.env.OPENCODE_ZED_DB
+  process.env.OPENCODE_ZED_DB = loop
+
+  try {
+    expect(resolveZedDbPath()).toBeUndefined()
+  } finally {
+    if (previous === undefined) delete process.env.OPENCODE_ZED_DB
+    else process.env.OPENCODE_ZED_DB = previous
+    home.mockRestore()
+  }
+})
+
 test("resolveZedSelection returns active editor selection", async () => {
   await using tmp = await tmpdir()
   const fixture = await writeZedFixture(tmp.path)
@@ -251,6 +270,71 @@ test("resolveZedSelection returns empty when no workspace matches", async () =>
   expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "empty" })
 })
 
+test("resolveZedSelection matches a Zed workspace that contains the session directory", async () => {
+  await using tmp = await tmpdir()
+  const fixture = await writeZedFixture(tmp.path)
+
+  expect(await resolveZedSelection(fixture.dbPath, path.join(tmp.path, "packages", "app"))).toEqual({
+    type: "selection",
+    selection: {
+      filePath: fixture.filePath,
+      source: "zed",
+      ranges: [
+        {
+          text: "two",
+          selection: {
+            start: { line: 2, character: 1 },
+            end: { line: 2, character: 4 },
+          },
+        },
+      ],
+    },
+  })
+})
+
+test("resolveZedSelection prefers the most specific containing Zed workspace", async () => {
+  await using tmp = await tmpdir()
+  const fixture = await writeZedFixture(tmp.path)
+  const child = path.join(tmp.path, "packages")
+  const childFile = path.join(child, "child.ts")
+  await mkdir(child, { recursive: true })
+  await Bun.write(childFile, "child")
+
+  const db = new Database(fixture.dbPath)
+  db.run("insert into workspaces values (2, ?, ?)", [JSON.stringify([child]), "2026-01-01"])
+  db.run("insert into panes values (2, 2, 1)")
+  db.run("insert into items values (2, 2, 2, 1, ?)", ["Editor"])
+  db.run("insert into editors values (2, 2, ?, ?)", [childFile, "child"])
+  db.run("insert into editor_selections values (2, 2, 0, 5)")
+  db.close()
+
+  expect(await resolveZedSelection(fixture.dbPath, path.join(child, "app"))).toEqual({
+    type: "selection",
+    selection: {
+      filePath: childFile,
+      source: "zed",
+      ranges: [
+        {
+          text: "child",
+          selection: {
+            start: { line: 1, character: 1 },
+            end: { line: 1, character: 6 },
+          },
+        },
+      ],
+    },
+  })
+})
+
+test("resolveZedSelection ignores a Zed workspace nested inside the session directory", async () => {
+  await using tmp = await tmpdir()
+  const child = path.join(tmp.path, "effect-lab")
+  await mkdir(child, { recursive: true })
+  const fixture = await writeZedFixture(child)
+
+  expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "empty" })
+})
+
 test("resolveZedSelection returns unavailable when a Zed terminal is active", async () => {
   await using tmp = await tmpdir()
   const fixture = await writeZedFixture(tmp.path, { itemKind: "Terminal", editor: false })