`-U` merges adjacent matches: `-o`, `--vimgrep`, `--count-matches` and `--json` report one match instead of two
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #187 by @shengyfu — merged
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 85/100
Research direction
Start in tgrep-cli/src/matching.rs, especially group_spans_by_line, FileMatches::find, and match_count; then read the existing group_spans_merges_overlapping_ranges_on_one_line test. Run the reproduction commands with pattern a against aa in multiline mode and add regression coverage for the affected output modes. Done when touching matches remain separate and the tests confirm counts and output agree.
Written by the indexing model from the issue text.
Description
Summary
With -U/--multiline, matches that touch each other on the same line are merged into one span. -o, --vimgrep, --count-matches and --json then report fewer, longer matches than ripgrep. --stats still counts them correctly, so the counts tgrep reports disagree with each other.
Without -U the same search reports each match separately, as ripgrep does.
Reproduce
mkdir repro && cd repro
printf 'aa\n' > a.txt
tgrep --no-index -U -o -n a a.txt
tgrep --no-index -U --vimgrep a a.txt
tgrep --no-index -U --count-matches a a.txt
tgrep --no-index -U --json a a.txt
tgrep --no-index -U --stats a a.txt
tgrep --no-index -o -n a a.txt # control: without -U
| Query | Actual: tgrep | Expected: rg 15.1.0 |
|---|---|---|
-U -o -n |
1:aa |
1:a, 1:a |
-U --vimgrep |
a.txt:1:1:aa |
a.txt:1:1:aa, a.txt:1:2:aa |
-U --count-matches |
1 |
2 |
-U --json |
one submatch aa (0–2); "matches":1 |
two submatches a (0–1, 1–2); "matches":2 |
-U --stats |
2 matches (1 matched lines) |
— |
-o -n (no -U) |
1:a, 1:a |
1:a, 1:a |
The README states that under -U, "--vimgrep reports one row per match, on its starting line".
Environment
- tgrep 1.1.0, built from 1120aca (current
main) with rustc 1.95.0 (59807616e 2026-04-14) - Windows 11 Pro (build 28000); the code involved is platform-independent
- Same results with
--no-index, with a local index, and throughtgrep serve(checked for-oand--count-matches)
Cause
In multiline mode, FileMatches::find passes the regex spans to group_spans_by_line, which merges a span into the previous one when span.0 <= last.1:
So the touching spans (0, 1) and (1, 2) become (0, 2). match_totals counts the spans before merging, which is why --stats says 2. match_count, used by --count-matches, and the per-match output of -o, --vimgrep and --json use the merged spans.
Possible fix
Regex matches never overlap, so merging only needs to join spans that really overlap: span.0 < last.1 keeps touching matches apart and still satisfies group_spans_merges_overlapping_ranges_on_one_line. Alternatively, keep the original spans for per-match output and --count-matches, and merge only for highlighting.
A regression test could check the queries above for aa with pattern a.
- Dominant language
- Rust
- Stars
- 3.4k
- Forks
- 138
- Avg merge
- 13h 24m
- Merged PRs (30d)
- 26
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from microsoft/tgrep
-
`-U --count-matches` counts one cross-line match once per covered line and disagrees with `--stats`Open
Difficulty 4/5 3-5 days Newbie friendliness 45/100
Maintainers usually reply within 1 day
Similar issues
-
[Feature]: [P3] engine-rs: the package source hash should ignore line endings and untracked filesOpen
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
maniator/verticopolis#880 ·
Maintainers usually reply within 1 day
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
Maintainers usually reply within 3 days
-
defect
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 2 days