Repository navigation
Replace All with empty string fails for multiple hits on same line - #4426
Conversation
|
Note: we should check whether/how this may be related to: |
|
@HeikoKlare thanks for the pointer. I checked how the two PRs relate. With #4373 applied, the reported symptom is gone: Find All followed by Replace All with an empty replace string now removes all matches, including several matches on the same line. Why #4373 hides the bug: The old
#4373 collects all matches first and replaces them from last to first at stored offsets. The selection no longer drives the search, so Replace All itself produces the correct document. The root cause in the viewer remains: I reproduced this manually with #4373 applied and without this PR:
Expected: The document reads Without Find All, or with a non-empty replace string, the selection is correct. I'd therefore keep both changes. |
There was a problem hiding this comment.
🟢 Approval recommended
The focused fix addresses the position-tracking defect and includes representative regression coverage.
0 open findings
What changed in this PR
Fixes Replace All with empty replacements after Find All by keeping selection positions document-tracked during rewrite sessions.
Changes:
- Re-registers selection positions when selection count changes.
- Adds regression coverage for adjacent matches and empty replacements.
| File | Description |
|---|---|
TextViewer.java |
Synchronizes replacement selection positions with document tracking. |
MultiSelectionTest.java |
Tests empty Replace All after multi-selection. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
HeikoKlare
left a comment
There was a problem hiding this comment.
Thank you for proposing this fix. The change looks sound to me and fixes the described issue. I also agree that it should be processed in addition to the referenced other fix that would simply hide the underlying issue addressed here.
I have one comment on a possible extension of the fix and another on the test design.
Beyond that, the commit is currently lacking a proper commit message. It only contains a headline (that accidentally reaches into the body). Please add a descriptive commit message body that explain the original issue and the overall concept of the fix.
| public void updateSelection(Position[] selections) { | ||
| fSelectionSet= true; | ||
| if (selections == null) { | ||
| fSelections = new Position[0]; |
There was a problem hiding this comment.
Should this do a replaceTrackedSelection() as well, so that the current positions are removed just like in the replacement case?
There was a problem hiding this comment.
I looked into this, and the null branch is never reached. updateSelection() has a single caller, setSelectedRanges(), which passes Arrays.stream(...).toArray(Position[]::new). That's never null, at most an empty array, and an empty array already goes through replaceTrackedSelections() in the else branch.
ViewerState is a private inner class of TextViewer, so this isn't public API, and no other caller can pass null.
I'd therefore suggest to keep this PR focused on the fix and remove the unnecessary null guard in a follow-up PR.
7947ed1 to
6be6df6
Compare
|
Thanks for the review, @HeikoKlare! |
While redraw is disabled, ViewerState tracks the selection via positions registered with the document. When the selection count changed, updateSelection() created new positions without registering them, so the selection stopped following document changes and restore() applied a stale selection. This caused Replace All with an empty replace string to skip matches after Find All. updateSelection() now unregisters the old positions and registers the new ones.
6be6df6 to
cb6c820
Compare

Replace All misses matches when you run it right after Find All with an empty replace string. It only happens when a replaced match is followed closely by another one, usually on the same line.
The bug doesn't show up without Find All, or with a non-empty replace string.
Before:
Expected: all matches are removed.
Actual: some matches stay in the document.
Root cause
Replace All opens a document rewrite session, which turns off redraw in
TextViewer. While redraw is off,TextViewer.ViewerStatekeeps the selection asPositionobjects that are registered with the document, so they move with every edit.ViewerState.connect()registers all N positions.ViewerState.updateSelection()sees that the number of selections changed and creates newPositionobjects, but never registers them with the document.""doesn't set a new selection inTextViewer.FindReplaceTarget.replaceSelection().getSelection()then returns the old match length instead of 0. The search continues atoffset + lengthand skips any match inside that range.With a single selection, the existing position gets updated in place and stays tracked. With a non-empty replace string,
replaceSelection()sets the selection explicitly. That's why both of those cases work.Fix
ViewerState.updateSelection()now removes the old positions from the document and registers the new ones whenever the number of selections changes. The selection keeps following document changes during a rewrite session, and old positions no longer stay registered with the document.After: