@@ -36,6 +36,7 @@ export interface SearchSessionOptions {
3636 includeHidden ?: boolean ;
3737 contextLines ?: number ;
3838 timeout ?: number ;
39+ earlyTermination ?: boolean ; // Stop search early when exact filename match is found
3940}
4041
4142/**
@@ -127,14 +128,16 @@ export interface SearchSessionOptions {
127128 sessionId,
128129 searchType : options . searchType ,
129130 hasTimeout : ! ! timeoutMs ,
130- timeoutMs
131+ timeoutMs,
132+ requestedPath : options . rootPath ,
133+ validatedPath : validPath
131134 } ) ;
132135
133136 // Wait for first chunk of data or early completion instead of fixed delay
134137 const firstChunk = new Promise < void > ( resolve => {
135- const onData = ( ) => {
136- session . process . stdout ?. off ( 'data' , onData ) ;
137- resolve ( ) ;
138+ const onData = ( ) => {
139+ session . process . stdout ?. off ( 'data' , onData ) ;
140+ resolve ( ) ;
138141 } ;
139142 session . process . stdout ?. once ( 'data' , onData ) ;
140143 setTimeout ( resolve , 40 ) ; // cap at 40ms instead of 50-100ms
@@ -189,27 +192,27 @@ export interface SearchSessionOptions {
189192 totalResults : session . totalMatches + session . totalContextLines ,
190193 totalMatches : session . totalMatches , // Actual matches only
191194 isComplete : session . isComplete ,
192- isError : session . isError ,
193- error : session . error ,
195+ isError : session . isError && ! ! session . error ?. trim ( ) , // Only error if we have actual errors
196+ error : session . error ?. trim ( ) || undefined ,
194197 hasMoreResults : false , // Tail always returns what's available
195198 runtime : Date . now ( ) - session . startTime
196199 } ;
197200 }
198-
201+
199202 // Handle positive offsets (range behavior) - like file reading
200203 const slicedResults = allResults . slice ( offset , offset + length ) ;
201204 const hasMoreResults = offset + length < allResults . length || ! session . isComplete ;
202-
205+
203206 session . lastReadTime = Date . now ( ) ;
204-
207+
205208 return {
206209 results : slicedResults ,
207210 returnedCount : slicedResults . length ,
208211 totalResults : session . totalMatches + session . totalContextLines ,
209212 totalMatches : session . totalMatches , // Actual matches only
210213 isComplete : session . isComplete ,
211- isError : session . isError ,
212- error : session . error ,
214+ isError : session . isError && ! ! session . error ?. trim ( ) , // Only error if we have actual errors
215+ error : session . error ?. trim ( ) || undefined ,
213216 hasMoreResults,
214217 runtime : Date . now ( ) - session . startTime
215218 } ;
@@ -387,25 +390,58 @@ export interface SearchSessionOptions {
387390
388391 process . stderr ?. on ( 'data' , ( data : Buffer ) => {
389392 const errorText = data . toString ( ) ;
390- session . error = ( session . error || '' ) + errorText ;
391- capture ( 'search_session_error' , {
392- sessionId : session . id ,
393- error : errorText . substring ( 0 , 200 ) // Limit error length for telemetry
394- } ) ;
393+
394+ // Filter meaningful errors
395+ const filteredErrors = errorText
396+ . split ( '\n' )
397+ . filter ( line => {
398+ const trimmed = line . trim ( ) ;
399+
400+ // Skip empty lines and lines with just symbols/numbers/colons
401+ if ( ! trimmed || trimmed . match ( / ^ [ \) \( \s \d : ] * $ / ) ) return false ;
402+
403+ // Skip all ripgrep system errors that start with "rg:"
404+ if ( trimmed . startsWith ( 'rg:' ) ) return false ;
405+
406+ return true ;
407+ } ) ;
408+
409+ // Only add to session.error if there are actual meaningful errors after filtering
410+ if ( filteredErrors . length > 0 ) {
411+ const meaningfulErrors = filteredErrors . join ( '\n' ) . trim ( ) ;
412+ if ( meaningfulErrors ) {
413+ session . error = ( session . error || '' ) + meaningfulErrors + '\n' ;
414+ capture ( 'search_session_error' , {
415+ sessionId : session . id ,
416+ error : meaningfulErrors . substring ( 0 , 200 )
417+ } ) ;
418+ }
419+ }
395420 } ) ;
396421
397422 process . on ( 'close' , ( code : number ) => {
398423 // Process any remaining buffer content
399424 if ( session . buffer . trim ( ) ) {
400425 this . processBufferedOutput ( session , true ) ;
401426 }
402-
427+
403428 session . isComplete = true ;
404-
405- if ( code !== 0 && code !== 1 ) {
406- // ripgrep returns 1 when no matches found, which is not an error
407- session . isError = true ;
408- session . error = session . error || `ripgrep exited with code ${ code } ` ;
429+
430+ // Only treat as error if:
431+ // 1. Unexpected exit code (not 0, 1, or 2) AND
432+ // 2. We have meaningful errors after filtering AND
433+ // 3. We found no results at all
434+ if ( code !== 0 && code !== 1 && code !== 2 ) {
435+ // Codes 0=success, 1=no matches, 2=some files couldn't be searched
436+ if ( session . error ?. trim ( ) && session . totalMatches === 0 ) {
437+ session . isError = true ;
438+ session . error = session . error || `ripgrep exited with code ${ code } ` ;
439+ }
440+ }
441+
442+ // If we have results, don't mark as error even if there were permission issues
443+ if ( session . totalMatches > 0 ) {
444+ session . isError = false ;
409445 }
410446
411447 capture ( 'search_session_completed' , {
@@ -455,6 +491,27 @@ export interface SearchSessionOptions {
455491 } else {
456492 session . totalMatches ++ ;
457493 }
494+
495+ // Early termination for exact filename matches (if enabled)
496+ if ( session . options . earlyTermination !== false && // Default to true
497+ session . options . searchType === 'files' &&
498+ this . isExactFilename ( session . options . pattern ) ) {
499+ const pat = path . normalize ( session . options . pattern ) ;
500+ const filePath = path . normalize ( result . file ) ;
501+ const ignoreCase = session . options . ignoreCase !== false ;
502+ const ends = ignoreCase
503+ ? filePath . toLowerCase ( ) . endsWith ( pat . toLowerCase ( ) )
504+ : filePath . endsWith ( pat ) ;
505+ if ( ends ) {
506+ // Found exact match, terminate search early
507+ setTimeout ( ( ) => {
508+ if ( ! session . process . killed ) {
509+ session . process . kill ( 'SIGTERM' ) ;
510+ }
511+ } , 100 ) ; // Small delay to allow any remaining results
512+ break ;
513+ }
514+ }
458515 }
459516 }
460517 }
0 commit comments