-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWorkspaceManager+WorkspaceLifecycle.swift
More file actions
207 lines (179 loc) · 7.11 KB
/
Copy pathWorkspaceManager+WorkspaceLifecycle.swift
File metadata and controls
207 lines (179 loc) · 7.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import AppKit
import Foundation
/// Workspace lifecycle and selection flows for the shared manager.
extension WorkspaceManager {
/// Returns the currently selected workspace if any.
var selectedWorkspace: WorkspaceModel? {
workspaceCatalog.selectedWorkspace(from: workspaces, window: window)
}
/// Loads persisted workspaces and initializes selection.
func loadWorkspaces() {
guard !hasLoadedPersistedWorkspaces else { return }
workspaceCatalog.loadWorkspaces(
into: &workspaces,
window: &window,
persistence: persistence
)
synchronizePendingCompletionCursor()
updateDockBadge()
refreshFocusedWorkspaceGitBranches()
hasLoadedPersistedWorkspaces = true
}
/// Finds a workspace by identifier.
func workspace(id: UUID) -> WorkspaceModel? {
workspaceCatalog.workspace(id: id, in: workspaces)
}
/// Creates a workspace and selects it.
@discardableResult
func createWorkspace(
name: String = "New Workspace",
initialSurface: SurfaceModel = SurfaceModel.makeDefault(),
rootWorkingDirectory: String? = nil
) -> WorkspaceModel {
workspaceCatalog.createWorkspace(
name: name,
initialSurface: initialSurface,
rootWorkingDirectory: rootWorkingDirectory,
workspaces: &workspaces,
window: &window,
persistence: persistence
)
}
/// Renames an existing workspace.
func renameWorkspace(id: UUID, name: String) {
workspaceCatalog.renameWorkspace(
id: id,
name: name,
workspaces: &workspaces,
persistence: persistence
)
}
/// Deletes a workspace and adjusts current selection.
func deleteWorkspace(id: UUID) {
let releasedSurfaceIds = workspace(id: id)?.rootPane.allSurfaceIds() ?? []
workspaceCatalog.deleteWorkspace(
id: id,
workspaces: &workspaces,
window: &window,
persistence: persistence
)
clearBusySurfaces(releasedSurfaceIds)
releasedSurfaceIds.forEach {
completionNotifications.removeNotifications(for: $0)
GhosttyRuntime.shared.releaseSurface(surfaceId: $0)
clearGitBranch(surfaceId: $0)
clearProgressReport(surfaceId: $0)
}
updateDockBadge()
}
/// Requests deletion for a workspace and asks for confirmation when live terminals exist.
func requestDeleteWorkspace(id: UUID) {
guard let workspace = workspace(id: id) else { return }
let activeProcessCount = activeProcessCount(in: workspace)
guard activeProcessCount > 0 else {
deleteWorkspace(id: id)
return
}
let request = WorkspaceDeletionRequest(
workspaceId: workspace.id,
workspaceName: workspace.name,
activeProcessCount: activeProcessCount
)
guard confirmWorkspaceDeletion(request) else { return }
deleteWorkspace(id: workspace.id)
}
/// Requests deletion for the currently selected workspace.
func requestDeleteSelectedWorkspace() {
guard let workspaceId = window.selectedWorkspaceId else { return }
requestDeleteWorkspace(id: workspaceId)
}
/// Requests rename for a workspace using the shared rename sheet.
func requestRenameWorkspace(id: UUID) {
guard let workspace = workspace(id: id) else { return }
pendingWorkspaceRename = WorkspaceRenameRequest(
workspaceId: workspace.id,
currentName: workspace.name
)
}
/// Requests rename for the currently selected workspace.
func requestRenameSelectedWorkspace() {
guard let workspaceId = window.selectedWorkspaceId else { return }
requestRenameWorkspace(id: workspaceId)
}
/// Confirms the currently pending workspace rename request.
func confirmPendingWorkspaceRename(name: String) {
guard let request = pendingWorkspaceRename else { return }
pendingWorkspaceRename = nil
renameWorkspace(id: request.workspaceId, name: name)
}
/// Cancels the currently pending workspace rename request.
func cancelPendingWorkspaceRename() {
pendingWorkspaceRename = nil
}
/// Selects a workspace in the current window.
func selectWorkspace(_ id: UUID?) {
workspaceCatalog.selectWorkspace(
id,
window: &window,
workspaces: &workspaces,
persistence: persistence
)
}
/// Persists the current workspace collection.
func save() {
persistence.save(workspaces)
persistence.flush()
}
/// Freezes resume invalidation so active agent sessions remain resumable across app shutdown.
func prepareForTermination() {
guard !isTerminating else { return }
isTerminating = true
save()
}
/// Selects workspace by 1-based index in sidebar order.
func selectWorkspace(atDisplayIndex index: Int) {
guard index > 0, index <= workspaces.count else { return }
selectWorkspace(workspaces[index - 1].id)
}
/// Returns whether a workspace exists at the provided 1-based index.
func hasWorkspace(atDisplayIndex index: Int) -> Bool {
index > 0 && index <= workspaces.count
}
/// Selects the workspace before the current one, wrapping to the last.
/// Returns true if a switch occurred.
@discardableResult
func selectPreviousWorkspace() -> Bool {
guard workspaces.count > 1,
let currentId = window.selectedWorkspaceId,
let currentIndex = workspaces.firstIndex(where: { $0.id == currentId }) else { return false }
let previousIndex = (currentIndex - 1 + workspaces.count) % workspaces.count
selectWorkspace(workspaces[previousIndex].id)
return true
}
/// Selects the workspace after the current one, wrapping to the first.
/// Returns true if a switch occurred.
@discardableResult
func selectNextWorkspace() -> Bool {
guard workspaces.count > 1,
let currentId = window.selectedWorkspaceId,
let currentIndex = workspaces.firstIndex(where: { $0.id == currentId }) else { return false }
let nextIndex = (currentIndex + 1) % workspaces.count
selectWorkspace(workspaces[nextIndex].id)
return true
}
/// Restores first-responder focus to the selected workspace's active terminal surface.
func restoreSelectedWorkspaceTerminalFocus() {
guard let workspaceId = window.selectedWorkspaceId,
let workspace = workspace(id: workspaceId) else {
return
}
guard let surfaceId = workspace.focusedSurfaceId ?? workspace.rootPane.firstActiveSurfaceId() else {
return
}
GhosttyRuntime.shared.focusSurfaceHost(surfaceId: surfaceId)
}
/// Returns the number of live terminal child processes currently mounted in a workspace.
private func activeProcessCount(in workspace: WorkspaceModel) -> Int {
workspace.rootPane.allSurfaceIds().count
}
}