@@ -264,6 +264,121 @@ func (fm *FileManager) ListFiles() ([]string, error) {
264264 return files , nil
265265}
266266
267+ // SearchFilesContent searches for text within the workspace, trying multiple search terms in order of specificity.
268+ func (fm * FileManager ) SearchFilesContent (searchTerms []string ) ([]string , error ) {
269+ var exactResults []string
270+ var tokenResults []string
271+
272+ var lowerTerms []string
273+ var tokenSets [][]string
274+
275+ for _ , term := range searchTerms {
276+ lower := strings .ToLower (term )
277+ lowerTerms = append (lowerTerms , lower )
278+
279+ // Clean up tokens by removing common delimiters
280+ cleanTerm := lower
281+ cleanTerm = strings .ReplaceAll (cleanTerm , "_" , " " )
282+ cleanTerm = strings .ReplaceAll (cleanTerm , "-" , " " )
283+ cleanTerm = strings .ReplaceAll (cleanTerm , "." , " " )
284+ tokens := strings .Fields (cleanTerm )
285+ if len (tokens ) > 0 {
286+ tokenSets = append (tokenSets , tokens )
287+ }
288+ }
289+
290+ // Extensions to exclude
291+ excludedExts := map [string ]bool {
292+ ".ico" : true , ".png" : true , ".jpg" : true , ".jpeg" : true , ".bmp" : true ,
293+ ".gif" : true , ".svg" : true , ".webp" : true , ".exe" : true , ".dll" : true ,
294+ ".pdf" : true , ".zip" : true , ".tar" : true , ".gz" : true , ".bin" : true ,
295+ ".so" : true , ".dylib" : true , ".class" : true , ".obj" : true , ".o" : true ,
296+ }
297+
298+ err := filepath .Walk (fm .workspaceDir , func (path string , info os.FileInfo , err error ) error {
299+ if err != nil {
300+ return err
301+ }
302+
303+ // Skip directories and hidden files/folders
304+ if info .IsDir () {
305+ name := filepath .Base (path )
306+ // Skip hidden dirs, node_modules, vendor etc. to speed up search
307+ if name [0 ] == '.' || name == "node_modules" || name == "vendor" || name == ".git" {
308+ return filepath .SkipDir
309+ }
310+ return nil
311+ }
312+
313+ // Skip hidden files
314+ if filepath .Base (path )[0 ] == '.' {
315+ return nil
316+ }
317+
318+ // Skip excluded file types
319+ ext := strings .ToLower (filepath .Ext (path ))
320+ if excludedExts [ext ] {
321+ return nil
322+ }
323+
324+ // Skip large files (> 1MB)
325+ if info .Size () > 1024 * 1024 {
326+ return nil
327+ }
328+
329+ content , err := os .ReadFile (path )
330+ if err != nil {
331+ return nil
332+ }
333+
334+ lowerContent := strings .ToLower (string (content ))
335+ relPath , err := filepath .Rel (fm .workspaceDir , path )
336+ if err != nil {
337+ return nil
338+ }
339+
340+ // 1. Exact Match on any term
341+ matchedExact := false
342+ for _ , term := range lowerTerms {
343+ if strings .Contains (lowerContent , term ) {
344+ exactResults = append (exactResults , relPath )
345+ matchedExact = true
346+ break
347+ }
348+ }
349+
350+ if matchedExact {
351+ return nil
352+ }
353+
354+ // 2. Tokenized/Fuzzy match on any token set (fallback)
355+ for _ , tokens := range tokenSets {
356+ allMatched := true
357+ for _ , token := range tokens {
358+ if ! strings .Contains (lowerContent , token ) {
359+ allMatched = false
360+ break
361+ }
362+ }
363+ if allMatched {
364+ tokenResults = append (tokenResults , relPath )
365+ break
366+ }
367+ }
368+
369+ return nil
370+ })
371+
372+ if err != nil {
373+ return nil , fmt .Errorf ("failed to search files: %w" , err )
374+ }
375+
376+ if len (exactResults ) > 0 {
377+ return exactResults , nil
378+ }
379+ return tokenResults , nil
380+ }
381+
267382// ListDirectories returns a list of subdirectories in the given directory
268383func (fm * FileManager ) ListDirectories (dirPath string ) ([]string , error ) {
269384 entries , err := os .ReadDir (dirPath )
0 commit comments