Dax Raad 2 luni în urmă
părinte
comite
8056842baf

+ 8 - 12
packages/opencode/src/file/fff.ts

@@ -1,23 +1,19 @@
 import { FileFinder } from "@ff-labs/bun"
 import { Log } from "@/util/log"
-import { lazy } from "../util/lazy"
 
 export namespace FFF {
   const log = Log.create({ service: "file.fff" })
-  let base = ""
-  const init = lazy(() => {
-    const result = FileFinder.init({ basePath: base })
-    if (!result.ok) {
-      log.error("init failed", { error: result.error, cwd: base })
-      return false
-    }
-    return true
-  })
+
+  const init = (cwd: string) => {
+    const result = FileFinder.init({ basePath: cwd })
+    if (result.ok) return true
+    log.error("init failed", { error: result.error, cwd })
+    return false
+  }
 
   export async function search(input: { cwd: string; query: string; limit: number }) {
     if (!input.query) return []
-    if (!base) base = input.cwd
-    if (!init()) return []
+    if (!init(input.cwd)) return []
 
     const result = FileFinder.search(input.query, {
       pageIndex: 0,

+ 37 - 25
packages/opencode/src/file/index.ts

@@ -548,43 +548,55 @@ export namespace File {
     const kind = input.type ?? "all"
     log.info("search", { query, kind })
 
+    const files = await FFF.search({
+      cwd: Instance.directory,
+      query,
+      limit: kind === "all" || kind === "directory" ? limit * 20 : limit,
+    })
+    const set = new Set<string>()
+    for (const file of files) {
+      let dir = path.dirname(file)
+      while (true) {
+        if (dir === ".") break
+        const next = path.dirname(dir)
+        set.add(dir + "/")
+        if (next === dir) break
+        dir = next
+      }
+    }
+    const allDirs = Array.from(set)
+
     if (!query) {
-      const result = await state().then((x) => x.files())
-      if (kind === "file") return result.files.slice(0, limit)
-      return result.dirs.toSorted().slice(0, limit)
+      const output = kind === "file" ? files.slice(0, limit) : allDirs.toSorted().slice(0, limit)
+      log.info("search", { query, kind, results: output.length })
+      return output
     }
 
     if (kind === "directory") {
-      const result = await state().then((x) => x.files())
-      const searchLimit = limit * 20
-      const output = fuzzysort
-        .go(query, result.dirs, { limit: searchLimit })
-        .map((r) => r.target)
-        .slice(0, limit)
+      const ranked: string[] = []
+      for (const item of fuzzysort.go(query, allDirs, { limit: limit * 20 })) {
+        ranked.push(item.target)
+      }
+      const output = ranked.slice(0, limit)
       log.info("search", { query, kind, results: output.length })
       return output
     }
 
-    const files = await FFF.search({
-      cwd: Instance.directory,
-      query,
-      limit,
-    })
-    const fileOutput = files.slice(0, limit)
     if (kind === "file") {
-      log.info("search", { query, kind, results: fileOutput.length })
-      return fileOutput
+      const output = files.slice(0, limit)
+      log.info("search", { query, kind, results: output.length })
+      return output
     }
 
-    const result = await state().then((x) => x.files())
-    const remaining = limit - fileOutput.length
-    if (remaining <= 0) {
-      log.info("search", { query, kind, results: fileOutput.length })
-      return fileOutput
+    const rankedDirs: string[] = []
+    for (const item of fuzzysort.go(query, allDirs, { limit })) {
+      rankedDirs.push(item.target)
+    }
+    const merged = files.slice(0, limit).concat(rankedDirs)
+    const output: string[] = []
+    for (const item of fuzzysort.go(query, merged, { limit })) {
+      output.push(item.target)
     }
-    const sorted = fuzzysort.go(query, result.dirs, { limit: remaining }).map((r) => r.target)
-    const output = fileOutput.concat(sorted)
-
     log.info("search", { query, kind, results: output.length })
     return output
   }

+ 52 - 0
packages/opencode/test/file/fff.test.ts

@@ -0,0 +1,52 @@
+import { describe, expect, test } from "bun:test"
+import path from "path"
+import { tmpdir } from "../fixture/fixture"
+import { FFF } from "../../src/file/fff"
+import { File } from "../../src/file"
+import { Instance } from "../../src/project/instance"
+
+describe("file.fff", () => {
+  test("returns files and supports directory search via File.search", async () => {
+    await using tmp = await tmpdir({
+      init: async (dir) => {
+        await Bun.write(path.join(dir, "src", "app", "index.ts"), "export const app = true")
+        await Bun.write(path.join(dir, "src", "app", "util.ts"), "export const util = true")
+        await Bun.write(path.join(dir, "docs", "guide.md"), "# guide")
+      },
+    })
+
+    const files = await FFF.search({
+      cwd: tmp.path,
+      query: "index",
+      limit: 20,
+    })
+    expect(files.includes(path.join("src", "app", "index.ts"))).toBe(true)
+
+    await Instance.provide({
+      directory: tmp.path,
+      fn: async () => {
+        const found = await File.search({
+          query: "index",
+          type: "file",
+          limit: 20,
+        })
+        expect(found.includes(path.join("src", "app", "index.ts"))).toBe(true)
+
+        const dirs = await File.search({
+          query: "app",
+          type: "directory",
+          limit: 20,
+        })
+        expect(dirs.includes("src/app/")).toBe(true)
+
+        const all = await File.search({
+          query: "app",
+          type: "all",
+          limit: 20,
+        })
+        expect(all.includes(path.join("src", "app", "index.ts"))).toBe(true)
+        expect(all.includes("src/app/")).toBe(true)
+      },
+    })
+  })
+})