Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions coworker/tools/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,22 @@ def _rel(path: str, root: Path) -> str:
return path


# One `path:line:text` match line. The path part must tolerate a Windows drive prefix:
# a naive split(":", 2) on `C:\ws\a.py:12:text` yields file="C", line="\ws\a.py" — every
# match garbled on Windows, where rg (invoked with an absolute base) echoes absolute paths.
_RG_LINE = re.compile(r"^(?P<file>(?:[A-Za-z]:)?[^:]*):(?P<line>\d+):(?P<text>.*)$")


def _parse_rg(stdout: str, root: Path, n: int) -> dict[str, Any]:
matches: list[dict[str, Any]] = []
for line in stdout.splitlines():
parts = line.split(":", 2)
if len(parts) == 3:
f, ln, txt = parts
m = _RG_LINE.match(line)
if m:
matches.append(
{
"file": _rel(f, root),
"line": int(ln) if ln.isdigit() else 0,
"text": txt[:300],
"file": _rel(m["file"], root),
"line": int(m["line"]),
"text": m["text"][:300],
}
)
if len(matches) >= n:
Expand Down
25 changes: 24 additions & 1 deletion tests/test_code_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from coworker.tools.files import file_tools
from coworker.tools.git import git_tools
from coworker.tools.search import _py_grep, search_tools
from coworker.tools.search import _parse_rg, _py_grep, search_tools
from coworker.web.fetch import _html_to_text, make_web_fetch_tool


Expand Down Expand Up @@ -66,6 +66,29 @@ def test_grep_rejects_path_escape(tmp_path):
assert "escapes" in grep(pattern="x", path="../..")["error"]


def test_parse_rg_posix_paths_and_colons_in_text(tmp_path):
root = tmp_path.resolve()
out = f"{root}/src/a.py:12:url = 'http://x:8080'\n"
res = _parse_rg(out, root, 100)
assert res["count"] == 1
m = res["matches"][0]
assert m["file"] == "src/a.py"
assert m["line"] == 12
assert m["text"] == "url = 'http://x:8080'"


def test_parse_rg_windows_drive_paths(tmp_path):
"""rg on Windows echoes absolute paths (`C:\\ws\\a.py:12:text`); a naive
split(':', 2) turned the drive letter into the filename and lost the line number."""
out = "C:\\ws\\src\\a.py:12:def hello():\n"
res = _parse_rg(out, tmp_path.resolve(), 100)
assert res["count"] == 1
m = res["matches"][0]
assert m["file"] == "C:\\ws\\src\\a.py" # not 'C'
assert m["line"] == 12
assert m["text"] == "def hello():"


def test_py_grep_fallback_skips_ignored_dirs(tmp_path):
_seed(tmp_path)
res = _py_grep(tmp_path.resolve(), tmp_path.resolve(), "hello", None, 100)
Expand Down