Skip to content

Commit 359023d

Browse files
feat: fff tools for file search
Co-Authored-By: Shoubhit Dash <shoubhit2005@gmail.com>
1 parent 0954966 commit 359023d

26 files changed

Lines changed: 1036 additions & 110 deletions

bun.lock

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bunfig.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
exact = true
33
# Only install newly resolved package versions published at least 3 days ago.
44
minimumReleaseAge = 259200
5-
minimumReleaseAgeExcludes = ["@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-x64", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid"]
5+
minimumReleaseAgeExcludes = ["@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-x64", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "@ff-labs/fff-node", "@ff-labs/fff-bun"]
66

77
[test]
88
root = "./do-not-run-tests-from-root"

packages/opencode/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
"node": "./src/storage/db.node.ts",
2929
"default": "./src/storage/db.bun.ts"
3030
},
31+
"#fff": {
32+
"bun": "./src/file/fff.bun.ts",
33+
"node": "./src/file/fff.node.ts",
34+
"default": "./src/file/fff.bun.ts"
35+
},
3136
"#pty": {
3237
"bun": "./src/pty/pty.bun.ts",
3338
"node": "./src/pty/pty.node.ts",
@@ -93,6 +98,8 @@
9398
"@clack/prompts": "1.0.0-alpha.1",
9499
"@effect/opentelemetry": "catalog:",
95100
"@effect/platform-node": "catalog:",
101+
"@ff-labs/fff-bun": "0.8.1",
102+
"@ff-labs/fff-node": "0.8.1",
96103
"@gitlab/opencode-gitlab-auth": "1.3.3",
97104
"@lydell/node-pty": "catalog:",
98105
"@modelcontextprotocol/sdk": "1.27.1",
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Effect, Layer } from "effect"
2+
import { Fff } from "#fff"
3+
import { AppRuntime } from "@/effect/app-runtime"
4+
import { Search } from "@/file/search"
5+
import { bootstrap } from "@/cli/bootstrap"
6+
7+
const dir = process.cwd()
8+
9+
const FILE_QUERIES = ["fff", "package.json", "tools/ experiment"]
10+
const GREP_QUERIES = ["FileFinder", "import", "grep", "autocomplete"]
11+
const GLOB_QUERIES = ["**/*.test.ts"]
12+
13+
const FILE_LIMIT = 100
14+
const GREP_LIMIT = 50
15+
const GLOB_LIMIT = 50
16+
17+
const search = <A>(effect: Effect.Effect<A, unknown, Search.Service>) => AppRuntime.runPromise(effect as never) as Promise<A>
18+
19+
await bootstrap(dir, async () => {
20+
// --- raw Fff picker ---
21+
const t0 = performance.now()
22+
const made = Fff.create({ basePath: dir, aiMode: true })
23+
if (!made.ok) {
24+
console.error("Fff.create failed:", made.error)
25+
process.exit(1)
26+
}
27+
const picker = made.value
28+
console.log(`picker create: ${(performance.now() - t0).toFixed(1)}ms`)
29+
30+
const tw = performance.now()
31+
const deadline = tw + 2500
32+
while (picker.isScanning() && performance.now() < deadline) {
33+
await new Promise((resolve) => setTimeout(resolve, 25))
34+
}
35+
console.log(`wait for scan (poll): ${(performance.now() - tw).toFixed(1)}ms`)
36+
37+
// warmup grep to let the content index build
38+
const tWarmup = performance.now()
39+
picker.grep("_warmup_", { mode: "regex", maxMatchesPerFile: 1, timeBudgetMs: 1_500 })
40+
console.log(`grep warmup: ${(performance.now() - tWarmup).toFixed(1)}ms`)
41+
42+
console.log()
43+
console.log("--- raw picker (warm) ---")
44+
45+
for (const q of FILE_QUERIES) {
46+
const t = performance.now()
47+
const r = picker.fileSearch(q, { pageSize: Math.max(FILE_LIMIT, 100) })
48+
const count = r.ok ? r.value.items.length : "err"
49+
console.log(`[picker] fileSearch "${q}": ${(performance.now() - t).toFixed(1)}ms (${count} results)`)
50+
}
51+
52+
for (const q of GREP_QUERIES) {
53+
const t = performance.now()
54+
const r = picker.grep(q, { mode: "regex", pageSize: GREP_LIMIT, timeBudgetMs: 1_500 })
55+
const count = r.ok ? r.value.items.length : "err"
56+
console.log(`[picker] grep "${q}": ${(performance.now() - t).toFixed(1)}ms (${count} matches)`)
57+
}
58+
59+
picker.destroy()
60+
61+
// --- Ripgrep service (via Search with file:["."] to force rg path) ---
62+
console.log()
63+
console.log("--- Ripgrep (via Search service) ---")
64+
65+
// warmup
66+
await search(Search.Service.use((svc) => svc.search({ cwd: dir, pattern: "_warmup_rg_", limit: 1, file: ["."] })))
67+
68+
for (const q of GREP_QUERIES) {
69+
const t = performance.now()
70+
const r = await search(Search.Service.use((svc) => svc.search({ cwd: dir, pattern: q, limit: GREP_LIMIT, file: ["."] })))
71+
console.log(
72+
`[ripgrep] grep "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.items.length} total, limit is per-file not total)`,
73+
)
74+
}
75+
76+
// --- Search service: init breakdown ---
77+
console.log()
78+
79+
// 1) runtime + InstanceState + picker create + scan poll
80+
const tRuntime = performance.now()
81+
await search(Search.Service.use((svc) => svc.file({ cwd: dir, query: "_warmup_file_", limit: 1 })))
82+
console.log(`[Search] init file (runtime + picker + scan): ${(performance.now() - tRuntime).toFixed(1)}ms`)
83+
84+
// 2) grep warmup (content index cold-start inside the Search service picker)
85+
const tGrepWarmup = performance.now()
86+
await search(Search.Service.use((svc) => svc.search({ cwd: dir, pattern: "_warmup_grep_", limit: 1 })))
87+
console.log(`[Search] init grep (content index warmup): ${(performance.now() - tGrepWarmup).toFixed(1)}ms`)
88+
89+
console.log()
90+
console.log("--- Search service (warm) ---")
91+
92+
for (const q of FILE_QUERIES) {
93+
const t = performance.now()
94+
const r = await search(Search.Service.use((svc) => svc.file({ cwd: dir, query: q, limit: FILE_LIMIT })))
95+
console.log(
96+
`[Search.file] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r?.length ?? "undefined (cache fallback)"} results)`,
97+
)
98+
}
99+
100+
for (const q of GREP_QUERIES) {
101+
const t = performance.now()
102+
const r = await search(Search.Service.use((svc) => svc.search({ cwd: dir, pattern: q, limit: GREP_LIMIT })))
103+
console.log(
104+
`[Search.search] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.items.length} matches, engine=${r.engine})`,
105+
)
106+
}
107+
108+
for (const q of GLOB_QUERIES) {
109+
const t = performance.now()
110+
const r = await search(Search.Service.use((svc) => svc.glob({ cwd: dir, pattern: q, limit: GLOB_LIMIT })))
111+
console.log(
112+
`[Search.glob] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.files.length} files, truncated=${r.truncated})`,
113+
)
114+
}
115+
})
116+
117+
process.exit(0)
118+

packages/opencode/src/cli/cmd/debug/file.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { EOL } from "os"
22
import { Effect } from "effect"
33
import { File } from "../../../file"
4-
import { Ripgrep } from "@/file/ripgrep"
4+
import { Search } from "@/file/search"
55
import { effectCmd } from "../../effect-cmd"
66
import { cmd } from "../cmd"
77

@@ -70,7 +70,7 @@ const FileTreeCommand = effectCmd({
7070
default: process.cwd(),
7171
}),
7272
handler: Effect.fn("Cli.debug.file.tree")(function* (args) {
73-
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
73+
const tree = yield* Effect.orDie(Search.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
7474
console.log(JSON.stringify(tree, null, 2))
7575
}),
7676
})

0 commit comments

Comments
 (0)