Skip to content

fix(edit): search quote-normalized text with str::find - #2480

Open
thaildhe172591 wants to merge 1 commit into
GCWing:mainfrom
thaildhe172591:fix/edit-quote-scan-linear
Open

fix(edit): search quote-normalized text with str::find#2480
thaildhe172591 wants to merge 1 commit into
GCWing:mainfrom
thaildhe172591:fix/edit-quote-scan-linear

Conversation

@thaildhe172591

Copy link
Copy Markdown

Summary

find_actual_string in edit_file.rs compared every character window of the file against the search string by hand. The comparison short-circuits on the first mismatch, so ordinary source files stay fast, but a file holding long runs of one character (minified output, padded or aligned text, whitespace-heavy data) makes most windows share a deep prefix with the search string and the scan turns quadratic. Measured on this machine (debug build): 256KB of spaces with a 2KB old_string took 32s for one call, and edit_string_candidates can probe several candidates per failed edit, so a single failed Edit on such a file hangs for minutes.

Quote normalization maps one char to one char, so this change normalizes both sides once and searches with str::find, which finds the same leftmost match in linear time. The same 256KB input now completes in about 68ms, and a 512KB input went from 126s to 131ms. The returned slice is cut from the line-ending-normalized file by char offset, which byte offsets cannot do once multibyte text precedes the match; a new test covers exactly that case, and a second test guards against the scan regressing to quadratic on long repeated runs.

This addresses the hang half of #1650. The token-amplification half of that report is about how many times file content re-enters the model context after a failed edit, which lives in the read-state/validate_input flow, not in this function; I have left it alone.

References #1650

Type and Areas

Type:

Bug fix (performance).

Areas:

Rust core (tool-runtime, src/crates/execution/tool-execution).

Motivation / Impact

Editing large files with repetitive content could hang the Edit tool for minutes on a single failed match. After this change the fallback scan stays linear. No behavior change for edits that already matched: all existing candidates and fallbacks resolve to the same result, only faster.

Verification

  • cargo test --locked -p tool-runtime --lib - 132 passed, 0 failed (includes the two new tests)
  • Red/green on the new regression test: with the old window scan spliced back in, apply_edit_scans_long_repeated_runs_without_hanging fails at 23.7s; with this change it passes in well under a second
  • cargo test --locked -p bitfun-core --lib - 150 passed, 0 failed
  • cargo check --locked --workspace - clean
  • cargo fmt -p tool-runtime -- --check - clean

Note: CI's tool-runtime step only runs --lib search::, so the fs:: tests here do not run in CI; the counts above are from local runs on Windows.

AI-assisted: fully tested (see verification above).

Reviewer Notes

The subtle point is the byte/char offset distinction: str::find returns a byte offset into the quote-normalized string, while the returned slice must come from the pre-normalization string. Char offsets line up between the two because normalize_quote_char is a char-to-char map; byte offsets do not once any multibyte character precedes the match. apply_edit_matches_curly_quotes_after_multibyte_content fails if that is ever gotten wrong.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable.

find_actual_string compared every char window of the file against the
search string by hand. The comparison short-circuits on the first
mismatch, so ordinary source stays fast, but a file holding long runs
of one character (minified output, padded or aligned text) makes most
windows share a deep prefix with the search string and the scan turns
quadratic: 256KB of spaces with a 2KB old_string took over 20 seconds
per candidate on this machine, and edit_string_candidates can probe
several candidates per failed edit.

Quote normalization maps one char to one char, so normalizing both
sides once and searching with str::find finds the same leftmost match
in linear time. The returned slice is cut from the line-ending
normalized file by char offset, which byte offsets cannot do once
multibyte text precedes the match; a new test covers that case, and a
second test guards the runtime on long repeated runs.

Validation: cargo test --locked -p tool-runtime --lib; cargo test
--locked -p bitfun-core --lib; cargo check --locked --workspace;
cargo fmt -p tool-runtime -- --check.

Refs: GCWing#1650
AI: fully tested
@GCWing
GCWing requested a review from wsp1911 August 26, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant