@@ -12,12 +12,63 @@ import (
1212 "sort"
1313 "strconv"
1414 "strings"
15+ "sync"
1516 "time"
1617
1718 "github.com/charmbracelet/crush/internal/config"
1819 "github.com/charmbracelet/crush/internal/fileutil"
1920)
2021
22+// regexCache provides thread-safe caching of compiled regex patterns
23+type regexCache struct {
24+ cache map[string]*regexp.Regexp
25+ mu sync.RWMutex
26+}
27+
28+// newRegexCache creates a new regex cache
29+func newRegexCache() *regexCache {
30+ return ®exCache{
31+ cache: make(map[string]*regexp.Regexp),
32+ }
33+}
34+
35+// get retrieves a compiled regex from cache or compiles and caches it
36+func (rc *regexCache) get(pattern string) (*regexp.Regexp, error) {
37+ // Try to get from cache first (read lock)
38+ rc.mu.RLock()
39+ if regex, exists := rc.cache[pattern]; exists {
40+ rc.mu.RUnlock()
41+ return regex, nil
42+ }
43+ rc.mu.RUnlock()
44+
45+ // Compile the regex (write lock)
46+ rc.mu.Lock()
47+ defer rc.mu.Unlock()
48+
49+ // Double-check in case another goroutine compiled it while we waited
50+ if regex, exists := rc.cache[pattern]; exists {
51+ return regex, nil
52+ }
53+
54+ // Compile and cache the regex
55+ regex, err := regexp.Compile(pattern)
56+ if err != nil {
57+ return nil, err
58+ }
59+
60+ rc.cache[pattern] = regex
61+ return regex, nil
62+}
63+
64+// Global regex cache instances
65+var (
66+ searchRegexCache = newRegexCache()
67+ globRegexCache = newRegexCache()
68+ // Pre-compiled regex for glob conversion (used frequently)
69+ globBraceRegex = regexp.MustCompile(`\{([^}]+)\}`)
70+)
71+
2172type GrepParams struct {
2273 Pattern string `json:"pattern"`
2374 Path string `json:"path"`
@@ -266,7 +317,8 @@ func searchWithRipgrep(pattern, path, include string) ([]grepMatch, error) {
266317func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error) {
267318 matches := []grepMatch{}
268319
269− regex, err := regexp.Compile(pattern)
320+ // Use cached regex compilation
321+ regex, err := searchRegexCache.get(pattern)
270322 if err != nil {
271323 return nil, fmt.Errorf("invalid regex pattern: %w", err)
272324 }
@@ -274,7 +326,7 @@ func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error
274326 var includePattern *regexp.Regexp
275327 if include != "" {
276328 regexPattern := globToRegex(include)
277− includePattern, err = regexp.Compile(regexPattern)
329+ includePattern, err = globRegexCache.get(regexPattern)
278330 if err != nil {
279331 return nil, fmt.Errorf("invalid include pattern: %w", err)
280332 }
@@ -349,8 +401,8 @@ func globToRegex(glob string) string {
349401 regexPattern = strings.ReplaceAll(regexPattern, "*", ".*")
350402 regexPattern = strings.ReplaceAll(regexPattern, "?", ".")
351403
352− re := regexp.MustCompile(`\{([^}]+)\}`)
353− regexPattern = re.ReplaceAllStringFunc(regexPattern, func(match string) string {
404+ // Use pre-compiled regex instead of compiling each time
405+ regexPattern = globBraceRegex.ReplaceAllStringFunc(regexPattern, func(match string) string {
354406 inner := match[1 : len(match)-1]
355407 return "(" + strings.ReplaceAll(inner, ",", "|") + ")"
356408 })