diff --git a/packages/opencode/src/cli/cmd/debug/ripgrep.ts b/packages/opencode/src/cli/cmd/debug/ripgrep.ts index a4cebc5b8fa..033e3199ed3 100644 --- a/packages/opencode/src/cli/cmd/debug/ripgrep.ts +++ b/packages/opencode/src/cli/cmd/debug/ripgrep.ts @@ -48,9 +48,9 @@ const FilesCommand = cmd({ for await (const file of Ripgrep.files({ cwd: Instance.directory, glob: args.glob ? [args.glob] : undefined, + limit: args.limit, })) { files.push(file) - if (args.limit && files.length >= args.limit) break } process.stdout.write(files.join(EOL) + EOL) }) diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 834cbee1ed1..69374a1b748 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -211,6 +211,7 @@ export namespace Ripgrep { hidden?: boolean follow?: boolean maxDepth?: number + limit?: number }) { const args = [await filepath(), "--files", "--glob=!.git/*"] if (input.follow !== false) args.push("--follow") @@ -221,6 +222,7 @@ export namespace Ripgrep { args.push(`--glob=${g}`) } } + const cap = input.limit ?? Number.POSITIVE_INFINITY // Bun.spawn should throw this, but it incorrectly reports that the executable does not exist. // See https://github.com/oven-sh/bun/issues/24012 @@ -241,28 +243,41 @@ export namespace Ripgrep { const reader = proc.stdout.getReader() const decoder = new TextDecoder() - let buffer = "" - - try { - while (true) { - const { done, value } = await reader.read() - if (done) break - - buffer += decoder.decode(value, { stream: true }) - // Handle both Unix (\n) and Windows (\r\n) line endings - const lines = buffer.split(/\r?\n/) - buffer = lines.pop() || "" - - for (const line of lines) { - if (line) yield line - } + const state = { buffer: "", count: 0 } + + while (true) { + const res = await reader.read() + if (res.done) break + + state.buffer += decoder.decode(res.value, { stream: true }) + // Handle both Unix (\n) and Windows (\r\n) line endings + const lines = state.buffer.split(/\r?\n/) + state.buffer = lines.pop() || "" + + for (const line of lines) { + if (!line) continue + yield line + state.count += 1 + if (state.count < cap) continue + proc.kill() + reader.releaseLock() + await proc.exited + return } + } - if (buffer) yield buffer - } finally { - reader.releaseLock() - await proc.exited + if (state.buffer) { + yield state.buffer + state.count += 1 + if (state.count >= cap) { + reader.releaseLock() + await proc.exited + return + } } + + reader.releaseLock() + await proc.exited } export async function tree(input: { cwd: string; limit?: number }) { diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index dda57f6ee1b..daf310ffaa8 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -28,21 +28,20 @@ export const GlobTool = Tool.define("glob", { }, }) - let search = params.path ?? Instance.directory - search = path.isAbsolute(search) ? search : path.resolve(Instance.directory, search) + const base = params.path ?? Instance.directory + const search = path.isAbsolute(base) ? base : path.resolve(Instance.directory, base) await assertExternalDirectory(ctx, search, { kind: "directory" }) const limit = 100 + const hidden = params.pattern.startsWith(".") || params.pattern.includes("/.") || search.includes(`${path.sep}.`) const files = [] - let truncated = false for await (const file of Ripgrep.files({ cwd: search, glob: [params.pattern], + limit, + hidden, + follow: false, })) { - if (files.length >= limit) { - truncated = true - break - } const full = path.resolve(search, file) const stats = await Bun.file(full) .stat() @@ -54,6 +53,7 @@ export const GlobTool = Tool.define("glob", { }) } files.sort((a, b) => b.mtime - a.mtime) + const truncated = files.length >= limit const output = [] if (files.length === 0) output.push("No files found") diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts index cc3d750078f..8d07170562f 100644 --- a/packages/opencode/src/tool/ls.ts +++ b/packages/opencode/src/tool/ls.ts @@ -55,10 +55,16 @@ export const ListTool = Tool.define("list", { }) const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || []) + const hidden = searchPath.includes(`${path.sep}.`) const files = [] - for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs })) { + for await (const file of Ripgrep.files({ + cwd: searchPath, + glob: ignoreGlobs, + limit: LIMIT, + hidden, + follow: false, + })) { files.push(file) - if (files.length >= LIMIT) break } // Build directory structure