mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 21:31:08 -05:00
Add search_files tool
This commit is contained in:
203
src/utils/ripgrep.ts
Normal file
203
src/utils/ripgrep.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as childProcess from "child_process"
|
||||
import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
|
||||
/*
|
||||
This file provides functionality to perform regex searches on files using ripgrep.
|
||||
Inspired by: https://github.com/DiscreteTom/vscode-ripgrep-utils
|
||||
|
||||
Key components:
|
||||
1. getBinPath: Locates the ripgrep binary within the VSCode installation.
|
||||
2. execRipgrep: Executes the ripgrep command and returns the output.
|
||||
3. regexSearchFiles: The main function that performs regex searches on files.
|
||||
- Parameters:
|
||||
* cwd: The current working directory (for relative path calculation)
|
||||
* directoryPath: The directory to search in
|
||||
* regex: The regular expression to search for (Rust regex syntax)
|
||||
* filePattern: Optional glob pattern to filter files (default: '*')
|
||||
- Returns: A formatted string containing search results with context
|
||||
|
||||
The search results include:
|
||||
- Relative file paths
|
||||
- 2 lines of context before and after each match
|
||||
- Matches formatted with pipe characters for easy reading
|
||||
|
||||
Usage example:
|
||||
const results = await regexSearchFiles('/path/to/cwd', '/path/to/search', 'TODO:', '*.ts');
|
||||
|
||||
rel/path/to/app.ts
|
||||
│----
|
||||
│function processData(data: any) {
|
||||
│ // Some processing logic here
|
||||
│ // TODO: Implement error handling
|
||||
│ return processedData;
|
||||
│}
|
||||
│----
|
||||
|
||||
rel/path/to/helper.ts
|
||||
│----
|
||||
│ let result = 0;
|
||||
│ for (let i = 0; i < input; i++) {
|
||||
│ // TODO: Optimize this function for performance
|
||||
│ result += Math.pow(i, 2);
|
||||
│ }
|
||||
│----
|
||||
*/
|
||||
|
||||
const isWindows = /^win/.test(process.platform)
|
||||
const binName = isWindows ? "rg.exe" : "rg"
|
||||
|
||||
interface SearchResult {
|
||||
file: string
|
||||
line: number
|
||||
column: number
|
||||
match: string
|
||||
beforeContext: string[]
|
||||
afterContext: string[]
|
||||
}
|
||||
|
||||
async function getBinPath(vscodeAppRoot: string): Promise<string | undefined> {
|
||||
const checkPath = async (pkgFolder: string) => {
|
||||
const fullPath = path.join(vscodeAppRoot, pkgFolder, binName)
|
||||
return (await pathExists(fullPath)) ? fullPath : undefined
|
||||
}
|
||||
|
||||
return (
|
||||
(await checkPath("node_modules/@vscode/ripgrep/bin/")) ||
|
||||
(await checkPath("node_modules/vscode-ripgrep/bin")) ||
|
||||
(await checkPath("node_modules.asar.unpacked/vscode-ripgrep/bin/")) ||
|
||||
(await checkPath("node_modules.asar.unpacked/@vscode/ripgrep/bin/"))
|
||||
)
|
||||
}
|
||||
|
||||
async function pathExists(path: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
fs.access(path, (err) => {
|
||||
resolve(err === null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function execRipgrep(bin: string, args: string[]): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = childProcess.spawn(bin, args)
|
||||
let output = ""
|
||||
let errorOutput = ""
|
||||
|
||||
process.stdout.on("data", (data) => {
|
||||
output += data.toString()
|
||||
})
|
||||
|
||||
process.stderr.on("data", (data) => {
|
||||
errorOutput += data.toString()
|
||||
})
|
||||
|
||||
process.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve(output)
|
||||
} else {
|
||||
reject(new Error(`ripgrep process exited with code ${code}: ${errorOutput}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function regexSearchFiles(
|
||||
cwd: string,
|
||||
directoryPath: string,
|
||||
regex: string,
|
||||
filePattern?: string
|
||||
): Promise<string> {
|
||||
const vscodeAppRoot = vscode.env.appRoot
|
||||
const rgPath = await getBinPath(vscodeAppRoot)
|
||||
|
||||
if (!rgPath) {
|
||||
throw new Error("Could not find ripgrep binary")
|
||||
}
|
||||
|
||||
const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", directoryPath]
|
||||
|
||||
let output: string
|
||||
try {
|
||||
output = await execRipgrep(rgPath, args)
|
||||
} catch {
|
||||
return "No results found"
|
||||
}
|
||||
const results: SearchResult[] = []
|
||||
let currentResult: Partial<SearchResult> | null = null
|
||||
|
||||
output.split("\n").forEach((line) => {
|
||||
if (line) {
|
||||
try {
|
||||
const parsed = JSON.parse(line)
|
||||
if (parsed.type === "match") {
|
||||
if (currentResult) {
|
||||
results.push(currentResult as SearchResult)
|
||||
}
|
||||
currentResult = {
|
||||
file: parsed.data.path.text,
|
||||
line: parsed.data.line_number,
|
||||
column: parsed.data.submatches[0].start,
|
||||
match: parsed.data.lines.text,
|
||||
beforeContext: [],
|
||||
afterContext: [],
|
||||
}
|
||||
} else if (parsed.type === "context" && currentResult) {
|
||||
if (parsed.data.line_number < currentResult.line!) {
|
||||
currentResult.beforeContext!.push(parsed.data.lines.text)
|
||||
} else {
|
||||
currentResult.afterContext!.push(parsed.data.lines.text)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing ripgrep output:", error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (currentResult) {
|
||||
results.push(currentResult as SearchResult)
|
||||
}
|
||||
|
||||
return formatResults(results, cwd)
|
||||
}
|
||||
|
||||
function formatResults(results: SearchResult[], cwd: string): string {
|
||||
const groupedResults: { [key: string]: SearchResult[] } = {}
|
||||
|
||||
let output = ""
|
||||
if (results.length >= 300) {
|
||||
output += `Showing first 300 of ${results.length.toLocaleString()} results, use a more specific search if necessary...\n\n`
|
||||
} else {
|
||||
output += `Found ${results.length.toLocaleString()} results...\n\n`
|
||||
}
|
||||
|
||||
// Group results by file name
|
||||
results.slice(0, 300).forEach((result) => {
|
||||
const relativeFilePath = path.relative(cwd, result.file)
|
||||
if (!groupedResults[relativeFilePath]) {
|
||||
groupedResults[relativeFilePath] = []
|
||||
}
|
||||
groupedResults[relativeFilePath].push(result)
|
||||
})
|
||||
|
||||
for (const [filePath, fileResults] of Object.entries(groupedResults)) {
|
||||
output += `${filePath}\n│----\n`
|
||||
|
||||
fileResults.forEach((result, index) => {
|
||||
const allLines = [...result.beforeContext, result.match, ...result.afterContext]
|
||||
allLines.forEach((line) => {
|
||||
output += `│${line?.trimEnd() ?? ""}\n`
|
||||
})
|
||||
|
||||
if (index < fileResults.length - 1) {
|
||||
output += "│----\n"
|
||||
}
|
||||
})
|
||||
|
||||
output += "│----\n\n"
|
||||
}
|
||||
|
||||
return output.trim()
|
||||
}
|
||||
Reference in New Issue
Block a user