Fix incorrect isInConflict logic#11
Conversation
…when detected changes to the backing file match what's in the buffer.
|
Added some more explanation. It may be useful; it may just be overkill. |
|
Sorry for the delay, I think this is next on my to-look-at list. Not sure how confident I'll be looking into it, but that's for another day... |
There was a problem hiding this comment.
Code change looks sensible.
The whole thing (inherited from before this PR) gives me a feeling like walking on a rickety bridge, but... Asking to address all that would broaden the scope of the PR too much, and for all I know this approach (again the overall design from before this PR) is the least-bad option somehow...
(Perhaps a better analogy is a Rube Goldberg machine of pipes leaking into other pipes that catch the water, become heavy and pivot such that they leak intricately into other pipes... Or more benignly, perhaps like those drinking bird toys, always teetering on the edge of states...)
I tried the test approach indicated, and thus verified the new assertions are sensitive to the actual code changes, and verified that the code changes cause the new assertions to pass. For what that's worth.
Smaller PR than I expected!
LGTM.
This is a blocker for pulsar#1478, which I'd tentatively targeted for version 1.132.0. So it's not a drop-everything-and-review sort of thing, but I'd appreciate if someone took a look at this after we ship 1.131.2.
Buffer conflict happens when
If the buffer were unmodified, we'd simply swap in the new contents of the buffer. But when there are pending buffer changes, we can't do that without losing the user's work. Hence the buffer is now in conflict. We have an
isInConflictmethod that returns a boolean, plus anonDidConflictmethod that notifies when the buffer enters a conflicted state.The logic of
isInConflictis simple:fileHasChangedSinceLastLoadis flipped totruewhenever we detect a filesystem change for the buffer's file. It should flip tofalsewhenever we load from disk and do something with the contents — or whenever we write to disk, since that guarantees synchronization between the buffer contents and the disk contents.How it should work
This is a bit esoteric, but imagine this scenario:
TextBufferbacked by a file on diskdid-conflictevent, and set a flag (fileHasChangedSinceLastLoad) totrueto mark the file as conflictedisInConflictmethod will thereafter returntrue, as it will whenever (a)fileHasChangedSinceLastLoadistrueand (b)isModified()returnstrueOnce you save the file again,
fileHasChangedSinceLastLoadedis reset tofalse, since that serves to synchronize the buffer contents and the file contents. Even if you modify the buffer again after saving,isInConflictwill correctly returnfalse, sincefileHasChangedSinceLastLoadedis stillfalse.How it actually works
But here's a scenario that slipped through the specs:
TextBufferbacked by a file on diskfileHasChangedSinceLastLoadflips totrueeven though the file isn't actually in conflict (anddid-conflictwas not emitted)isInConflictmethod's test are nowtrue, and the buffer is treated as being in conflict even though it isn'tI think the idea was that the other code paths through the
onDidChangehandler were eventually supposed to result infileHasChangedSinceLastLoadedflipping back tofalse… but in practice, we take one of the side doors instead, and return early before the line infinishLoadingthat actually resets the flag.In practice, the changes in pulsar#1478 cause the new “this file is in conflict; are you sure you want to save?” dialog to fire much too often… because
isInConflictis incorrectly returningtruepractically all the time after the initial save to disk.This PR should fix this. There are probably a couple different approaches I could've taken for this, but I went with the one that I knew I could reason about correctly: in the code paths in
onDidChangewhere we know the buffer contents are synchronized with what's on disk, we'll proactively flipfileHasChangedSinceLastLoadback tofalse.Testing
The first commit changes an existing spec to demonstrate the problem; if you check out just that commit, the spec will fail.
The next commit makes the failing spec pass again.