128 lines
4 KiB
Swift
128 lines
4 KiB
Swift
import AppKit
|
|
import SwiftUI
|
|
import Foundation
|
|
|
|
class Tab: Identifiable, ObservableObject {
|
|
let id: UUID
|
|
@Published var title: String
|
|
@Published var currentDirectory: String
|
|
let terminalSurface: TerminalSurface
|
|
|
|
init(title: String = "Terminal") {
|
|
self.id = UUID()
|
|
self.title = title
|
|
self.currentDirectory = FileManager.default.homeDirectoryForCurrentUser.path
|
|
self.terminalSurface = TerminalSurface(tabId: id)
|
|
}
|
|
}
|
|
|
|
class TabManager: ObservableObject {
|
|
@Published var tabs: [Tab] = []
|
|
@Published var selectedTabId: UUID?
|
|
private var observers: [NSObjectProtocol] = []
|
|
|
|
init() {
|
|
addTab()
|
|
observers.append(NotificationCenter.default.addObserver(
|
|
forName: .ghosttyDidSetTitle,
|
|
object: nil,
|
|
queue: .main
|
|
) { [weak self] notification in
|
|
guard let self else { return }
|
|
guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID else { return }
|
|
guard let title = notification.userInfo?[GhosttyNotificationKey.title] as? String else { return }
|
|
self.updateTabTitle(tabId: tabId, title: title)
|
|
})
|
|
}
|
|
|
|
func addTab() {
|
|
let newTab = Tab(title: "Terminal \(tabs.count + 1)")
|
|
tabs.append(newTab)
|
|
selectedTabId = newTab.id
|
|
NotificationCenter.default.post(
|
|
name: .ghosttyDidFocusTab,
|
|
object: nil,
|
|
userInfo: [GhosttyNotificationKey.tabId: newTab.id]
|
|
)
|
|
}
|
|
|
|
func closeTab(_ tab: Tab) {
|
|
guard tabs.count > 1 else { return }
|
|
|
|
if let index = tabs.firstIndex(where: { $0.id == tab.id }) {
|
|
tabs.remove(at: index)
|
|
|
|
if selectedTabId == tab.id {
|
|
if index > 0 {
|
|
selectedTabId = tabs[index - 1].id
|
|
} else {
|
|
selectedTabId = tabs.first?.id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func closeCurrentTab() {
|
|
guard let selectedId = selectedTabId,
|
|
let tab = tabs.first(where: { $0.id == selectedId }) else { return }
|
|
closeTab(tab)
|
|
}
|
|
|
|
func selectTab(_ tab: Tab) {
|
|
selectedTabId = tab.id
|
|
}
|
|
|
|
func titleForTab(_ tabId: UUID) -> String? {
|
|
tabs.first(where: { $0.id == tabId })?.title
|
|
}
|
|
|
|
private func updateTabTitle(tabId: UUID, title: String) {
|
|
guard !title.isEmpty else { return }
|
|
guard let index = tabs.firstIndex(where: { $0.id == tabId }) else { return }
|
|
if tabs[index].title != title {
|
|
tabs[index].title = title
|
|
}
|
|
}
|
|
|
|
func focusTab(_ tabId: UUID) {
|
|
guard tabs.contains(where: { $0.id == tabId }) else { return }
|
|
selectedTabId = tabId
|
|
NotificationCenter.default.post(
|
|
name: .ghosttyDidFocusTab,
|
|
object: nil,
|
|
userInfo: [GhosttyNotificationKey.tabId: tabId]
|
|
)
|
|
|
|
DispatchQueue.main.async {
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
NSApp.unhide(nil)
|
|
if let window = NSApp.keyWindow ?? NSApp.windows.first {
|
|
window.makeKeyAndOrderFront(nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func selectNextTab() {
|
|
guard let currentId = selectedTabId,
|
|
let currentIndex = tabs.firstIndex(where: { $0.id == currentId }) else { return }
|
|
let nextIndex = (currentIndex + 1) % tabs.count
|
|
selectedTabId = tabs[nextIndex].id
|
|
}
|
|
|
|
func selectPreviousTab() {
|
|
guard let currentId = selectedTabId,
|
|
let currentIndex = tabs.firstIndex(where: { $0.id == currentId }) else { return }
|
|
let prevIndex = (currentIndex - 1 + tabs.count) % tabs.count
|
|
selectedTabId = tabs[prevIndex].id
|
|
}
|
|
|
|
func selectTab(at index: Int) {
|
|
guard index >= 0 && index < tabs.count else { return }
|
|
selectedTabId = tabs[index].id
|
|
}
|
|
}
|
|
|
|
extension Notification.Name {
|
|
static let ghosttyDidSetTitle = Notification.Name("ghosttyDidSetTitle")
|
|
static let ghosttyDidFocusTab = Notification.Name("ghosttyDidFocusTab")
|
|
}
|