Skip to content

Commit b88f38f

Browse files
thdxrBenGu3
authored andcommitted
fix(core): expose partial filesystem scan results
1 parent ef77b00 commit b88f38f

3 files changed

Lines changed: 43 additions & 32 deletions

File tree

packages/core/src/filesystem/search.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export * as FileSystemSearch from "./search"
22

33
import path from "path"
4-
import { Context, Effect, Fiber, Layer, Scope } from "effect"
4+
import { Context, Effect, Layer, Scope } from "effect"
55
import { Fff } from "#fff"
66
import fuzzysort from "fuzzysort"
77
import { FileSystem } from "../filesystem"
@@ -28,22 +28,20 @@ export const ripgrepLayer = Layer.effect(
2828
const state = {
2929
files: [] as string[],
3030
directories: [] as string[],
31-
scan: undefined as Fiber.Fiber<void, never> | undefined,
3231
}
33-
state.scan = yield* ripgrep.find({ cwd: location.directory, pattern: "*", limit: 100_000 }).pipe(
34-
Effect.tap((result) =>
32+
const directories = new Set<string>()
33+
yield* ripgrep.find({
34+
cwd: location.directory,
35+
pattern: "*",
36+
limit: location.vcs ? Number.MAX_SAFE_INTEGER : 100_000,
37+
onEntry: (entry) =>
3538
Effect.sync(() => {
36-
state.files = result.map((item) => item.path)
37-
state.directories = Array.from(
38-
new Set(
39-
state.files.flatMap((file) => {
40-
const parts = file.split("/")
41-
return parts.slice(0, -1).map((_, index) => parts.slice(0, index + 1).join("/") + path.sep)
42-
}),
43-
),
44-
)
39+
state.files.push(entry.path)
40+
const parts = entry.path.split("/")
41+
parts.slice(0, -1).forEach((_, index) => directories.add(parts.slice(0, index + 1).join("/") + path.sep))
42+
state.directories = Array.from(directories)
4543
}),
46-
),
44+
}).pipe(
4745
Effect.orDie,
4846
Effect.asVoid,
4947
Effect.forkIn(scope),
@@ -104,7 +102,6 @@ export const ripgrepLayer = Layer.effect(
104102
}),
105103
find: (input) =>
106104
Effect.gen(function* () {
107-
if (input.query) yield* Fiber.join(state.scan!)
108105
const items =
109106
input.type === "file"
110107
? state.files

packages/core/src/ripgrep.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface FindInput {
5656
readonly hidden?: boolean
5757
readonly follow?: boolean
5858
readonly signal?: AbortSignal
59+
readonly onEntry?: (entry: Entry) => Effect.Effect<void>
5960
}
6061

6162
export interface GlobInput {
@@ -102,6 +103,7 @@ export const layer = Layer.effect(
102103
readonly signal?: AbortSignal
103104
readonly parse: (line: string) => Effect.Effect<A | undefined, Error>
104105
readonly pattern?: string
106+
readonly onItem?: (item: A) => Effect.Effect<void>
105107
}) => {
106108
const program = Effect.scoped(
107109
Effect.gen(function* () {
@@ -112,11 +114,16 @@ export const layer = Layer.effect(
112114
Effect.map((output) => output.buffer.toString("utf8")),
113115
Effect.forkScoped,
114116
)
117+
let observed = 0
115118
const rows = yield* Stream.decodeText(handle.stdout).pipe(
116119
Stream.splitLines,
117120
Stream.filter((line) => line.length > 0),
118121
Stream.mapEffect(input.parse),
119122
Stream.filter((row): row is A => row !== undefined),
123+
Stream.tap((row) => {
124+
if (!input.onItem || observed++ >= input.limit) return Effect.void
125+
return input.onItem(row)
126+
}),
120127
Stream.take(input.limit + 1),
121128
Stream.runCollect,
122129
Effect.map((chunk) => [...chunk]),
@@ -181,7 +188,7 @@ export const layer = Layer.effect(
181188
Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
182189
),
183190
find: (input) =>
184-
run<string>({
191+
run<Entry>({
185192
cwd: input.cwd,
186193
limit: input.limit,
187194
signal: input.signal,
@@ -194,24 +201,22 @@ export const layer = Layer.effect(
194201
`--glob=${input.pattern}`,
195202
".",
196203
],
197-
parse: (line) =>
198-
Effect.succeed(
199-
line
200-
.replace(/^(?:\.[\\/])+/u, "")
201-
.replace(/^[\\/]+/u, "")
202-
.replaceAll("\\", "/"),
203-
),
204-
}).pipe(
205-
Effect.map((result) =>
206-
result.items.map((relative) => {
207-
const absolute = path.resolve(input.cwd, relative)
208-
return new Entry({
204+
parse: (line) => {
205+
const relative = line
206+
.replace(/^(?:\.[\\/])+/u, "")
207+
.replace(/^[\\/]+/u, "")
208+
.replaceAll("\\", "/")
209+
return Effect.succeed(
210+
new Entry({
209211
path: RelativePath.make(relative),
210212
type: "file",
211-
mime: FSUtil.mimeType(absolute),
212-
})
213-
}),
214-
),
213+
mime: FSUtil.mimeType(path.resolve(input.cwd, relative)),
214+
}),
215+
)
216+
},
217+
onItem: input.onEntry,
218+
}).pipe(
219+
Effect.map((result) => result.items),
215220
Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
216221
),
217222
grep: (input) =>

packages/core/test/ripgrep.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ describe("Ripgrep", () => {
2525
expect(files.map((item) => item.path)).toContain(RelativePath.make(".opencode/config"))
2626
expect(files.map((item) => item.path)).toContain(RelativePath.make(".git/config"))
2727

28+
const observed: string[] = []
29+
const limited = yield* ripgrep.find({
30+
cwd: tmp.path,
31+
pattern: "**/*",
32+
limit: 1,
33+
onEntry: (entry) => Effect.sync(() => observed.push(entry.path)),
34+
})
35+
expect(observed).toEqual(limited.map((item) => item.path))
36+
2837
const matches = yield* ripgrep.grep({ cwd: tmp.path, pattern: "needle", include: "config", limit: 10 })
2938
expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".opencode/config"))
3039
expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".git/config"))

0 commit comments

Comments
 (0)