@@ -6,6 +6,7 @@ import DESCRIPTION from "./grep.txt"
66import { Instance } from "../project/instance"
77
88const MAX_LINE_LENGTH = 2000
9+ const MATCH_LIMIT = 100
910
1011export const GrepTool = Tool . define ( "grep" , {
1112 description : DESCRIPTION ,
@@ -33,51 +34,95 @@ export const GrepTool = Tool.define("grep", {
3334 stderr : "pipe" ,
3435 } )
3536
36- const output = await new Response ( proc . stdout ) . text ( )
37+ const reader = proc . stdout . getReader ( )
38+ const decoder = new TextDecoder ( )
39+ let buffer = ""
40+ const matches : Array < {
41+ path : string
42+ modTime : number
43+ lineNum : number
44+ lineText : string
45+ } > = [ ]
46+ let hitCollectLimit = false
47+
48+ try {
49+ while ( true ) {
50+ const { done, value } = await reader . read ( )
51+ if ( done ) break
52+
53+ buffer += decoder . decode ( value , { stream : true } )
54+ const lines = buffer . split ( "\n" )
55+ buffer = lines . pop ( ) || ""
56+
57+ for ( const line of lines ) {
58+ if ( ! line ) continue
59+ if ( matches . length >= MATCH_LIMIT ) {
60+ hitCollectLimit = true
61+ break
62+ }
63+
64+ const [ filePath , lineNumStr , ...lineTextParts ] = line . split ( "|" )
65+ if ( ! filePath || ! lineNumStr || lineTextParts . length === 0 ) continue
66+
67+ const lineNum = parseInt ( lineNumStr , 10 )
68+ const lineText = lineTextParts . join ( "|" )
69+
70+ const file = Bun . file ( filePath )
71+ const stats = await file . stat ( ) . catch ( ( ) => null )
72+ if ( ! stats ) continue
73+
74+ matches . push ( {
75+ path : filePath ,
76+ modTime : stats . mtime . getTime ( ) ,
77+ lineNum,
78+ lineText,
79+ } )
80+ }
81+
82+ if ( hitCollectLimit ) break
83+ }
84+
85+ if ( ! hitCollectLimit && buffer ) {
86+ const [ filePath , lineNumStr , ...lineTextParts ] = buffer . split ( "|" )
87+ if ( filePath && lineNumStr && lineTextParts . length > 0 ) {
88+ const lineNum = parseInt ( lineNumStr , 10 )
89+ const lineText = lineTextParts . join ( "|" )
90+ const file = Bun . file ( filePath )
91+ const stats = await file . stat ( ) . catch ( ( ) => null )
92+ if ( stats ) {
93+ matches . push ( {
94+ path : filePath ,
95+ modTime : stats . mtime . getTime ( ) ,
96+ lineNum,
97+ lineText,
98+ } )
99+ }
100+ }
101+ }
102+ } finally {
103+ if ( hitCollectLimit ) proc . kill ( )
104+ reader . releaseLock ( )
105+ }
106+
37107 const errorOutput = await new Response ( proc . stderr ) . text ( )
38108 const exitCode = await proc . exited
39109
40- if ( exitCode === 1 ) {
110+ if ( exitCode === 1 && matches . length === 0 ) {
41111 return {
42112 title : params . pattern ,
43113 metadata : { matches : 0 , truncated : false } ,
44114 output : "No files found" ,
45115 }
46116 }
47117
48- if ( exitCode !== 0 ) {
118+ if ( exitCode !== 0 && exitCode !== 1 && ! hitCollectLimit ) {
49119 throw new Error ( `ripgrep failed: ${ errorOutput } ` )
50120 }
51121
52- const lines = output . trim ( ) . split ( "\n" )
53- const matches = [ ]
54-
55- for ( const line of lines ) {
56- if ( ! line ) continue
57-
58- const [ filePath , lineNumStr , ...lineTextParts ] = line . split ( "|" )
59- if ( ! filePath || ! lineNumStr || lineTextParts . length === 0 ) continue
60-
61- const lineNum = parseInt ( lineNumStr , 10 )
62- const lineText = lineTextParts . join ( "|" )
63-
64- const file = Bun . file ( filePath )
65- const stats = await file . stat ( ) . catch ( ( ) => null )
66- if ( ! stats ) continue
67-
68- matches . push ( {
69- path : filePath ,
70- modTime : stats . mtime . getTime ( ) ,
71- lineNum,
72- lineText,
73- } )
74- }
75-
76122 matches . sort ( ( a , b ) => b . modTime - a . modTime )
77123
78- const limit = 100
79- const truncated = matches . length > limit
80- const finalMatches = truncated ? matches . slice ( 0 , limit ) : matches
124+ const truncated = matches . length > MATCH_LIMIT
125+ const finalMatches = truncated ? matches . slice ( 0 , MATCH_LIMIT ) : matches
81126
82127 if ( finalMatches . length === 0 ) {
83128 return {
@@ -103,7 +148,7 @@ export const GrepTool = Tool.define("grep", {
103148 outputLines . push ( ` Line ${ match . lineNum } : ${ truncatedLineText } ` )
104149 }
105150
106- if ( truncated ) {
151+ if ( truncated || hitCollectLimit ) {
107152 outputLines . push ( "" )
108153 outputLines . push ( "(Results are truncated. Consider using a more specific path or pattern.)" )
109154 }
@@ -112,7 +157,7 @@ export const GrepTool = Tool.define("grep", {
112157 title : params . pattern ,
113158 metadata : {
114159 matches : finalMatches . length ,
115- truncated,
160+ truncated : truncated || hitCollectLimit ,
116161 } ,
117162 output : outputLines . join ( "\n" ) ,
118163 }
0 commit comments