on main help sign in
fix(grep): resolve Windows path parsing with null separation (#1095)
commit1f46a69b546885ac95b93d8c4d5b06b97cf84977 authorRory de Zoete <33318916+zoete@users.noreply.github.com> dateTue 23 Sep 2025, 16:39 (1 year ago) parentc2d85d1
2 files changed+33−8
internal/llm/tools/grep.go+31−6 internal/llm/tools/rg.go+2−2
internal/llm/tools/grep.go+31−6
@@ -259,18 +259,16 @@ func searchWithRipgrep(ctx context.Context, pattern, path, include string) ([]gr
259259 continue
260260 }
261261
262− // Parse ripgrep output format: file:line:content
263− parts := strings.SplitN(line, ":", 3)
264− if len(parts) < 3 {
262+ // Parse ripgrep output using null separation
263+ filePath, lineNumStr, lineText, ok := parseRipgrepLine(line)
264+ if !ok {
265265 continue
266266 }
267267
268− filePath := parts[0]
269− lineNum, err := strconv.Atoi(parts[1])
268+ lineNum, err := strconv.Atoi(lineNumStr)
270269 if err != nil {
271270 continue
272271 }
273− lineText := parts[2]
274272
275273 fileInfo, err := os.Stat(filePath)
276274 if err != nil {
@@ -288,6 +286,33 @@ func searchWithRipgrep(ctx context.Context, pattern, path, include string) ([]gr
288286 return matches, nil
289287}
290288
289+// parseRipgrepLine parses ripgrep output with null separation to handle Windows paths
290+func parseRipgrepLine(line string) (filePath, lineNum, lineText string, ok bool) {
291+ // Split on null byte first to separate filename from rest
292+ parts := strings.SplitN(line, "\x00", 2)
293+ if len(parts) != 2 {
294+ return "", "", "", false
295+ }
296+
297+ filePath = parts[0]
298+ remainder := parts[1]
299+
300+ // Now split the remainder on first colon: "linenum:content"
301+ colonIndex := strings.Index(remainder, ":")
302+ if colonIndex == -1 {
303+ return "", "", "", false
304+ }
305+
306+ lineNumStr := remainder[:colonIndex]
307+ lineText = remainder[colonIndex+1:]
308+
309+ if _, err := strconv.Atoi(lineNumStr); err != nil {
310+ return "", "", "", false
311+ }
312+
313+ return filePath, lineNumStr, lineText, true
314+}
315+
291316func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error) {
292317 matches := []grepMatch{}
293318
internal/llm/tools/rg.go+2−2
@@ -42,8 +42,8 @@ func getRgSearchCmd(ctx context.Context, pattern, path, include string) *exec.Cm
4242 if name == "" {
4343 return nil
4444 }
45− // Use -n to show line numbers and include the matched line
46− args := []string{"-H", "-n", pattern}
45+ // Use -n to show line numbers, -0 for null separation to handle Windows paths
46+ args := []string{"-H", "-n", "-0", pattern}
4747 if include != "" {
4848 args = append(args, "--glob", include)
4949 }