-
-
Notifications
You must be signed in to change notification settings - Fork 320
fix(tabs): keep a closed query tab's SQL and add Reopen Closed Tab (#1854) #1857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import AppKit | ||
| import Foundation | ||
|
|
||
| /// Brings a closed tab back into a native window tab. Reopening reuses the restoration path that | ||
| /// cold launch already uses, so a reopened table tab recovers its filters, sort, and column | ||
| /// layout instead of reimplementing that here. | ||
| @MainActor | ||
| internal enum RecentlyClosedTabReopener { | ||
| internal static func reopenMostRecent() { | ||
| guard let entry = RecentlyClosedTabStore.shared.mostRecentEntry else { return } | ||
| reopen(id: entry.id) | ||
| } | ||
|
|
||
| internal static func reopen(id: UUID) { | ||
| guard let entry = RecentlyClosedTabStore.shared.consume(id: id) else { return } | ||
|
|
||
| // Closing the last tab of the last window leaves the window standing but empty. Reopening | ||
| // into a new window tab there would strand that empty tab alongside the restored one, so | ||
| // the empty window is filled in place, matching how New Tab reuses it. | ||
| if let coordinator = emptyWindowCoordinator(for: entry.connectionId) { | ||
| restore(entry, into: coordinator) | ||
| return | ||
| } | ||
|
|
||
| guard WindowManager.shared.hasOpenWindow(for: entry.connectionId) else { | ||
| Task { await LaunchIntentRouter.shared.route(.reopenClosedTab(entry)) } | ||
| return | ||
| } | ||
|
|
||
| openWindowTab(for: entry) | ||
| NSApp.activate(ignoringOtherApps: true) | ||
| } | ||
|
|
||
| private static func emptyWindowCoordinator(for connectionId: UUID) -> MainContentCoordinator? { | ||
| let empty = MainContentCoordinator.allActiveCoordinators().filter { | ||
| $0.connectionId == connectionId && $0.tabManager.tabs.isEmpty | ||
| } | ||
| return empty.first { $0.contentWindow?.isKeyWindow == true } ?? empty.first | ||
| } | ||
|
|
||
| private static func restore(_ entry: RecentlyClosedTabEntry, into coordinator: MainContentCoordinator) { | ||
| let tab = makeTab(for: entry) | ||
| coordinator.tabManager.adoptTab(tab, claimFocus: tab.tabType == .query) | ||
|
|
||
| if tab.tabType == .table, let tableName = tab.tableContext.tableName { | ||
| coordinator.restoreLastHiddenColumnsForTable() | ||
| coordinator.restoreFiltersForTable(tableName) | ||
| coordinator.lazyLoadCurrentTabIfNeeded(trigger: .restore) | ||
| } | ||
|
|
||
| coordinator.contentWindow?.makeKeyAndOrderFront(nil) | ||
| NSApp.activate(ignoringOtherApps: true) | ||
| } | ||
|
|
||
| private static func makeTab(for entry: RecentlyClosedTabEntry) -> QueryTab { | ||
| QueryTab( | ||
| from: entry.tab, | ||
| defaultPageSize: AppSettingsManager.shared.dataGrid.defaultPageSize | ||
| ) | ||
|
Comment on lines
+56
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the closed tab is file-backed, rebuilding it with Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| internal static func openWindowTab(for entry: RecentlyClosedTabEntry) { | ||
| let tab = makeTab(for: entry) | ||
| let payload = EditorTabPayload( | ||
| connectionId: entry.connectionId, | ||
| tabType: tab.tabType, | ||
| tableName: tab.tableContext.tableName, | ||
| databaseName: tab.tableContext.databaseName, | ||
| schemaName: tab.tableContext.schemaName, | ||
| isView: tab.tableContext.isView, | ||
| skipAutoExecute: true, | ||
| sourceFileURL: tab.content.sourceFileURL, | ||
| erDiagramSchemaKey: tab.display.erDiagramSchemaKey, | ||
| tabTitle: tab.title, | ||
| intent: .restoreOrDefault | ||
| ) | ||
| RestorationGroupRegistry.register( | ||
| .init(tabs: [tab], selectedTabId: tab.id, loadTiming: .immediate), | ||
| for: payload.id | ||
| ) | ||
| WindowManager.shared.openTab(payload: payload) | ||
| } | ||
| } | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When there is no open window for the entry's connection, this removes the recently closed entry (and
consumedeletes any overflow sidecar) before the async reconnect/open path has succeeded. If the reconnect fails, the password prompt is cancelled, or the saved connection is missing,LaunchIntentRouteronly shows an error and the tab is already gone, so the user cannot retryReopen Closed Taband large unsaved SQL may be deleted. Keep the entry until the reopen path succeeds, or reinsert it on failure.Useful? React with 👍 / 👎.