Fix #1197: SelectedCount is stale while selection change events run - #1381
Open
TetzkatLipHoka wants to merge 1 commit into
Open
Fix #1197: SelectedCount is stale while selection change events run#1381TetzkatLipHoka wants to merge 1 commit into
TetzkatLipHoka wants to merge 1 commit into
Conversation
…events run ToggleSelection() - the Shift+Arrow path - removes nodes via InternalRemoveFromSelection(). That routine only *marks* the entry in FSelection (it sets the low bit of the pointer, which is what PackArray later looks for) but fires DoRemoveFromSelection() and Change() straight away. FSelectionCount is not corrected until PackArray runs after the loop, so every handler invoked in between sees a count that is too high by the number of nodes already dropped. Iterating SelectedNodes gives the right answer at the same moment because vsSelected has been cleared, which is exactly the discrepancy reported. FSelectionCount cannot simply be decremented when marking: it is also the length PackArray scans, so lowering it early would leave marked entries in the array. Instead the pending marks are counted and subtracted in GetSelectedCount, and SelectedCount now reads that getter instead of the raw field. Internal callers keep using FSelectionCount directly, so the physical bookkeeping is unchanged. Event order is deliberately left alone - Change() is called from InternalRemoveFromSelection() on purpose, see the comment referring to JAM-Software#1047. The five line pack-and-resize block that appeared at seven call sites is now PackSelection(), which also resets the pending counter. Centralising it is what keeps that counter from drifting, since resetting it at seven places is easy to forget. It returns whether the array was shortened, which is what InvertSelection used its local flag for. Adds Tests/VTSelectedCountIssue1197Tests.pas: one test asserts the count seen during OnRemoveFromSelection, a second asserts the count after the operation so a future change cannot over-correct. Verified both ways - with the fix the suite is 136 passed / 2 failed, reverting only the getter change puts it back to 135 / 3. The two remaining failures are the pre-existing TestCopyHTML1 and TestCopyHTML2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1197.
The problem
ToggleSelection()— the Shift+Arrow path — removes nodes throughInternalRemoveFromSelection(). That routine only marks the entry inFSelection(it sets the low bit of the pointer, which is what
PackArraylater looks for) butfires
DoRemoveFromSelection()andChange()straight away.FSelectionCountis notcorrected until
PackArrayruns after the loop, so every handler invoked in betweensees a count that is too high by the number of nodes already dropped.
Iterating
SelectedNodesgives the right answer at the same moment, becausevsSelectedhas already been cleared — which is exactly the discrepancy the reporterdescribes.
Why not just decrement the counter
FSelectionCountis also the lengthPackArrayscans. Lowering it while entries arestill marked would make
PackArraystop early and leave marked entries in the array.So instead the pending marks are counted, and
GetSelectedCountsubtracts them.SelectedCountnow reads that getter instead of the raw field. Internal callers keepusing
FSelectionCountdirectly, so the physical bookkeeping is unchanged.Event order is deliberately left alone:
Change()is called fromInternalRemoveFromSelection()on purpose — see the comment there referring to #1047.PackSelection()
The same five-line pack-and-resize block appeared at seven call sites. It is now
PackSelection(), which also resets the pending counter. Centralising it is what keepsthat counter from drifting, since resetting it in seven places is easy to forget. It
returns whether the array was shortened, which is what
InvertSelectionused its localflag for.
This removed the last use of a few local variables, so those declarations are gone too.
Tests
Tests/VTSelectedCountIssue1197Tests.pasadds two tests:SelectedCountIsCorrectDuringRemoveFromSelectionasserts the count seen from insideOnRemoveFromSelectionagainst the number of nodes actually carryingvsSelected.SelectedCountIsCorrectAfterToggleSelectionasserts the count after the operation, soa future change cannot over-correct. This one already passes without the fix.
Verified both ways on Delphi 13.1 / Win32:
The two remaining failures are the pre-existing
TestCopyHTML1/TestCopyHTML2, whichalso fail on an unmodified
masterhere.AllUnitscompiles without errors and without hints.