src / ripgrep.ts
import { readFile } from "fs/promises";
import * as path from "path";
import { existsSync, statSync } from "fs";
interface SearchResult {
filePath: string;
line: number;
column: number;
match: string;
beforeContext: string[];
afterContext: string[];
}
const MAX_RESULTS = 300;
const MAX_FILE_SIZE_SEARCH = 2 * 1024 * 1024; // Skip files larger than 2MB
export async function regexSearchFiles(
cwd: string,
directoryPath: string,
regexPattern: string,
filePattern: string = "*"
): Promise<string> {
try {
let globby;
try {
const module = await import("globby");
globby = module.globby;
} catch (e) {
return "Error: Dependency 'globby' not found. Please run 'npm install globby' in the plugin directory.";
}
const regex = new RegExp(regexPattern, "g");
const absoluteDirPath = path.resolve(cwd, directoryPath);
const normalizedDir = absoluteDirPath.split(path.sep).join("/");
const globPattern = filePattern
? path.posix.join(normalizedDir, filePattern)
: path.posix.join(normalizedDir, "**/*");
const files = await globby(globPattern, {
cwd: absoluteDirPath,
absolute: true,
onlyFiles: true,
dot: true,
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"]
});
const results: SearchResult[] = [];
let matchCount = 0;
for (const filePath of files) {
if (matchCount >= MAX_RESULTS) break;
try {
const stats = statSync(filePath);
if (stats.size > MAX_FILE_SIZE_SEARCH) continue;
const content = await readFile(filePath, "utf-8");
const lines = content.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
regex.lastIndex = 0;
const match = regex.exec(line);
if (match) {
matchCount++;
const beforeContext = i > 0 ? [lines[i - 1]] : [];
const afterContext = i < lines.length - 1 ? [lines[i + 1]] : [];
results.push({
filePath: filePath,
line: i + 1,
column: match.index + 1,
match: line,
beforeContext,
afterContext
});
if (matchCount >= MAX_RESULTS) break;
}
}
} catch (err) {
continue;
}
}
if (results.length === 0) return "No results found";
return formatResults(results, cwd);
} catch (error: any) {
return `Search Error: ${error.message}`;
}
}
const MAX_OUTPUT_MB = 0.5;
const MAX_BYTE_SIZE = MAX_OUTPUT_MB * 1024 * 1024;
function formatResults(results: SearchResult[], cwd: string): string {
const groupedResults: { [key: string]: SearchResult[] } = {};
const output: string[] = [];
if (results.length >= MAX_RESULTS) {
output.push(`Showing first ${MAX_RESULTS} results. Use a more specific search if necessary.\n\n`);
} else {
output.push(`Found ${results.length === 1 ? "1 result" : `${results.length.toLocaleString()} results`}.\n\n`);
}
results.forEach((result) => {
const relativeFilePath = path.relative(cwd, result.filePath);
if (!groupedResults[relativeFilePath]) {
groupedResults[relativeFilePath] = [];
}
groupedResults[relativeFilePath].push(result);
});
let byteSize = Buffer.byteLength(output.join(""), "utf8");
let wasLimitReached = false;
for (const [filePath, fileResults] of Object.entries(groupedResults)) {
const filePathString = `${filePath.replace(/\\/g, "/")}\n│----\n`;
const filePathBytes = Buffer.byteLength(filePathString, "utf8");
if (byteSize + filePathBytes >= MAX_BYTE_SIZE) {
wasLimitReached = true;
break;
}
output.push(filePathString);
byteSize += filePathBytes;
for (let i = 0; i < fileResults.length; i++) {
const res = fileResults[i];
const allLines = [...res.beforeContext, res.match, ...res.afterContext];
const resultLines: string[] = [];
let resultBytes = 0;
for (const line of allLines) {
const trimmed = line.trimEnd();
const str = `│${trimmed}\n`;
const len = Buffer.byteLength(str, "utf8");
resultLines.push(str);
resultBytes += len;
}
if (byteSize + resultBytes >= MAX_BYTE_SIZE) {
wasLimitReached = true;
break;
}
output.push(...resultLines);
byteSize += resultBytes;
if (i < fileResults.length - 1) {
const sep = "│----\n";
const sepLen = Buffer.byteLength(sep, "utf8");
if (byteSize + sepLen >= MAX_BYTE_SIZE) {
wasLimitReached = true;
break;
}
output.push(sep);
byteSize += sepLen;
}
}
if (wasLimitReached) break;
const closing = "│----\n\n";
const closingLen = Buffer.byteLength(closing, "utf8");
if (byteSize + closingLen >= MAX_BYTE_SIZE) {
wasLimitReached = true;
break;
}
output.push(closing);
byteSize += closingLen;
}
if (wasLimitReached) {
output.push(`\n[Results truncated due to exceeding ${MAX_OUTPUT_MB}MB limit.]`);
}
return output.join("").trim();
}
export async function getBinPath(): Promise<string | undefined> {
return "INTERNAL_JS_SEARCH";
}