Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/debug/ripgrep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down
53 changes: 34 additions & 19 deletions packages/opencode/src/file/ripgrep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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 }) {
Expand Down
14 changes: 7 additions & 7 deletions packages/opencode/src/tool/glob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
Expand Down
10 changes: 8 additions & 2 deletions packages/opencode/src/tool/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down