Skip to content

Commit bb3e65b

Browse files
committed
feat(opencode): fff tools for file search
1 parent 09d9cf0 commit bb3e65b

28 files changed

Lines changed: 1210 additions & 136 deletions

File tree

bun.lock

Lines changed: 19 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 = ["@ai-sdk/amazon-bedrock", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "gitlab-ai-provider"]
5+
minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "gitlab-ai-provider", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64"]
66

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

packages/core/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
"bun": "./src/pty/pty.bun.ts",
3333
"node": "./src/pty/pty.node.ts",
3434
"default": "./src/pty/pty.bun.ts"
35+
},
36+
"#fff": {
37+
"bun": "./src/filesystem/fff.bun.ts",
38+
"node": "./src/filesystem/fff.node.ts",
39+
"default": "./src/filesystem/fff.bun.ts"
3540
}
3641
},
3742
"devDependencies": {
@@ -81,6 +86,7 @@
8186
"@effect/platform-node": "catalog:",
8287
"@effect/sql-sqlite-bun": "catalog:",
8388
"@lydell/node-pty": "catalog:",
89+
"@ff-labs/fff-bun": "0.9.3",
8490
"@npmcli/arborist": "9.4.0",
8591
"@npmcli/config": "10.8.1",
8692
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
FileFinder,
3+
type DirItem,
4+
type DirSearchResult,
5+
type FileItem,
6+
type GrepCursor,
7+
type GrepMatch,
8+
type GrepResult,
9+
type InitOptions,
10+
type MixedItem,
11+
type MixedSearchResult,
12+
type SearchResult,
13+
} from "@ff-labs/fff-bun"
14+
15+
export type Result<T> = { ok: true; value: T } | { ok: false; error: string }
16+
17+
export type Init = InitOptions
18+
19+
export interface Search {
20+
items: FileItem[]
21+
scores: SearchResult["scores"]
22+
totalMatched: number
23+
totalFiles: number
24+
}
25+
26+
export interface DirSearch {
27+
items: DirItem[]
28+
scores: DirSearchResult["scores"]
29+
totalMatched: number
30+
totalDirs: number
31+
}
32+
33+
export interface MixedSearch {
34+
items: MixedItem[]
35+
scores: MixedSearchResult["scores"]
36+
totalMatched: number
37+
totalFiles: number
38+
totalDirs: number
39+
}
40+
41+
export type File = FileItem
42+
export type Directory = DirItem
43+
export type Mixed = MixedItem
44+
export type Cursor = GrepCursor | null
45+
export type Hit = GrepMatch
46+
47+
export interface Grep {
48+
items: GrepResult["items"]
49+
totalMatched: number
50+
totalFilesSearched: number
51+
totalFiles: number
52+
filteredFileCount: number
53+
nextCursor: Cursor
54+
regexFallbackError?: string
55+
}
56+
57+
export interface Picker {
58+
destroy(): void
59+
isScanning(): boolean
60+
waitForScan(timeoutMs?: number): Promise<Result<boolean>>
61+
refreshGitStatus(): Result<number>
62+
fileSearch(
63+
query: string,
64+
opts?: {
65+
currentFile?: string
66+
pageIndex?: number
67+
pageSize?: number
68+
},
69+
): Result<Search>
70+
glob(
71+
pattern: string,
72+
opts?: {
73+
currentFile?: string
74+
pageIndex?: number
75+
pageSize?: number
76+
},
77+
): Result<Search>
78+
directorySearch(
79+
query: string,
80+
opts?: {
81+
currentFile?: string
82+
pageIndex?: number
83+
pageSize?: number
84+
},
85+
): Result<DirSearch>
86+
mixedSearch(
87+
query: string,
88+
opts?: {
89+
currentFile?: string
90+
pageIndex?: number
91+
pageSize?: number
92+
},
93+
): Result<MixedSearch>
94+
grep(
95+
query: string,
96+
opts?: {
97+
mode?: "plain" | "regex" | "fuzzy"
98+
maxMatchesPerFile?: number
99+
timeBudgetMs?: number
100+
beforeContext?: number
101+
afterContext?: number
102+
cursor?: Cursor
103+
pageSize?: number
104+
},
105+
): Result<Grep>
106+
trackQuery(query: string, file: string): Result<boolean>
107+
getHistoricalQuery(offset: number): Result<string | null>
108+
}
109+
110+
export function available() {
111+
return FileFinder.isAvailable()
112+
}
113+
114+
export function create(opts: Init): Result<Picker> {
115+
const made = FileFinder.create(opts)
116+
if (!made.ok) return made
117+
const pick = made.value
118+
return {
119+
ok: true,
120+
value: {
121+
destroy: () => pick.destroy(),
122+
isScanning: () => pick.isScanning(),
123+
waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
124+
refreshGitStatus: () => pick.refreshGitStatus(),
125+
fileSearch: (query, next) => pick.fileSearch(query, next),
126+
glob: (pattern, next) => pick.glob(pattern, next),
127+
directorySearch: (query, next) => pick.directorySearch(query, next),
128+
mixedSearch: (query, next) => pick.mixedSearch(query, next),
129+
grep: (query, next) => pick.grep(query, next),
130+
trackQuery: (query, file) => pick.trackQuery(query, file),
131+
getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
132+
},
133+
}
134+
}
135+
136+
export * as Fff from "./fff.bun"
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
export type Result<T> = { ok: true; value: T } | { ok: false; error: string }
2+
3+
export interface Init {
4+
basePath: string
5+
frecencyDbPath?: string
6+
historyDbPath?: string
7+
useUnsafeNoLock?: boolean
8+
disableMmapCache?: boolean
9+
disableContentIndexing?: boolean
10+
disableWatch?: boolean
11+
aiMode?: boolean
12+
logFilePath?: string
13+
logLevel?: "trace" | "debug" | "info" | "warn" | "error"
14+
enableFsRootScanning?: boolean
15+
enableHomeDirScanning?: boolean
16+
}
17+
18+
export interface File {
19+
relativePath: string
20+
fileName: string
21+
modified: number
22+
}
23+
24+
export interface Directory {
25+
relativePath: string
26+
dirName: string
27+
maxAccessFrecency: number
28+
}
29+
30+
export type Mixed = { type: "file"; item: File } | { type: "directory"; item: Directory }
31+
32+
export interface Search {
33+
items: File[]
34+
scores: Array<{ total: number }>
35+
totalMatched: number
36+
totalFiles: number
37+
}
38+
39+
export interface DirSearch {
40+
items: Directory[]
41+
scores: Array<{ total: number }>
42+
totalMatched: number
43+
totalDirs: number
44+
}
45+
46+
export interface MixedSearch {
47+
items: Mixed[]
48+
scores: Array<{ total: number }>
49+
totalMatched: number
50+
totalFiles: number
51+
totalDirs: number
52+
}
53+
54+
export type Cursor = null
55+
56+
export interface Hit {
57+
relativePath: string
58+
fileName: string
59+
lineNumber: number
60+
byteOffset: number
61+
lineContent: string
62+
matchRanges: [number, number][]
63+
contextBefore?: string[]
64+
contextAfter?: string[]
65+
}
66+
67+
export interface Grep {
68+
items: Hit[]
69+
totalMatched: number
70+
totalFilesSearched: number
71+
totalFiles: number
72+
filteredFileCount: number
73+
nextCursor: Cursor
74+
regexFallbackError?: string
75+
}
76+
77+
export interface Picker {
78+
destroy(): void
79+
isScanning(): boolean
80+
waitForScan(timeoutMs?: number): Promise<Result<boolean>>
81+
refreshGitStatus(): Result<number>
82+
fileSearch(
83+
query: string,
84+
opts?: {
85+
currentFile?: string
86+
pageIndex?: number
87+
pageSize?: number
88+
},
89+
): Result<Search>
90+
glob(
91+
pattern: string,
92+
opts?: {
93+
currentFile?: string
94+
pageIndex?: number
95+
pageSize?: number
96+
},
97+
): Result<Search>
98+
directorySearch(
99+
query: string,
100+
opts?: {
101+
currentFile?: string
102+
pageIndex?: number
103+
pageSize?: number
104+
},
105+
): Result<DirSearch>
106+
mixedSearch(
107+
query: string,
108+
opts?: {
109+
currentFile?: string
110+
pageIndex?: number
111+
pageSize?: number
112+
},
113+
): Result<MixedSearch>
114+
grep(
115+
query: string,
116+
opts?: {
117+
mode?: "plain" | "regex" | "fuzzy"
118+
maxMatchesPerFile?: number
119+
timeBudgetMs?: number
120+
beforeContext?: number
121+
afterContext?: number
122+
cursor?: Cursor
123+
pageSize?: number
124+
},
125+
): Result<Grep>
126+
trackQuery(query: string, file: string): Result<boolean>
127+
getHistoricalQuery(offset: number): Result<string | null>
128+
}
129+
130+
export function available() {
131+
return false
132+
}
133+
134+
export function create(_opts: Init): Result<Picker> {
135+
return { ok: false, error: "fff unavailable on node runtime" }
136+
}
137+
138+
export * as Fff from "./fff.node"

0 commit comments

Comments
 (0)