pull/13073/head
Dax Raad 2026-02-12 18:19:14 -05:00
parent 8ea1df1560
commit 8056842baf
3 changed files with 99 additions and 39 deletions

View File

@ -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,

View File

@ -548,43 +548,55 @@ export namespace File {
const kind = input.type ?? "all"
log.info("search", { query, kind })
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 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 (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)
if (!query) {
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 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
}

View File

@ -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)
},
})
})
})