| 94 | } |
| 95 | |
| 96 | function runRipgrep(rgArgs: string[], signal?: AbortSignal): Promise<LineSearchResult> { |
| 97 | return new Promise((resolve) => { |
| 98 | let stdout = ''; |
| 99 | let stderr = ''; |
| 100 | let proc; |
| 101 | try { |
| 102 | proc = spawn('rg', rgArgs, { signal }); |
| 103 | } catch (e: any) { |
| 104 | resolve({ error: `[ERROR] ${e.message}`, isError: true }); |
| 105 | return; |
| 106 | } |
| 107 | proc.stdout?.on('data', (d: Buffer) => { stdout += d; }); |
| 108 | proc.stderr?.on('data', (d: Buffer) => { stderr += d; }); |
| 109 | proc.on('close', (code, sig) => { |
| 110 | if (sig) { resolve({ error: `[CANCELLED] killed by ${sig}`, isError: true }); return; } |
| 111 | // ripgrep exits 1 when there are simply no matches — not an error. |
| 112 | if (code !== 0 && code !== 1) { |
| 113 | resolve({ error: `[ERROR] ripgrep exited ${code}: ${stderr.slice(0, 300)}`, isError: true }); |
| 114 | return; |
| 115 | } |
| 116 | resolve({ stdout, usedFallback: false }); |
| 117 | }); |
| 118 | proc.on('error', (e) => resolve({ error: `[ERROR] ${e.message}`, isError: true })); |
| 119 | }); |
| 120 | } |
| 121 | |
| 122 | /** Pure-JS line search. Exported for direct testing. Emits absolute `file:line:content`. */ |
| 123 | export async function jsLineSearch(cwd: string, opts: LineSearchOptions): Promise<string> { |