Skip to content

Commit 9ba56ff

Browse files
committed
feat(cli): add grep/find/findstr as alternative search strategy
1 parent ab93032 commit 9ba56ff

File tree

1 file changed

+92
-14
lines changed

1 file changed

+92
-14
lines changed

extensions/cli/src/tools/searchCode.ts

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,83 @@ import * as fs from "fs";
33
import * as util from "util";
44

55
import { ContinueError, ContinueErrorReason } from "core/util/errors.js";
6+
import { findUp } from "find-up";
67

78
import { Tool } from "./types.js";
89

910
const execPromise = util.promisify(child_process.exec);
1011

12+
async function getGitignorePatterns() {
13+
const gitIgnorePath = await findUp(".gitignore");
14+
if (!gitIgnorePath) return [];
15+
const content = fs.readFileSync(gitIgnorePath, "utf-8");
16+
const ignorePatterns = [];
17+
for (let line of content.trim().split("\n")) {
18+
line = line.trim();
19+
if (line.startsWith("#") || line === "") continue; // ignore comments and empty line
20+
if (line.startsWith("!")) continue; // ignore negated ignores
21+
ignorePatterns.push(line);
22+
}
23+
return ignorePatterns;
24+
}
25+
26+
// procedure 1: search with ripgrep
27+
async function checkIfRipgrepIsInstalled(): Promise<boolean> {
28+
try {
29+
await execPromise("rg --version");
30+
return true;
31+
} catch {
32+
return false;
33+
}
34+
}
35+
36+
async function searchWithRipgrep(
37+
pattern: string,
38+
searchPath: string,
39+
filePattern?: string,
40+
) {
41+
let command = `rg --line-number --with-filename --color never "${pattern}"`;
42+
43+
if (filePattern) {
44+
command += ` -g "${filePattern}"`;
45+
}
46+
47+
const ignorePatterns = await getGitignorePatterns();
48+
for (const ignorePattern of ignorePatterns) {
49+
command += ` -g "!${ignorePattern}"`;
50+
}
51+
52+
command += ` "${searchPath}"`;
53+
const { stdout, stderr } = await execPromise(command);
54+
return { stdout, stderr };
55+
}
56+
57+
// procedure 2: search with grep on unix or findstr on windows
58+
async function searchWithGrepOrFindstr(
59+
pattern: string,
60+
searchPath: string,
61+
filePattern?: string,
62+
) {
63+
const isWindows = process.platform === "win32";
64+
const ignorePatterns = await getGitignorePatterns();
65+
let command: string;
66+
if (isWindows) {
67+
const fileSpec = filePattern ? filePattern : "*";
68+
command = `findstr /S /N /P /R "${pattern}" "${fileSpec}"`; // findstr does not support ignoring patterns
69+
} else {
70+
let excludeArgs = "";
71+
for (const ignorePattern of ignorePatterns) {
72+
excludeArgs += ` --exclude="${ignorePattern}" --exclude-dir="${ignorePattern}"`; // use both exclude and exclude-dir because ignorePattern can be a file or directory
73+
}
74+
if (filePattern) {
75+
command = `find . -type f -name "${filePattern}" -print0 | xargs -0 grep -nH -I${excludeArgs} "${pattern}"`;
76+
} else {
77+
command = `grep -R -n -H -I${excludeArgs} "${pattern}" .`;
78+
}
79+
}
80+
return await execPromise(command, { cwd: searchPath });
81+
}
82+
1183
// Default maximum number of results to display
1284
const DEFAULT_MAX_RESULTS = 100;
1385

@@ -63,15 +135,26 @@ export const searchCodeTool: Tool = {
63135
);
64136
}
65137

66-
let command = `rg --line-number --with-filename --color never "${args.pattern}"`;
67-
68-
if (args.file_pattern) {
69-
command += ` -g "${args.file_pattern}"`;
70-
}
71-
72-
command += ` "${searchPath}"`;
138+
let stdout = "",
139+
stderr = "";
73140
try {
74-
const { stdout, stderr } = await execPromise(command);
141+
if (await checkIfRipgrepIsInstalled()) {
142+
const results = await searchWithRipgrep(
143+
args.pattern,
144+
searchPath,
145+
args.file_pattern,
146+
);
147+
stdout = results.stdout;
148+
stderr = results.stderr;
149+
} else {
150+
const results = await searchWithGrepOrFindstr(
151+
args.pattern,
152+
searchPath,
153+
args.file_pattern,
154+
);
155+
stdout = results.stdout;
156+
stderr = results.stderr;
157+
}
75158

76159
if (stderr) {
77160
return `Warning during search: ${stderr}\n\n${stdout}`;
@@ -105,13 +188,8 @@ export const searchCodeTool: Tool = {
105188
args.file_pattern ? ` in files matching "${args.file_pattern}"` : ""
106189
}.`;
107190
}
108-
if (error instanceof Error) {
109-
if (error.message.includes("command not found")) {
110-
throw new Error(`ripgrep is not installed.`);
111-
}
112-
}
113191
throw new Error(
114-
`Error executing ripgrep: ${
192+
`Error executing search: ${
115193
error instanceof Error ? error.message : String(error)
116194
}`,
117195
);

0 commit comments

Comments
 (0)