@@ -3,15 +3,27 @@ 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
11- // Default maximum number of results to display
12- const DEFAULT_MAX_RESULTS = 100 ;
13- const MAX_LINE_LENGTH = 1000 ;
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+ }
1425
26+ // procedure 1: search with ripgrep
1527export async function checkIfRipgrepIsInstalled ( ) : Promise < boolean > {
1628 try {
1729 await execPromise ( "rg --version" ) ;
@@ -21,6 +33,57 @@ export async function checkIfRipgrepIsInstalled(): Promise<boolean> {
2133 }
2234}
2335
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 -path "${ 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+
83+ // Default maximum number of results to display
84+ const DEFAULT_MAX_RESULTS = 100 ;
85+ const MAX_LINE_LENGTH = 1000 ;
86+
2487export const searchCodeTool : Tool = {
2588 name : "Search" ,
2689 displayName : "Search" ,
@@ -73,15 +136,26 @@ export const searchCodeTool: Tool = {
73136 ) ;
74137 }
75138
76- let command = `rg --line-number --with-filename --color never "${ args . pattern } "` ;
77-
78- if ( args . file_pattern ) {
79- command += ` -g "${ args . file_pattern } "` ;
80- }
81-
82- command += ` "${ searchPath } "` ;
139+ let stdout = "" ,
140+ stderr = "" ;
83141 try {
84- const { stdout, stderr } = await execPromise ( command ) ;
142+ if ( await checkIfRipgrepIsInstalled ( ) ) {
143+ const results = await searchWithRipgrep (
144+ args . pattern ,
145+ searchPath ,
146+ args . file_pattern ,
147+ ) ;
148+ stdout = results . stdout ;
149+ stderr = results . stderr ;
150+ } else {
151+ const results = await searchWithGrepOrFindstr (
152+ args . pattern ,
153+ searchPath ,
154+ args . file_pattern ,
155+ ) ;
156+ stdout = results . stdout ;
157+ stderr = results . stderr ;
158+ }
85159
86160 if ( stderr ) {
87161 return `Warning during search: ${ stderr } \n\n${ stdout } ` ;
@@ -121,13 +195,8 @@ export const searchCodeTool: Tool = {
121195 args . file_pattern ? ` in files matching "${ args . file_pattern } "` : ""
122196 } .`;
123197 }
124- if ( error instanceof Error ) {
125- if ( error . message . includes ( "command not found" ) ) {
126- throw new Error ( `ripgrep is not installed.` ) ;
127- }
128- }
129198 throw new Error (
130- `Error executing ripgrep : ${
199+ `Error executing search : ${
131200 error instanceof Error ? error . message : String ( error )
132201 } `,
133202 ) ;
0 commit comments