Yep. AI rewrote the whole thing.

This commit is contained in:
2026-03-13 03:24:24 +11:00
parent e4719cb9f4
commit fe6c7d8c12
47 changed files with 5348 additions and 1182 deletions

View File

@@ -1,118 +1,75 @@
import SwiftUI
import Combine
/// Manages multiple terminal tabs. Singleton shared across all screens
/// whichever notch is currently open displays these tabs.
/// Compatibility adapter for the legacy single-workspace architecture.
/// New code should use `WorkspaceRegistry` + `WorkspaceController`.
@MainActor
class TerminalManager: ObservableObject {
static let shared = TerminalManager()
@Published var tabs: [TerminalSession] = []
@Published var activeTabIndex: Int = 0
@AppStorage(NotchSettings.Keys.terminalFontSize)
private var fontSize: Double = NotchSettings.Defaults.terminalFontSize
@AppStorage(NotchSettings.Keys.terminalTheme)
private var theme: String = NotchSettings.Defaults.terminalTheme
private var cancellables = Set<AnyCancellable>()
private var workspaceCancellable: AnyCancellable?
private init() {
newTab()
workspaceCancellable = WorkspaceRegistry.shared.defaultWorkspaceController.objectWillChange
.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
// MARK: - Active tab
private var workspace: WorkspaceController {
WorkspaceRegistry.shared.defaultWorkspaceController
}
var tabs: [TerminalSession] {
workspace.tabs
}
var activeTabIndex: Int {
workspace.activeTabIndex
}
var activeTab: TerminalSession? {
guard tabs.indices.contains(activeTabIndex) else { return nil }
return tabs[activeTabIndex]
workspace.activeTab
}
/// Short title for the closed notch bar the active tab's process name.
var activeTitle: String {
activeTab?.title ?? "shell"
workspace.activeTitle
}
// MARK: - Tab operations
func newTab() {
let session = TerminalSession(
fontSize: CGFloat(fontSize),
theme: TerminalTheme.resolve(theme)
)
// Forward title changes to trigger view updates in this manager
session.$title
.receive(on: RunLoop.main)
.sink { [weak self] _ in self?.objectWillChange.send() }
.store(in: &cancellables)
tabs.append(session)
activeTabIndex = tabs.count - 1
}
func closeTab(at index: Int) {
guard tabs.indices.contains(index) else { return }
tabs[index].terminate()
tabs.remove(at: index)
// Adjust active index
if tabs.isEmpty {
newTab()
} else if activeTabIndex >= tabs.count {
activeTabIndex = tabs.count - 1
}
workspace.newTab()
}
func closeActiveTab() {
closeTab(at: activeTabIndex)
workspace.closeActiveTab()
}
func closeTab(at index: Int) {
workspace.closeTab(at: index)
}
func switchToTab(at index: Int) {
guard tabs.indices.contains(index) else { return }
activeTabIndex = index
workspace.switchToTab(at: index)
}
func nextTab() {
guard tabs.count > 1 else { return }
activeTabIndex = (activeTabIndex + 1) % tabs.count
workspace.nextTab()
}
func previousTab() {
guard tabs.count > 1 else { return }
activeTabIndex = (activeTabIndex - 1 + tabs.count) % tabs.count
}
/// Removes the tab at the given index and returns the session so it
/// can be hosted in a pop-out window.
func detachTab(at index: Int) -> TerminalSession? {
guard tabs.indices.contains(index) else { return nil }
let session = tabs.remove(at: index)
if tabs.isEmpty {
newTab()
} else if activeTabIndex >= tabs.count {
activeTabIndex = tabs.count - 1
}
return session
workspace.previousTab()
}
func detachActiveTab() -> TerminalSession? {
detachTab(at: activeTabIndex)
workspace.detachActiveTab()
}
/// Updates font size on all existing terminal sessions.
func updateAllFontSizes(_ size: CGFloat) {
for tab in tabs {
tab.updateFontSize(size)
}
workspace.updateAllFontSizes(size)
}
func updateAllThemes(_ theme: TerminalTheme) {
for tab in tabs {
tab.applyTheme(theme)
}
workspace.updateAllThemes(theme)
}
}