Skip to content

-U --replace does not replace cross-line matches; -o -r can emit nothing #198

Closed

Description

@wuliangtdi

Summary

A regex that matches across a newline is found successfully, but --replace does not replace it. Adding --only-matching produces empty stdout instead of the expanded replacement. The server path also reports exit 1 for that empty-output case, while scan and local-index paths exit 0.

This persists in v1.2.0 after #192 fixed #188's cross-line --count-matches totals. This report concerns replacement output.

Reproduce

mkdir repro-multiline-replace && cd repro-multiline-replace
mkdir data
printf 'hello\nworld\n' > data/a.txt

tgrep --no-index -I -U -r X -- 'hello\nworld' data
rg               -I -U -r X -- 'hello\nworld' data

tgrep --no-index -I -U -o -r '${2}/${1}' -- '(hello)\n(world)' data
rg               -I -U -o -r '${2}/${1}' -- '(hello)\n(world)' data
Query Actual: tgrep 1.2.0 Expected: rg 15.1.0
Cross-line match, -r X hello\nworld\n, exit 0 X\n, exit 0
Cross-line captures, -o -r '${2}/${1}' Empty stdout; scan/local exit 0, server exit 1 world/hello\n, exit 0

The content-search cases reproduce with --no-index, an on-disk index, and tgrep serve --no-watch.

To repeat the indexed cases, run tgrep index data, remove --no-index, and keep the directory path data. For the server cases, start tgrep serve --no-watch data in another terminal and repeat those directory searches. --stats was used in the Windows harness to verify the selected backend, including (via server).

Environment

  • tgrep 1.2.0, official Windows x86_64 release.
  • Source inspected at release commit 3ba6ee02537281b68ee1366bb40df9f8b7cc54f0.
  • Windows; test harness run with PowerShell 7.
  • ripgrep 15.1.0 (af60c2de9d).
  • Verified on 2026-10-12. The shell examples describe the same fixture bytes and arguments tested on Windows.

Cause

tgrep-cli/src/matching.rs:830 retrieves the text for one line and calls matcher.expansions(line, rep) for -o -r. The normal replacement branch likewise calls matcher.replace_all(line, rep) in tgrep-cli/src/matching.rs:875.

Re-running the original cross-line regex on each individual line cannot recover a match that requires both lines.

Possible fix

Expand replacements from the original match/capture ranges before splitting the result into display lines. Preserve one logical cross-line replacement, capture expansion, and match-based exit status independently of whether rendering emitted a line.

A regression could assert both outputs and exit codes above with and without -o, across all three backends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions