summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-30 22:33:39 -0400
committerGitHub <[email protected]>2026-04-30 22:33:39 -0400
commit4c70ea28d2a44941ea65729863d0fa6e965321ce (patch)
treead7f3be405b80459dea9dde5075105dc6f586a08 /packages
parent5ba68a28c02a95e8359deefa9ee2806f84169e40 (diff)
downloadopencode-4c70ea28d2a44941ea65729863d0fa6e965321ce.tar.gz
opencode-4c70ea28d2a44941ea65729863d0fa6e965321ce.zip
fix(tui): scope Zed editor context to containing workspaces (#25211)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/editor-zed.ts13
-rw-r--r--packages/opencode/test/cli/tui/editor-context-zed.test.ts88
2 files changed, 96 insertions, 5 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts b/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts
index 5b7bf1cf4..6805f0b66 100644
--- a/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts
+++ b/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)
}
diff --git a/packages/opencode/test/cli/tui/editor-context-zed.test.ts b/packages/opencode/test/cli/tui/editor-context-zed.test.ts
index 9a9bca8c5..0287b0910 100644
--- a/packages/opencode/test/cli/tui/editor-context-zed.test.ts
+++ b/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 })