151 lines
4.8 KiB
Swift
151 lines
4.8 KiB
Swift
import SwiftUI
|
|
|
|
@MainActor
|
|
final class WorkspaceRegistry: ObservableObject {
|
|
static let shared = WorkspaceRegistry(store: UserDefaultsWorkspaceStore())
|
|
|
|
@Published private(set) var workspaceSummaries: [WorkspaceSummary]
|
|
|
|
private let store: any WorkspaceStoreType
|
|
private var controllers: [WorkspaceID: WorkspaceController] = [:]
|
|
private let controllerFactory: @MainActor (WorkspaceSummary) -> WorkspaceController
|
|
|
|
init(
|
|
initialWorkspaces: [WorkspaceSummary]? = nil,
|
|
store: (any WorkspaceStoreType)? = nil,
|
|
controllerFactory: @escaping @MainActor (WorkspaceSummary) -> WorkspaceController = { summary in
|
|
WorkspaceController(summary: summary)
|
|
}
|
|
) {
|
|
let resolvedStore = store ?? UserDefaultsWorkspaceStore()
|
|
let resolvedWorkspaces = initialWorkspaces ?? resolvedStore.loadWorkspaceSummaries()
|
|
|
|
self.store = resolvedStore
|
|
self.controllerFactory = controllerFactory
|
|
self.workspaceSummaries = resolvedWorkspaces
|
|
|
|
for summary in resolvedWorkspaces {
|
|
controllers[summary.id] = controllerFactory(summary)
|
|
}
|
|
|
|
_ = ensureWorkspaceExists()
|
|
}
|
|
|
|
var defaultWorkspaceID: WorkspaceID {
|
|
ensureWorkspaceExists()
|
|
}
|
|
|
|
var defaultWorkspaceController: WorkspaceController {
|
|
let workspaceID = ensureWorkspaceExists()
|
|
guard let controller = controllers[workspaceID] else {
|
|
let summary = WorkspaceSummary(id: workspaceID, name: "Main")
|
|
let controller = controllerFactory(summary)
|
|
controllers[workspaceID] = controller
|
|
return controller
|
|
}
|
|
return controller
|
|
}
|
|
|
|
func allWorkspaceSummaries() -> [WorkspaceSummary] {
|
|
workspaceSummaries
|
|
}
|
|
|
|
func summary(for id: WorkspaceID) -> WorkspaceSummary? {
|
|
workspaceSummaries.first { $0.id == id }
|
|
}
|
|
|
|
func controller(for id: WorkspaceID) -> WorkspaceController? {
|
|
controllers[id]
|
|
}
|
|
|
|
func canDeleteWorkspace(id: WorkspaceID) -> Bool {
|
|
workspaceSummaries.count > 1 && workspaceSummaries.contains { $0.id == id }
|
|
}
|
|
|
|
func deletionFallbackWorkspaceID(
|
|
forDeleting id: WorkspaceID,
|
|
preferredFallback preferredFallbackID: WorkspaceID? = nil
|
|
) -> WorkspaceID? {
|
|
let candidates = workspaceSummaries.filter { $0.id != id }
|
|
|
|
if let preferredFallbackID,
|
|
candidates.contains(where: { $0.id == preferredFallbackID }) {
|
|
return preferredFallbackID
|
|
}
|
|
|
|
return candidates.first?.id
|
|
}
|
|
|
|
@discardableResult
|
|
func ensureWorkspaceExists() -> WorkspaceID {
|
|
if let existing = workspaceSummaries.first {
|
|
return existing.id
|
|
}
|
|
return createWorkspace(named: "Main")
|
|
}
|
|
|
|
@discardableResult
|
|
func createWorkspace(named name: String? = nil) -> WorkspaceID {
|
|
let workspaceName = resolvedWorkspaceName(from: name)
|
|
let summary = WorkspaceSummary(name: workspaceName)
|
|
workspaceSummaries.append(summary)
|
|
controllers[summary.id] = controllerFactory(summary)
|
|
persistWorkspaceSummaries()
|
|
return summary.id
|
|
}
|
|
|
|
func renameWorkspace(id: WorkspaceID, to name: String) {
|
|
let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return }
|
|
guard let index = workspaceSummaries.firstIndex(where: { $0.id == id }) else { return }
|
|
|
|
workspaceSummaries[index].name = trimmed
|
|
controllers[id]?.rename(to: trimmed)
|
|
persistWorkspaceSummaries()
|
|
}
|
|
|
|
@discardableResult
|
|
func deleteWorkspace(id: WorkspaceID) -> Bool {
|
|
guard canDeleteWorkspace(id: id) else { return false }
|
|
workspaceSummaries.removeAll { $0.id == id }
|
|
controllers.removeValue(forKey: id)
|
|
_ = ensureWorkspaceExists()
|
|
persistWorkspaceSummaries()
|
|
return true
|
|
}
|
|
|
|
func updateAllWorkspacesFontSizes(_ size: CGFloat) {
|
|
for controller in controllers.values {
|
|
controller.updateAllFontSizes(size)
|
|
}
|
|
}
|
|
|
|
func updateAllWorkspacesThemes(_ theme: TerminalTheme) {
|
|
for controller in controllers.values {
|
|
controller.updateAllThemes(theme)
|
|
}
|
|
}
|
|
|
|
private func resolvedWorkspaceName(from proposedName: String?) -> String {
|
|
let trimmed = proposedName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
if !trimmed.isEmpty {
|
|
return trimmed
|
|
}
|
|
|
|
let existing = Set(workspaceSummaries.map(\.name))
|
|
if !existing.contains("Main") {
|
|
return "Main"
|
|
}
|
|
|
|
var index = 2
|
|
while existing.contains("Workspace \(index)") {
|
|
index += 1
|
|
}
|
|
return "Workspace \(index)"
|
|
}
|
|
|
|
private func persistWorkspaceSummaries() {
|
|
store.saveWorkspaceSummaries(workspaceSummaries)
|
|
}
|
|
}
|