Initial commit: macOS terminal app with vertical tabs using libghostty
Features: - Vertical tabs sidebar with SwiftUI - Terminal emulation via GhosttyKit.xcframework (libghostty) - Keyboard shortcuts: Cmd+T/N, Ctrl+Shift+` (new tab), Cmd+W (close), Cmd+Shift+[/], Ctrl+Tab (navigation), Cmd+1-9 (jump to tab) - Reads Ghostty config from ~/Library/Application Support/com.mitchellh.ghostty/config - Metal-based rendering
This commit is contained in:
commit
c5bd543fe0
14 changed files with 2682 additions and 0 deletions
73
Sources/TabManager.swift
Normal file
73
Sources/TabManager.swift
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
class Tab: Identifiable, ObservableObject {
|
||||
let id = UUID()
|
||||
@Published var title: String
|
||||
@Published var currentDirectory: String
|
||||
|
||||
init(title: String = "Terminal") {
|
||||
self.title = title
|
||||
self.currentDirectory = FileManager.default.homeDirectoryForCurrentUser.path
|
||||
}
|
||||
}
|
||||
|
||||
class TabManager: ObservableObject {
|
||||
@Published var tabs: [Tab] = []
|
||||
@Published var selectedTabId: UUID?
|
||||
|
||||
init() {
|
||||
addTab()
|
||||
}
|
||||
|
||||
func addTab() {
|
||||
let newTab = Tab(title: "Terminal \(tabs.count + 1)")
|
||||
tabs.append(newTab)
|
||||
selectedTabId = 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 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue