diff --git a/src/services/SearchService.ts b/src/services/SearchService.ts index e1a704e..76e2d40 100644 --- a/src/services/SearchService.ts +++ b/src/services/SearchService.ts @@ -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(); let totalResults = 0; - let buffer = ''; const flush = () => { if (pendingByFile.size === 0) return; @@ -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[] {