@@ -8,6 +8,7 @@ import (
"path/filepath"
"sort"
"strings"
+ "sync"
"time"
"github.com/bmatcuk/doublestar/v4"
@@ -95,14 +96,14 @@ func NewFastGlobWalker(searchPath string) *FastGlobWalker {
return walker
}
-func (w *FastGlobWalker) shouldSkip(path string) bool {
+func shouldSkip(path, rootPath string, gitignore *ignore.GitIgnore) bool {
if SkipHidden(path) {
return true
}
- if w.gitignore != nil {
- relPath, err := filepath.Rel(w.rootPath, path)
- if err == nil && w.gitignore.MatchesPath(relPath) {
+ if gitignore != nil {
+ relPath, err := filepath.Rel(rootPath, path)
+ if err == nil && gitignore.MatchesPath(relPath) {
return true
}
}
@@ -111,6 +112,7 @@ func (w *FastGlobWalker) shouldSkip(path string) bool {
}
func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool, error) {
+ var mu sync.Mutex
walker := NewFastGlobWalker(searchPath)
var matches []FileInfo
conf := fastwalk.Config{
@@ -119,21 +121,28 @@ func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool,
ToSlash: fastwalk.DefaultToSlash(),
Sort: fastwalk.SortFilesFirst,
}
+ rootPath, gitignore := walker.rootPath, walker.gitignore
err := fastwalk.Walk(&conf, searchPath, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil // Skip files we can't access
}
if d.IsDir() {
- if walker.shouldSkip(path) {
+ mu.Lock()
+ if shouldSkip(path, rootPath, gitignore) {
+ mu.Unlock()
return filepath.SkipDir
}
+ mu.Unlock()
return nil
}
- if walker.shouldSkip(path) {
+ mu.Lock()
+ if shouldSkip(path, rootPath, gitignore) {
+ mu.Unlock()
return nil
}
+ mu.Unlock()
// Check if path matches the pattern
relPath, err := filepath.Rel(searchPath, path)
@@ -151,6 +160,9 @@ func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool,
return nil
}
+ mu.Lock()
+ defer mu.Unlock()
+
matches = append(matches, FileInfo{Path: path, ModTime: info.ModTime()})
if limit > 0 && len(matches) >= limit*2 {
return filepath.SkipAll
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"strings"
+ "sync"
"github.com/charmbracelet/crush/internal/fsext"
)
@@ -64,6 +65,7 @@ type GlobResponseMetadata struct {
type globTool struct {
workingDir string
+ mu sync.Mutex
}
func NewGlobTool(workingDir string) BaseTool {
@@ -109,20 +111,21 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
searchPath = g.workingDir
}
+ g.mu.Lock()
files, truncated, err := globFiles(params.Pattern, searchPath, 100)
if err != nil {
+ g.mu.Unlock()
return ToolResponse{}, fmt.Errorf("error finding files: %w", err)
}
- var output string
- if len(files) == 0 {
- output = "No files found"
- } else {
+ output := "No files found"
+ if len(files) > 0 {
output = strings.Join(files, "\n")
if truncated {
output += "\n\n(Results are truncated. Consider using a more specific path or pattern.)"
}
}
+ g.mu.Unlock()
return WithResponseMetadata(
NewTextResponse(output),