fix(tabs): keep unsaved edits when a .sql file already open is opened again - #2228
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.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.
What this fixes
Opening a
.sqlfile that is already open threw away whatever you had typed in its tab, with no prompt and nothing to undo it.Found while investigating #2217.
Root cause
QueryTabManager.addTabdedupes on the source file URL and then overwrites the buffer:content.queryis what the editor renders, and an external write to that binding reaches the text view throughsetText, which resets its storage. So the replacement is not undoable, and nothing asks first.Reachable in one session, no relaunch needed:
openLinkedFavoritetakes its reuse branch instead, which registers the file and short-circuits later opens.)WindowLifecycleMonitor:registerSourceFilehas two call sites, and neither is on this path.window(forSourceFile:)finds nothing, the reuse branch is skipped because the tab already has asourceFileURL, so a payload opens andaddTabhits the dedupe. The edits are gone.A tab restored from disk at launch is in the same state, since restore rebuilds
sourceFileURLand registers nothing.The app already has the right shape for this elsewhere:
FileModifiedOnDiskBannerandreloadFileForTabask before replacing a buffer from disk.QueryTab+Protection.holdsQueryWorkexists for exactly "a tab holding query work must not be silently reused in place", and is consulted on the navigation path but not here.The fix
Reopening a file that is already open shows its tab. The buffer is replaced only when there is nothing of the user's in it, which
TabQueryContent.isFileDirtyalready answers. A clean tab still picks up the current file, so nothing is lost for the common case of reopening a file you have not touched.The baseline moves with the buffer.
savedFileContentandloadMtimewere left behind by the old overwrite, so a tab could read as dirty against content it had just loaded, and raise the changed-on-disk banner for a change it had already taken. Both now follow the text, and a pending banner is cleared.isFileDirtyhas to be truthful for that guard to mean anything, and it was not. It reads a missingsavedFileContentas clean, and a tab rebuilt from a persisted record has none:QueryTab(from: PersistedTab)carries the text but nothing to compare it to. Restore at launch read the baseline back; reopening from Recently Closed did not, so the same tab was honest through one door and not the other. Such a tab lies three ways: no unsaved marker, Save skips it because it believes there is nothing to write, and now the reopen guard would have waved the overwrite through.FileTabBaselineis the one door both paths go through, andadoptReopenedFileadditionally refuses when the baseline is unknown, because unknown is not clean.FileTextLoadernow reports the modification date it saw, taken before the read. A caller that stats afterwards records a date newer than the text it holds, so a write landing in between is invisible: the tab looks current against a file it never read. Taking it first fails the other way, so the changed-on-disk notice can fire once too often but never go missing.Verified
verify.sh generate,verify.sh build: PASS.verify.sh test FileTabBaselineTests SQLFileDeduplicationTests QueryTabManagerTests TabPersistenceCoordinatorTests QueryTabManagerCloseTests QueryTabManagerAdoptTabTests: PASS, 38 cases.swiftlint --strictover every file this changes: clean. The one error the run reports isExecutionAuditLog.swift:40, which is onmainalready and is not in this diff.Five new cases in
SQLFileDeduplicationTests: unsaved edits survive a reopen; a tab with no baseline is not overwritten either; a clean tab still takes the file's current text; the saved baseline moves with it so the tab is not left falsely dirty; and a pending changed-on-disk banner is cleared. The existing case that pins the clean-tab overwrite is unchanged and still passes.FileTabBaselineTestscovers the baseline itself against real files on disk: a rebuilt tab learns what its file says, one holding unsaved work reads as dirty once it has, a tab with no file and a file that cannot be read are both left alone rather than given an invented baseline, a list is covered whole, and the loader reports the date it read.No UI automation: driving it needs a linked SQL favourite pointing at a file on disk, which the UI suite has no fixture for.