(ctx context.Context, execCtx ExecutionContext, input any)
| 589 | } |
| 590 | |
| 591 | func (t *GrepSearchTool) Execute(ctx context.Context, execCtx ExecutionContext, input any) (string, error) { |
| 592 | if _, err := exec.LookPath("rg"); err != nil { |
| 593 | return "Error: ripgrep (rg) is not installed. Please install it from https://github.com/BurntSushi/ripgrep", nil |
| 594 | } |
| 595 | in := deref[GrepSearchInput](input) |
| 596 | if in.Path == "" { |
| 597 | in.Path = "." |
| 598 | } |
| 599 | if in.OutputMode == "" { |
| 600 | in.OutputMode = "files_with_matches" |
| 601 | } |
| 602 | headLimit := 250 |
| 603 | if in.HeadLimit != nil { |
| 604 | headLimit = *in.HeadLimit |
| 605 | } |
| 606 | searchPath := resolveToolPath(execCtx, in.Path) |
| 607 | cwd := stringFromExecCtx(execCtx, "cwd") |
| 608 | if cwd == "" { |
| 609 | cwd = config.GetConfig().CWD |
| 610 | } |
| 611 | args := []string{"--no-heading", "--with-filename", "--line-number", "--color=never"} |
| 612 | if in.CaseInsensitive { |
| 613 | args = append(args, "-i") |
| 614 | } |
| 615 | if in.Glob != "" { |
| 616 | args = append(args, "--glob", in.Glob) |
| 617 | } |
| 618 | switch in.OutputMode { |
| 619 | case "files_with_matches": |
| 620 | args = append(args, "-l") |
| 621 | case "count": |
| 622 | args = append(args, "-c") |
| 623 | } |
| 624 | args = append(args, in.Pattern, searchPath) |
| 625 | cmdCtx, cancel := context.WithTimeout(ctx, 10*time.Second) |
| 626 | defer cancel() |
| 627 | cmd := exec.CommandContext(cmdCtx, "rg", args...) |
| 628 | cmd.Dir = cwd |
| 629 | var stdout, stderr bytes.Buffer |
| 630 | cmd.Stdout = &stdout |
| 631 | cmd.Stderr = &stderr |
| 632 | err := cmd.Run() |
| 633 | if cmdCtx.Err() == context.DeadlineExceeded { |
| 634 | return "Error: grep search timed out (10s limit). Try narrowing the search scope.", nil |
| 635 | } |
| 636 | if err != nil && stdout.Len() == 0 { |
| 637 | // rg returns 1 for no matches; Python treats empty stdout as no matches. |
| 638 | if stdout.Len() == 0 { |
| 639 | return "No matches found.", nil |
| 640 | } |
| 641 | } |
| 642 | output := strings.TrimSpace(strings.ToValidUTF8(stdout.String(), "\uFFFD")) |
| 643 | if output == "" { |
| 644 | return "No matches found.", nil |
| 645 | } |
| 646 | lines := strings.Split(output, "\n") |
| 647 | if headLimit != 0 && len(lines) > headLimit { |
| 648 | keep := headLimit |
nothing calls this directly
no test coverage detected