ok
This commit is contained in:
parent
1408cbb68c
commit
990b6ba12a
4 changed files with 123 additions and 11 deletions
|
|
@ -1797,6 +1797,7 @@ struct ContentView: View {
|
|||
ForEach(mountedWorkspaces) { tab in
|
||||
let isSelectedWorkspace = selectedWorkspaceId == tab.id
|
||||
let isRetiringWorkspace = retiringWorkspaceId == tab.id
|
||||
let shouldPrimeInBackground = tabManager.pendingBackgroundWorkspaceLoadIds.contains(tab.id)
|
||||
// Keep the retiring workspace visible during handoff, but never input-active.
|
||||
// Allowing both selected+retiring workspaces to be input-active lets the
|
||||
// old workspace steal first responder (notably with WKWebView), which can
|
||||
|
|
@ -1823,6 +1824,9 @@ struct ContentView: View {
|
|||
.allowsHitTesting(isSelectedWorkspace)
|
||||
.accessibilityHidden(!isVisible)
|
||||
.zIndex(isSelectedWorkspace ? 2 : (isRetiringWorkspace ? 1 : 0))
|
||||
.task(id: shouldPrimeInBackground ? tab.id : nil) {
|
||||
await primeBackgroundWorkspaceIfNeeded(workspaceId: tab.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
.opacity(sidebarSelectionState.selection == .tabs ? 1 : 0)
|
||||
|
|
@ -2167,6 +2171,10 @@ struct ContentView: View {
|
|||
reconcileMountedWorkspaceIds()
|
||||
})
|
||||
|
||||
view = AnyView(view.onReceive(tabManager.$pendingBackgroundWorkspaceLoadIds) { _ in
|
||||
reconcileMountedWorkspaceIds()
|
||||
})
|
||||
|
||||
view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .ghosttyDidSetTitle)) { notification in
|
||||
guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID,
|
||||
tabId == tabManager.selectedTabId else { return }
|
||||
|
|
@ -2227,6 +2235,7 @@ struct ContentView: View {
|
|||
if let previousSelectedWorkspaceId, !existingIds.contains(previousSelectedWorkspaceId) {
|
||||
self.previousSelectedWorkspaceId = tabManager.selectedTabId
|
||||
}
|
||||
tabManager.pruneBackgroundWorkspaceLoads(existingIds: existingIds)
|
||||
reconcileMountedWorkspaceIds(tabs: tabs)
|
||||
selectedTabIds = selectedTabIds.filter { existingIds.contains($0) }
|
||||
if selectedTabIds.isEmpty, let selectedId = tabManager.selectedTabId {
|
||||
|
|
@ -2531,9 +2540,10 @@ struct ContentView: View {
|
|||
let currentTabs = tabs ?? tabManager.tabs
|
||||
let orderedTabIds = currentTabs.map { $0.id }
|
||||
let effectiveSelectedId = selectedId ?? tabManager.selectedTabId
|
||||
let pinnedIds = retiringWorkspaceId.map { Set([ $0 ]) } ?? []
|
||||
let handoffPinnedIds = retiringWorkspaceId.map { Set([ $0 ]) } ?? []
|
||||
let pinnedIds = handoffPinnedIds.union(tabManager.pendingBackgroundWorkspaceLoadIds)
|
||||
let isCycleHot = tabManager.isWorkspaceCycleHot
|
||||
let shouldKeepHandoffPair = isCycleHot && !pinnedIds.isEmpty
|
||||
let shouldKeepHandoffPair = isCycleHot && !handoffPinnedIds.isEmpty
|
||||
let baseMaxMounted = shouldKeepHandoffPair
|
||||
? WorkspaceMountPolicy.maxMountedWorkspacesDuringCycle
|
||||
: WorkspaceMountPolicy.maxMountedWorkspaces
|
||||
|
|
@ -2570,6 +2580,76 @@ struct ContentView: View {
|
|||
#endif
|
||||
}
|
||||
|
||||
private enum BackgroundWorkspacePrimeState {
|
||||
case pending
|
||||
case completed(reason: String)
|
||||
}
|
||||
|
||||
private func primeBackgroundWorkspaceIfNeeded(workspaceId: UUID) async {
|
||||
let shouldPrime = await MainActor.run {
|
||||
tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId)
|
||||
}
|
||||
guard shouldPrime else { return }
|
||||
|
||||
#if DEBUG
|
||||
let startedAt = ProcessInfo.processInfo.systemUptime
|
||||
dlog("workspace.backgroundPrime.start workspace=\(workspaceId.uuidString.prefix(5))")
|
||||
#endif
|
||||
|
||||
let timeout = Date().addingTimeInterval(2.0)
|
||||
while !Task.isCancelled {
|
||||
let state = await MainActor.run {
|
||||
stepBackgroundWorkspacePrime(workspaceId: workspaceId)
|
||||
}
|
||||
switch state {
|
||||
case .pending:
|
||||
if Date() < timeout {
|
||||
try? await Task.sleep(nanoseconds: 50_000_000)
|
||||
continue
|
||||
}
|
||||
await MainActor.run {
|
||||
tabManager.completeBackgroundWorkspaceLoad(for: workspaceId)
|
||||
}
|
||||
#if DEBUG
|
||||
let elapsedMs = (ProcessInfo.processInfo.systemUptime - startedAt) * 1000
|
||||
dlog(
|
||||
"workspace.backgroundPrime.finish workspace=\(workspaceId.uuidString.prefix(5)) " +
|
||||
"reason=timeout ms=\(String(format: "%.2f", elapsedMs))"
|
||||
)
|
||||
#endif
|
||||
return
|
||||
case .completed(let reason):
|
||||
#if DEBUG
|
||||
let elapsedMs = (ProcessInfo.processInfo.systemUptime - startedAt) * 1000
|
||||
dlog(
|
||||
"workspace.backgroundPrime.finish workspace=\(workspaceId.uuidString.prefix(5)) " +
|
||||
"reason=\(reason) ms=\(String(format: "%.2f", elapsedMs))"
|
||||
)
|
||||
#endif
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func stepBackgroundWorkspacePrime(workspaceId: UUID) -> BackgroundWorkspacePrimeState {
|
||||
guard tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId) else {
|
||||
return .completed(reason: "already_cleared")
|
||||
}
|
||||
guard let workspace = tabManager.tabs.first(where: { $0.id == workspaceId }) else {
|
||||
tabManager.completeBackgroundWorkspaceLoad(for: workspaceId)
|
||||
return .completed(reason: "workspace_removed")
|
||||
}
|
||||
|
||||
workspace.requestBackgroundTerminalSurfaceStartIfNeeded()
|
||||
guard workspace.hasLoadedTerminalSurface() else {
|
||||
return .pending
|
||||
}
|
||||
|
||||
tabManager.completeBackgroundWorkspaceLoad(for: workspaceId)
|
||||
return .completed(reason: "surface_ready")
|
||||
}
|
||||
|
||||
private func addTab() {
|
||||
tabManager.addTab()
|
||||
sidebarSelectionState.selection = .tabs
|
||||
|
|
|
|||
|
|
@ -564,6 +564,7 @@ class TabManager: ObservableObject {
|
|||
|
||||
@Published var tabs: [Workspace] = []
|
||||
@Published private(set) var isWorkspaceCycleHot: Bool = false
|
||||
@Published private(set) var pendingBackgroundWorkspaceLoadIds: Set<UUID> = []
|
||||
|
||||
/// Global monotonically increasing counter for CMUX_PORT ordinal assignment.
|
||||
/// Static so port ranges don't overlap across multiple windows (each window has its own TabManager).
|
||||
|
|
@ -799,6 +800,7 @@ class TabManager: ObservableObject {
|
|||
func addWorkspace(
|
||||
workingDirectory overrideWorkingDirectory: String? = nil,
|
||||
select: Bool = true,
|
||||
eagerLoadTerminal: Bool = false,
|
||||
placementOverride: NewWorkspacePlacement? = nil
|
||||
) -> Workspace {
|
||||
sentryBreadcrumb("workspace.create", data: ["tabCount": tabs.count + 1])
|
||||
|
|
@ -819,6 +821,10 @@ class TabManager: ObservableObject {
|
|||
} else {
|
||||
tabs.append(newWorkspace)
|
||||
}
|
||||
if eagerLoadTerminal {
|
||||
requestBackgroundWorkspaceLoad(for: newWorkspace.id)
|
||||
newWorkspace.requestBackgroundTerminalSurfaceStartIfNeeded()
|
||||
}
|
||||
if select {
|
||||
selectedTabId = newWorkspace.id
|
||||
NotificationCenter.default.post(
|
||||
|
|
@ -837,9 +843,25 @@ class TabManager: ObservableObject {
|
|||
return newWorkspace
|
||||
}
|
||||
|
||||
func requestBackgroundWorkspaceLoad(for workspaceId: UUID) {
|
||||
guard pendingBackgroundWorkspaceLoadIds.insert(workspaceId).inserted else { return }
|
||||
}
|
||||
|
||||
func completeBackgroundWorkspaceLoad(for workspaceId: UUID) {
|
||||
guard pendingBackgroundWorkspaceLoadIds.remove(workspaceId) != nil else { return }
|
||||
}
|
||||
|
||||
func pruneBackgroundWorkspaceLoads(existingIds: Set<UUID>) {
|
||||
let pruned = pendingBackgroundWorkspaceLoadIds.intersection(existingIds)
|
||||
guard pruned != pendingBackgroundWorkspaceLoadIds else { return }
|
||||
pendingBackgroundWorkspaceLoadIds = pruned
|
||||
}
|
||||
|
||||
// Keep addTab as convenience alias
|
||||
@discardableResult
|
||||
func addTab(select: Bool = true) -> Workspace { addWorkspace(select: select) }
|
||||
func addTab(select: Bool = true, eagerLoadTerminal: Bool = false) -> Workspace {
|
||||
addWorkspace(select: select, eagerLoadTerminal: eagerLoadTerminal)
|
||||
}
|
||||
|
||||
func terminalPanelForWorkspaceConfigInheritanceSource() -> TerminalPanel? {
|
||||
guard let workspace = selectedWorkspace else { return nil }
|
||||
|
|
|
|||
|
|
@ -2687,10 +2687,11 @@ class TerminalController {
|
|||
let startedAt = ProcessInfo.processInfo.systemUptime
|
||||
#endif
|
||||
v2MainSync {
|
||||
let ws = tabManager.addWorkspace(workingDirectory: cwd, select: shouldFocus)
|
||||
if !shouldFocus, let terminalPanel = ws.focusedTerminalPanel {
|
||||
terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded()
|
||||
}
|
||||
let ws = tabManager.addWorkspace(
|
||||
workingDirectory: cwd,
|
||||
select: shouldFocus,
|
||||
eagerLoadTerminal: !shouldFocus
|
||||
)
|
||||
newId = ws.id
|
||||
}
|
||||
#if DEBUG
|
||||
|
|
@ -10299,10 +10300,7 @@ class TerminalController {
|
|||
let startedAt = ProcessInfo.processInfo.systemUptime
|
||||
#endif
|
||||
DispatchQueue.main.sync {
|
||||
let workspace = tabManager.addTab(select: focus)
|
||||
if !focus, let terminalPanel = workspace.focusedTerminalPanel {
|
||||
terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded()
|
||||
}
|
||||
let workspace = tabManager.addTab(select: focus, eagerLoadTerminal: !focus)
|
||||
newTabId = workspace.id
|
||||
}
|
||||
#if DEBUG
|
||||
|
|
|
|||
|
|
@ -1480,6 +1480,18 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
return surfaceKind(for: panel)
|
||||
}
|
||||
|
||||
func requestBackgroundTerminalSurfaceStartIfNeeded() {
|
||||
for terminalPanel in panels.values.compactMap({ $0 as? TerminalPanel }) {
|
||||
terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func hasLoadedTerminalSurface() -> Bool {
|
||||
let terminalPanels = panels.values.compactMap { $0 as? TerminalPanel }
|
||||
guard !terminalPanels.isEmpty else { return true }
|
||||
return terminalPanels.contains { $0.surface.surface != nil }
|
||||
}
|
||||
|
||||
func panelTitle(panelId: UUID) -> String? {
|
||||
guard let panel = panels[panelId] else { return nil }
|
||||
let fallback = panelTitles[panelId] ?? panel.displayTitle
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue