Keep focus on destination after cross-window surface move
This commit is contained in:
parent
b0b73e8878
commit
bcd024d8f8
2 changed files with 77 additions and 8 deletions
|
|
@ -1059,6 +1059,9 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
private var pendingTabSelection: (tabId: TabID, pane: PaneID)?
|
||||
private var isReconcilingFocusState = false
|
||||
private var focusReconcileScheduled = false
|
||||
#if DEBUG
|
||||
private(set) var debugFocusReconcileScheduledDuringDetachCount: Int = 0
|
||||
#endif
|
||||
private var geometryReconcileScheduled = false
|
||||
private var isNormalizingPinnedTabOrder = false
|
||||
private var pendingNonFocusSplitFocusReassert: PendingNonFocusSplitFocusReassert?
|
||||
|
|
@ -1087,6 +1090,8 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
|
||||
private var detachingTabIds: Set<TabID> = []
|
||||
private var pendingDetachedSurfaces: [TabID: DetachedSurfaceTransfer] = [:]
|
||||
private var activeDetachCloseTransactions: Int = 0
|
||||
private var isDetachingCloseTransaction: Bool { activeDetachCloseTransactions > 0 }
|
||||
|
||||
func panelIdFromSurfaceId(_ surfaceId: TabID) -> UUID? {
|
||||
surfaceIdToPanelId[surfaceId]
|
||||
|
|
@ -2188,6 +2193,8 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
|
||||
detachingTabIds.insert(tabId)
|
||||
forceCloseTabIds.insert(tabId)
|
||||
activeDetachCloseTransactions += 1
|
||||
defer { activeDetachCloseTransactions = max(0, activeDetachCloseTransactions - 1) }
|
||||
guard bonsplitController.closeTab(tabId) else {
|
||||
detachingTabIds.remove(tabId)
|
||||
pendingDetachedSurfaces.removeValue(forKey: tabId)
|
||||
|
|
@ -2633,6 +2640,11 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
/// Reconcile focus/first-responder convergence.
|
||||
/// Coalesce to the next main-queue turn so bonsplit selection/pane mutations settle first.
|
||||
private func scheduleFocusReconcile() {
|
||||
#if DEBUG
|
||||
if isDetachingCloseTransaction {
|
||||
debugFocusReconcileScheduledDuringDetachCount += 1
|
||||
}
|
||||
#endif
|
||||
guard !focusReconcileScheduled else { return }
|
||||
focusReconcileScheduled = true
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
|
|
@ -3117,6 +3129,7 @@ extension Workspace: BonsplitDelegate {
|
|||
forceCloseTabIds.remove(tabId)
|
||||
let selectTabId = postCloseSelectTabId.removeValue(forKey: tabId)
|
||||
let closedBrowserRestoreSnapshot = pendingClosedBrowserRestoreSnapshots.removeValue(forKey: tabId)
|
||||
let isDetaching = detachingTabIds.remove(tabId) != nil || isDetachingCloseTransaction
|
||||
|
||||
// Clean up our panel
|
||||
guard let panelId = panelIdFromSurfaceId(tabId) else {
|
||||
|
|
@ -3124,7 +3137,9 @@ extension Workspace: BonsplitDelegate {
|
|||
NSLog("[Workspace] didCloseTab: no panelId for tabId")
|
||||
#endif
|
||||
scheduleTerminalGeometryReconcile()
|
||||
scheduleFocusReconcile()
|
||||
if !isDetaching {
|
||||
scheduleFocusReconcile()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -3132,7 +3147,6 @@ extension Workspace: BonsplitDelegate {
|
|||
NSLog("[Workspace] didCloseTab panelId=\(panelId) remainingPanels=\(panels.count - 1) remainingPanes=\(controller.allPaneIds.count)")
|
||||
#endif
|
||||
|
||||
let isDetaching = detachingTabIds.remove(tabId) != nil
|
||||
let panel = panels[panelId]
|
||||
|
||||
if isDetaching, let panel {
|
||||
|
|
@ -3182,7 +3196,6 @@ extension Workspace: BonsplitDelegate {
|
|||
if panels.isEmpty {
|
||||
if isDetaching {
|
||||
scheduleTerminalGeometryReconcile()
|
||||
scheduleFocusReconcile()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -3217,7 +3230,9 @@ extension Workspace: BonsplitDelegate {
|
|||
normalizePinnedTabs(in: pane)
|
||||
}
|
||||
scheduleTerminalGeometryReconcile()
|
||||
scheduleFocusReconcile()
|
||||
if !isDetaching {
|
||||
scheduleFocusReconcile()
|
||||
}
|
||||
}
|
||||
|
||||
func splitTabBar(_ controller: BonsplitController, didSelectTab tab: Bonsplit.Tab, inPane pane: PaneID) {
|
||||
|
|
@ -3237,7 +3252,9 @@ extension Workspace: BonsplitDelegate {
|
|||
normalizePinnedTabs(in: source)
|
||||
normalizePinnedTabs(in: destination)
|
||||
scheduleTerminalGeometryReconcile()
|
||||
scheduleFocusReconcile()
|
||||
if !isDetachingCloseTransaction {
|
||||
scheduleFocusReconcile()
|
||||
}
|
||||
}
|
||||
|
||||
func splitTabBar(_ controller: BonsplitController, didFocusPane pane: PaneID) {
|
||||
|
|
@ -3259,6 +3276,7 @@ extension Workspace: BonsplitDelegate {
|
|||
|
||||
func splitTabBar(_ controller: BonsplitController, didClosePane paneId: PaneID) {
|
||||
let closedPanelIds = pendingPaneClosePanelIds.removeValue(forKey: paneId.id) ?? []
|
||||
let shouldScheduleFocusReconcile = !isDetachingCloseTransaction
|
||||
|
||||
if !closedPanelIds.isEmpty {
|
||||
for panelId in closedPanelIds {
|
||||
|
|
@ -3284,13 +3302,15 @@ extension Workspace: BonsplitDelegate {
|
|||
if let focusedPane = bonsplitController.focusedPaneId,
|
||||
let focusedTabId = bonsplitController.selectedTab(inPane: focusedPane)?.id {
|
||||
applyTabSelection(tabId: focusedTabId, inPane: focusedPane)
|
||||
} else {
|
||||
} else if shouldScheduleFocusReconcile {
|
||||
scheduleFocusReconcile()
|
||||
}
|
||||
}
|
||||
|
||||
scheduleTerminalGeometryReconcile()
|
||||
scheduleFocusReconcile()
|
||||
if shouldScheduleFocusReconcile {
|
||||
scheduleFocusReconcile()
|
||||
}
|
||||
}
|
||||
|
||||
func splitTabBar(_ controller: BonsplitController, shouldClosePane pane: PaneID) -> Bool {
|
||||
|
|
@ -3541,7 +3561,9 @@ extension Workspace: BonsplitDelegate {
|
|||
func splitTabBar(_ controller: BonsplitController, didChangeGeometry snapshot: LayoutSnapshot) {
|
||||
_ = snapshot
|
||||
scheduleTerminalGeometryReconcile()
|
||||
scheduleFocusReconcile()
|
||||
if !isDetachingCloseTransaction {
|
||||
scheduleFocusReconcile()
|
||||
}
|
||||
}
|
||||
|
||||
// No post-close polling refresh loop: we rely on view invariants and Ghostty's wakeups.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue