@@ -3,11 +3,83 @@ import * as fs from "fs";
33import * as util from "util" ;
44
55import { ContinueError , ContinueErrorReason } from "core/util/errors.js" ;
6+ import { findUp } from "find-up" ;
67
78import { Tool } from "./types.js" ;
89
910const 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
1284const 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