rg.go

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