1package tools
2
3import (
4 "context"
5 "log/slog"
6 "os/exec"
7 "path/filepath"
8 "strings"
9 "sync"
10
11 "github.com/charmbracelet/crush/internal/log"
12)
13
14var getRg = sync.OnceValue(func() string {
15 path, err := exec.LookPath("rg")
16 if err != nil {
17 if log.Initialized() {
18 slog.Warn("Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower.")
19 }
20 return ""
21 }
22 return path
23})
24
25func getRgCmd(ctx context.Context, globPattern string) *exec.Cmd {
26 name := getRg()
27 if name == "" {
28 return nil
29 }
30 args := []string{"--files", "-L", "--null"}
31 if globPattern != "" {
32 if !filepath.IsAbs(globPattern) && !strings.HasPrefix(globPattern, "/") {
33 globPattern = "/" + globPattern
34 }
35 args = append(args, "--glob", globPattern)
36 }
37 return exec.CommandContext(ctx, name, args...)
38}
39
40func getRgSearchCmd(ctx context.Context, pattern, path, include string) *exec.Cmd {
41 name := getRg()
42 if name == "" {
43 return nil
44 }
45 // Use -n to show line numbers, -0 for null separation to handle Windows paths
46 args := []string{"--json", "-H", "-n", "-0", pattern}
47 if include != "" {
48 args = append(args, "--glob", include)
49 }
50 args = append(args, path)
51
52 return exec.CommandContext(ctx, name, args...)
53}