diff --git a/packages/opencode/src/file/service.ts b/packages/opencode/src/file/service.ts index d4f6b347f81..467bc019630 100644 --- a/packages/opencode/src/file/service.ts +++ b/packages/opencode/src/file/service.ts @@ -379,20 +379,60 @@ export namespace File { next.dirs = Array.from(dirs).toSorted() } else { - const seen = new Set() - for await (const file of Ripgrep.files({ cwd: instance.directory })) { + const seenDirs = new Set() + const seenFiles = new Set() + const seenLinkedTargets = new Set() + const key = (value: string) => { + const normalized = value.replaceAll("\\", "/") + return process.platform === "win32" ? normalized.toLowerCase() : normalized + } + const addFile = (file: string) => { + const fileKey = key(file) + if (seenFiles.has(fileKey)) return + seenFiles.add(fileKey) next.files.push(file) + let current = file while (true) { const dir = path.dirname(current) if (dir === ".") break if (dir === current) break current = dir - if (seen.has(dir)) continue - seen.add(dir) + const dirKey = key(dir) + if (seenDirs.has(dirKey)) continue + seenDirs.add(dirKey) next.dirs.push(dir + "/") } } + + for await (const file of Ripgrep.files({ cwd: instance.directory })) { + addFile(file) + } + + const entries = await fs.promises + .readdir(instance.directory, { withFileTypes: true }) + .catch(() => [] as fs.Dirent[]) + + for (const entry of entries) { + if (!entry.isSymbolicLink()) continue + const linkPath = path.join(instance.directory, entry.name) + const target = await fs.promises.realpath(linkPath).catch(() => undefined) + if (!target) continue + const targetStat = await fs.promises.stat(target).catch(() => undefined) + if (!targetStat?.isDirectory()) continue + + const targetKey = key(target) + if (seenLinkedTargets.has(targetKey)) continue + seenLinkedTargets.add(targetKey) + + try { + for await (const linkedFile of Ripgrep.files({ cwd: linkPath })) { + addFile(path.join(entry.name, linkedFile)) + } + } catch { + continue + } + } } }) diff --git a/packages/opencode/test/file/index.test.ts b/packages/opencode/test/file/index.test.ts index 8f4cbe8688c..fc83c4e427b 100644 --- a/packages/opencode/test/file/index.test.ts +++ b/packages/opencode/test/file/index.test.ts @@ -785,6 +785,31 @@ describe("file/index Filesystem patterns", () => { }, }) }) + + test( + "indexes files inside linked directories", + async () => { + await using tmp = await setupSearchableRepo() + await using external = await tmpdir() + + await fs.mkdir(path.join(external.path, "linked"), { recursive: true }) + await fs.writeFile(path.join(external.path, "linked", "entry.ts"), "export const linked = true\n", "utf-8") + + await fs.symlink(external.path, path.join(tmp.path, "docs"), process.platform === "win32" ? "junction" : "dir") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await File.init() + + const result = await File.search({ query: "entry.ts", type: "file" }) + const normalized = result.map((item) => item.replaceAll("\\", "/")) + expect(normalized).toContain("docs/linked/entry.ts") + }, + }) + }, + { timeout: 30000 }, + ) }) describe("File.read() - diff/patch", () => {