Skip to content

Commit 5787cf9

Browse files
committed
fix after rebase
1 parent 6eb5397 commit 5787cf9

16 files changed

Lines changed: 5150 additions & 3351 deletions

File tree

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Fff } from "../../../file/fff"
33
import { Instance } from "../../../project/instance"
44
import { bootstrap } from "../../bootstrap"
55
import { cmd } from "../cmd"
6-
import { Glob } from "@/util/glob"
6+
import { Glob } from "@opencode-ai/shared/util/glob"
77

88
export const SearchCommand = cmd({
99
command: "search",
@@ -46,11 +46,13 @@ const FilesCommand = cmd({
4646
async handler(args) {
4747
await bootstrap(process.cwd(), async () => {
4848
const limit = args.limit ?? 100
49-
const files = (await Glob.scan("**/*", {
50-
cwd: Instance.directory,
51-
include: "file",
52-
dot: true,
53-
}))
49+
const files = (
50+
await Glob.scan("**/*", {
51+
cwd: Instance.directory,
52+
include: "file",
53+
dot: true,
54+
})
55+
)
5456
.map((x) => x.replaceAll("\\", "/"))
5557
.filter((x) => Fff.allowed({ rel: x, hidden: true, glob: args.glob ? [args.glob] : undefined }))
5658
.filter((x) => !args.query || x.includes(args.query))

packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -651,11 +651,7 @@ export function Autocomplete(props: {
651651
>
652652
<text
653653
fg={
654-
option().gitStatus
655-
? option().gitStatus === "modified"
656-
? theme.warning
657-
: theme.diffAdded
658-
: undefined
654+
option().gitStatus ? (option().gitStatus === "modified" ? theme.warning : theme.diffAdded) : undefined
659655
}
660656
flexShrink={0}
661657
>

packages/opencode/src/file/fff.ts

Lines changed: 78 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import fs from "fs/promises"
21
import path from "path"
32
import {
43
FileFinder,
@@ -12,10 +11,9 @@ import {
1211
} from "@ff-labs/fff-bun"
1312
import z from "zod"
1413
import { Global } from "../global"
15-
import { Instance } from "../project/instance"
16-
import { Filesystem } from "../util/filesystem"
17-
import { Glob } from "../util/glob"
18-
import { Log } from "../util/log"
14+
import { Glob } from "@opencode-ai/shared/util/glob"
15+
import { Filesystem, Log } from "../util"
16+
import { registerDisposer } from "../effect/instance-registry"
1917

2018
export namespace Fff {
2119
export const Match = z.object({
@@ -38,71 +36,95 @@ export namespace Fff {
3836
),
3937
})
4038

41-
const state = Instance.state(
42-
async () => ({
43-
map: new Map<string, FileFinder>(),
44-
pending: new Map<string, Promise<FileFinder>>(),
45-
}),
46-
async (state) => {
47-
for (const pick of state.map.values()) pick.destroy()
48-
},
49-
)
39+
const state = {
40+
map: new Map<string, FileFinder>(),
41+
// keep the state of the already indexed fff pickers
42+
// to avoid asking if it is finished scanned every time
43+
ready: new Set<string>(),
44+
}
45+
46+
registerDisposer(async (directory) => {
47+
const dir = Filesystem.resolve(directory)
48+
const pick = state.map.get(dir)
49+
if (!pick) return
50+
state.map.delete(dir)
51+
state.ready.delete(dir)
52+
53+
try {
54+
pick.destroy()
55+
} catch {}
56+
})
5057

5158
const root = path.join(Global.Path.cache, "fff")
5259

53-
async function dbs() {
54-
await fs.mkdir(root, { recursive: true })
55-
// fff databases are global across the file system
60+
function key(dir: string) {
61+
return Buffer.from(dir).toString("base64url")
62+
}
63+
64+
function dbs(dir: string) {
65+
const id = key(dir)
5666
return {
57-
frecency: path.join(root, "frecency.mdb"),
58-
history: path.join(root, "history.mdb"),
67+
frecency: path.join(root, `${id}.frecency.mdb`),
68+
history: path.join(root, `${id}.history.mdb`),
5969
}
6070
}
6171

62-
export async function picker(cwd: string) {
72+
export function picker(cwd: string) {
6373
const dir = Filesystem.resolve(cwd)
64-
const memo = await state()
65-
const cached = memo.map.get(dir)
74+
const cached = state.map.get(dir)
6675
if (cached) return cached
6776

68-
const wait = memo.pending.get(dir)
69-
if (wait) return wait
77+
const files = dbs(dir)
78+
const base = Log.file()
79+
const logfile = path.join(Global.Path.log, base ? "fff-" + path.basename(base) : "fff.log")
80+
const result = FileFinder.create({
81+
aiMode: true,
82+
basePath: dir,
83+
frecencyDbPath: files.frecency,
84+
historyDbPath: files.history,
85+
logFilePath: logfile,
86+
// fff uses the same log level
87+
logLevel: Log.currentLevel().toLowerCase() as "debug" | "info" | "warn" | "error",
88+
// if there is second project opened within the same sesion - disable
89+
// viertual memory mapping, the memory mapping address space is finite, so we
90+
// don't want to blow user's computer (the limit depends on repo size)
91+
cacheBudgetMaxFiles: state.map.size > 0 ? 0 : undefined,
92+
})
93+
94+
if (!result.ok) throw new Error(result.error)
95+
const pick = result.value
96+
state.map.set(dir, pick)
97+
return pick
98+
}
7099

71-
const next = (async () => {
72-
const files = await dbs()
73-
const base = Log.file()
74-
const logfile = path.join(Global.Path.log, base ? "fff-" + path.basename(base) : "fff.log")
75-
const result = FileFinder.create({
76-
aiMode: true,
77-
basePath: dir,
78-
frecencyDbPath: files.frecency,
79-
historyDbPath: files.history,
80-
logFilePath: logfile,
81-
logLevel: Log.currentLevel().toLowerCase() as "debug" | "info" | "warn" | "error",
82-
// if there is second project opened within the same sesion - disable
83-
// content mapping, the memory mapping address space is finite, so we
84-
// don't want to blow user's computer (the limit depends on repo size)
85-
cacheBudgetMaxFiles: memo.map.size > 0 ? 0 : undefined,
86-
})
87-
if (!result.ok) throw new Error(result.error)
88-
// we do not syncrhnously wait for the results here to not block anything
89-
// fff will do the indexing in the background and will automatically
90-
// become available
91-
const pick = result.value
92-
memo.map.set(dir, pick)
93-
return pick
94-
})()
100+
const FFF_WAIT_INTERVAL = 25
101+
async function waitScan(picker: FileFinder, timeoutMs: number) {
102+
const start = Date.now()
95103

96-
memo.pending.set(dir, next)
97-
try {
98-
return await next
99-
} finally {
100-
if (memo.pending.get(dir) === next) memo.pending.delete(dir)
104+
// becuase fff is a native library it doesn't touches event loop, so
105+
// poll for picker to be ready for returning the data if it is still scanning
106+
while (picker.isScanning()) {
107+
if (Date.now() - start >= timeoutMs) throw new Error("fff scan timeout")
108+
await new Promise<void>((resolve) => setTimeout(resolve, FFF_WAIT_INTERVAL))
109+
}
110+
}
111+
112+
async function open(cwd: string) {
113+
const dir = Filesystem.resolve(cwd)
114+
const pick = picker(cwd)
115+
116+
if (!state.ready.has(dir)) {
117+
await waitScan(pick, 5000)
118+
state.ready.add(dir)
119+
} else {
120+
pick.scanFiles()
121+
if (pick.isScanning()) await waitScan(pick, 5000)
101122
}
123+
return pick
102124
}
103125

104126
export async function files(input: { cwd: string; query: string; page?: number; size?: number; current?: string }) {
105-
const fff = await picker(input.cwd)
127+
const fff = await open(input.cwd)
106128
const out = fff.fileSearch(input.query, {
107129
pageIndex: input.page ?? 0,
108130
pageSize: input.size ?? 100,
@@ -113,7 +135,7 @@ export namespace Fff {
113135
}
114136

115137
export async function mixed(input: { cwd: string; query: string; page?: number; size?: number; current?: string }) {
116-
const fff = await picker(input.cwd)
138+
const fff = await open(input.cwd)
117139
const out = fff.mixedSearch(input.query, {
118140
pageIndex: input.page ?? 0,
119141
pageSize: input.size ?? 100,
@@ -133,7 +155,7 @@ export namespace Fff {
133155
budget?: number
134156
cursor?: GrepCursor | null
135157
}) {
136-
const pick = await picker(input.cwd)
158+
const pick = await open(input.cwd)
137159
const out = pick.grep(input.query, {
138160
mode: input.mode,
139161
maxMatchesPerFile: input.max,

packages/opencode/src/file/index.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import path from "path"
1111
import z from "zod"
1212
import { Global } from "../global"
1313
import { Instance } from "../project/instance"
14-
import { Glob } from "../util/glob"
14+
import { Glob } from "@opencode-ai/shared/util/glob"
1515
import { Log } from "../util"
1616
import { Fff } from "./fff"
1717
import { Protected } from "./protected"
@@ -396,15 +396,13 @@ export const layer = Layer.effect(
396396

397397
next.dirs = Array.from(dirs).toSorted()
398398
} else {
399-
const files = (
400-
yield* Effect.promise(() =>
401-
Glob.scan("**/*", {
402-
cwd: ctx.directory,
403-
include: "file",
404-
dot: true,
405-
}),
406-
)
407-
).toSorted((a, b) => a.localeCompare(b))
399+
const files = (yield* Effect.promise(() =>
400+
Glob.scan("**/*", {
401+
cwd: ctx.directory,
402+
include: "file",
403+
dot: true,
404+
}),
405+
)).toSorted((a, b) => a.localeCompare(b))
408406
const seen = new Set<string>()
409407
for (const file of files) {
410408
next.files.push(file)
@@ -712,9 +710,6 @@ export const layer = Layer.effect(
712710
}),
713711
)
714712

715-
export const defaultLayer = layer.pipe(
716-
Layer.provide(AppFileSystem.defaultLayer),
717-
Layer.provide(Git.defaultLayer),
718-
)
713+
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Git.defaultLayer))
719714

720715
export * as File from "."

0 commit comments

Comments
 (0)