Skip to content
Open
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
155 changes: 89 additions & 66 deletions src/services/SearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,8 @@ export class SearchService {
return;
}

const args = ['--json', '-i', '-F', '--no-heading'];

if (includePattern) {
toGlobParts(includePattern).forEach(p => args.push('--glob', p));
}
if (excludePattern) {
toGlobParts(excludePattern).forEach(p => args.push('--glob', `!${p}`));
}

args.push('--', query, ...workspaceFolders.map(f => f.uri.fsPath));

const child = cp.spawn(this.rgPath!, args, { stdio: ['ignore', 'pipe', 'ignore'] });

token.onCancellationRequested(() => child.kill());

const pendingByFile = new Map<string, SearchMatch[]>();
let totalResults = 0;
let buffer = '';

const flush = () => {
if (pendingByFile.size === 0) return;
Expand All @@ -86,64 +70,103 @@ export class SearchService {
pendingByFile.clear();
};

child.stdout.on('data', (chunk: Buffer) => {
if (token.isCancellationRequested) return;
const createChunkHandler = () => {
let buffer = '';

return (chunk: Buffer) => {
if (token.isCancellationRequested) return;

buffer += chunk.toString('utf8');
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';

for (const line of lines) {
if (!line) continue;
let parsed: RgMatch;
try { parsed = JSON.parse(line); } catch { continue; }
if (parsed.type !== 'match') continue;

const filePath = parsed.data.path.text;
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath));
const relativePath = workspaceFolder
? vscode.workspace.asRelativePath(vscode.Uri.file(filePath), false)
: filePath;

const rawLine = parsed.data.lines.text;
const trimmedLine = rawLine.trimStart();
const leadingSpaces = rawLine.length - trimmedLine.length;
const preview = trimmedLine.trimEnd();

if (!pendingByFile.has(filePath)) {
pendingByFile.set(filePath, []);
}

for (const sub of parsed.data.submatches) {
pendingByFile.get(filePath)!.push({
filePath,
relativePath,
line: parsed.data.line_number,
column: sub.start,
preview,
previewColumn: Math.max(0, sub.start - leadingSpaces),
});
totalResults++;
}

if (pendingByFile.size >= FLUSH_THRESHOLD) {
flush();
}
}
};
};

buffer += chunk.toString('utf8');
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
let remainingChildren = workspaceFolders.length;
const finishChild = () => {
remainingChildren--;
if (remainingChildren === 0) {
flush();
resolve(totalResults > 0);
}
};

for (const line of lines) {
if (!line) continue;
let parsed: RgMatch;
try { parsed = JSON.parse(line); } catch { continue; }
if (parsed.type !== 'match') continue;
for (const folder of workspaceFolders) {
const args = buildRipgrepArgs(query, includePattern, excludePattern, folder.uri.fsPath);
const child = cp.spawn(this.rgPath!, args, {
cwd: folder.uri.fsPath,
stdio: ['ignore', 'pipe', 'ignore']
});

const filePath = parsed.data.path.text;
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath));
const relativePath = workspaceFolder
? vscode.workspace.asRelativePath(vscode.Uri.file(filePath), false)
: filePath;
token.onCancellationRequested(() => child.kill());
child.stdout.on('data', createChunkHandler());

const rawLine = parsed.data.lines.text;
const trimmedLine = rawLine.trimStart();
const leadingSpaces = rawLine.length - trimmedLine.length;
const preview = trimmedLine.trimEnd();
child.on('close', finishChild);

if (!pendingByFile.has(filePath)) {
pendingByFile.set(filePath, []);
}
child.on('error', (err: Error) => {
console.error('ripgrep error:', err);
finishChild();
});
}
});
}
}

for (const sub of parsed.data.submatches) {
pendingByFile.get(filePath)!.push({
filePath,
relativePath,
line: parsed.data.line_number,
column: sub.start,
preview,
previewColumn: Math.max(0, sub.start - leadingSpaces),
});
totalResults++;
}
export function buildRipgrepArgs(
query: string,
includePattern: string | undefined,
excludePattern: string | undefined,
workspacePath: string
): string[] {
const args = ['--json', '-i', '-F', '--no-heading'];

if (pendingByFile.size >= FLUSH_THRESHOLD) {
flush();
}
}
});

child.on('close', () => {
flush();
resolve(totalResults > 0);
});

child.on('error', (err: Error) => {
console.error('ripgrep error:', err);
flush();
resolve(totalResults > 0);
});
});
if (includePattern) {
toGlobParts(includePattern).forEach(p => args.push('--glob', p));
}
if (excludePattern) {
toGlobParts(excludePattern).forEach(p => args.push('--glob', `!${p}`));
}

args.push('--', query, workspacePath);
return args;
}

function toGlobParts(commaPattern: string): string[] {
Expand Down