Skip to content

Fix #1197: SelectedCount is stale while selection change events run - #1381

Open
TetzkatLipHoka wants to merge 1 commit into
JAM-Software:masterfrom
TetzkatLipHoka:fix/issue-1197-selectedcount
Open

Fix #1197: SelectedCount is stale while selection change events run#1381
TetzkatLipHoka wants to merge 1 commit into
JAM-Software:masterfrom
TetzkatLipHoka:fix/issue-1197-selectedcount

Conversation

@TetzkatLipHoka

Copy link
Copy Markdown
Contributor

Fixes #1197.

The problem

ToggleSelection() — the Shift+Arrow path — removes nodes through
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 already been cleared — which is exactly the discrepancy the reporter
describes.

Why not just decrement the counter

FSelectionCount is also the length PackArray scans. Lowering it while entries are
still marked would make PackArray stop early and leave marked entries in the array.

So instead the pending marks are counted, and GetSelectedCount subtracts them.
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 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 keeps
that counter from drifting, since resetting it in seven places is easy to forget. It
returns whether the array was shortened, which is what InvertSelection used its local
flag for.

This removed the last use of a few local variables, so those declarations are gone too.

Tests

Tests/VTSelectedCountIssue1197Tests.pas adds two tests:

  • SelectedCountIsCorrectDuringRemoveFromSelection asserts the count seen from inside
    OnRemoveFromSelection against the number of nodes actually carrying vsSelected.
  • SelectedCountIsCorrectAfterToggleSelection asserts the count after the operation, so
    a future change cannot over-correct. This one already passes without the fix.

Verified both ways on Delphi 13.1 / Win32:

passed failed
with this change 136 2
reverting only the getter change 135 3

The two remaining failures are the pre-existing TestCopyHTML1 / TestCopyHTML2, which
also fail on an unmodified master here.

AllUnits compiles without errors and without hints.

…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>
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.

SelectedCount is not always correct

1 participant