feat(file): use FFF for file searches

This commit is contained in:
Dax Raad 2026-02-10 23:03:26 -05:00
commit 3c11eda350
4 changed files with 93 additions and 24 deletions

View file

@ -71,6 +71,7 @@
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
"@clack/prompts": "1.0.0-alpha.1",
"@ff-labs/bun": "0.1.37",
"@gitlab/gitlab-ai-provider": "3.5.0",
"@gitlab/opencode-gitlab-auth": "1.3.2",
"@hono/standard-validator": "0.1.5",

View file

@ -0,0 +1,33 @@
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
})
export async function search(input: { cwd: string; query: string; limit: number }) {
if (!input.query) return []
if (!base) base = input.cwd
if (!init()) return []
const result = FileFinder.search(input.query, {
pageIndex: 0,
pageSize: input.limit,
})
if (!result.ok) {
log.error("search failed", { error: result.error, query: input.query, cwd: input.cwd })
return []
}
return result.value.items.map((item: { relativePath: string }) => item.relativePath)
}
}

View file

@ -9,6 +9,7 @@ import ignore from "ignore"
import { Log } from "../util/log"
import { Filesystem } from "../util/filesystem"
import { Instance } from "../project/instance"
import { FFF } from "./fff"
import { Ripgrep } from "./ripgrep"
import fuzzysort from "fuzzysort"
import { Global } from "../global"
@ -547,35 +548,42 @@ export namespace File {
const kind = input.type ?? (input.dirs === false ? "file" : "all")
log.info("search", { query, kind })
const result = await state().then((x) => x.files())
const hidden = (item: string) => {
const normalized = item.replaceAll("\\", "/").replace(/\/+$/, "")
return normalized.split("/").some((p) => p.startsWith(".") && p.length > 1)
}
const preferHidden = query.startsWith(".") || query.includes("/.")
const sortHiddenLast = (items: string[]) => {
if (preferHidden) return items
const visible: string[] = []
const hiddenItems: string[] = []
for (const item of items) {
const isHidden = hidden(item)
if (isHidden) hiddenItems.push(item)
if (!isHidden) visible.push(item)
}
return [...visible, ...hiddenItems]
}
if (!query) {
const result = await state().then((x) => x.files())
if (kind === "file") return result.files.slice(0, limit)
return sortHiddenLast(result.dirs.toSorted()).slice(0, limit)
return result.dirs.toSorted().slice(0, limit)
}
const items =
kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs]
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)
log.info("search", { query, kind, results: output.length })
return output
}
const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((r) => r.target)
const output = kind === "directory" ? sortHiddenLast(sorted).slice(0, limit) : sorted
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 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 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