Files
ghostty-org--ghostty/macos/Sources/Features/Terminal/TerminalView.swift
T
wehub-resource-sync bd44b5aa08
Test / build-libghostty-vt (x86_64-windows-gnu) (push) Has been cancelled
Test / build-libghostty-vt-macos (aarch64-ios) (push) Has been cancelled
Test / build-libghostty-vt-macos (aarch64-macos) (push) Has been cancelled
Test / build-libghostty-vt-macos (x86_64-macos) (push) Has been cancelled
Test / build-nix (namespace-profile-ghostty-md-arm64) (push) Has been cancelled
Test / build-dist (push) Has been cancelled
Test / GTK x11=true wayland=true (push) Has been cancelled
Test / Build -Dsimd=false (push) Has been cancelled
Test / Build -Dsimd=true (push) Has been cancelled
Test / Build -Dsentry=false (push) Has been cancelled
Test / Build -Dsentry=true (push) Has been cancelled
Test / zig-fmt (push) Has been cancelled
Test / GitHub Actions Pins (push) Has been cancelled
Test / prettier (push) Has been cancelled
Test / swiftlint (push) Has been cancelled
Test / alejandra (push) Has been cancelled
Test / typos (push) Has been cancelled
Nix / Required Checks: Nix (push) Has been cancelled
Nix / check-zig-cache-hash (push) Has been cancelled
Test / skip (push) Has been cancelled
Test / Required Checks: Test (push) Has been cancelled
Test / build-bench (push) Has been cancelled
Test / list-examples (push) Has been cancelled
Test / Example ${{ matrix.dir }} (Windows) (push) Has been cancelled
Test / build-cmake (push) Has been cancelled
Test / test-lib-vt-pkgconfig (push) Has been cancelled
Test / build-flatpak (push) Has been cancelled
Test / build-snap (push) Has been cancelled
Test / build-libghostty-vt (aarch64-linux) (push) Has been cancelled
Test / build-libghostty-vt (aarch64-macos) (push) Has been cancelled
Test / build-libghostty-vt (wasm32-freestanding) (push) Has been cancelled
Test / build-libghostty-vt (x86_64-linux) (push) Has been cancelled
Test / build-libghostty-vt (x86_64-linux-musl) (push) Has been cancelled
Test / build-libghostty-vt (x86_64-macos) (push) Has been cancelled
Test / build-libghostty-vt-android (aarch64-linux-android) (push) Has been cancelled
Test / build-libghostty-vt-android (arm-linux-androideabi) (push) Has been cancelled
Test / build-libghostty-vt-android (x86_64-linux-android) (push) Has been cancelled
Test / build-libghostty-vt-windows (push) Has been cancelled
Test / build-libghostty-windows-gnu (push) Has been cancelled
Test / build-linux (namespace-profile-ghostty-md) (push) Has been cancelled
Test / build-linux (namespace-profile-ghostty-md-arm64) (push) Has been cancelled
Test / build-linux-libghostty (push) Has been cancelled
Test / build-nix (namespace-profile-ghostty-md) (push) Has been cancelled
Test / build-dist-lib-vt (push) Has been cancelled
Test / trigger-snap (push) Has been cancelled
Test / trigger-flatpak (push) Has been cancelled
Test / build-macos (push) Has been cancelled
Test / build-macos-freetype (push) Has been cancelled
Test / test (push) Has been cancelled
Test / test-lib-vt (push) Has been cancelled
Test / GTK x11=false wayland=false (push) Has been cancelled
Test / GTK x11=true wayland=false (push) Has been cancelled
Test / GTK x11=false wayland=true (push) Has been cancelled
Test / test-macos (push) Has been cancelled
Test / test-windows (push) Has been cancelled
Test / Build -Di18n=false (push) Has been cancelled
Test / Build -Di18n=true (push) Has been cancelled
Test / Build test/fuzz-libghostty (push) Has been cancelled
Test / shellcheck (push) Has been cancelled
Test / translations (push) Has been cancelled
Test / blueprint-compiler (push) Has been cancelled
Test / Test pkg/wuffs (push) Has been cancelled
Test / Test build on Debian 13 (push) Has been cancelled
Test / valgrind (push) Has been cancelled
Test / Example ${{ matrix.dir }} (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:48 +08:00

181 lines
7.3 KiB
Swift

import SwiftUI
import GhosttyKit
import os
/// This delegate is notified of actions and property changes regarding the terminal view. This
/// delegate is optional and can be used by a TerminalView caller to react to changes such as
/// titles being set, cell sizes being changed, etc.
protocol TerminalViewDelegate: AnyObject {
/// Called when the currently focused surface changed. This can be nil.
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?)
/// The URL of the pwd should change.
func pwdDidChange(to: URL?)
/// The cell size changed.
func cellSizeDidChange(to: NSSize)
/// Perform an action. At the time of writing this is only triggered by the command palette.
func performAction(_ action: String, on: Ghostty.SurfaceView)
/// A split tree operation
func performSplitAction(_ action: TerminalSplitOperation)
}
/// The view model is a required implementation for TerminalView callers. This contains
/// the main state between the TerminalView caller and SwiftUI. This abstraction is what
/// allows AppKit to own most of the data in SwiftUI.
protocol TerminalViewModel: ObservableObject {
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
/// and children. This should be @Published.
var surfaceTree: SplitTree<Ghostty.SurfaceView> { get set }
/// The command palette state.
var commandPaletteIsShowing: Bool { get set }
/// The update overlay should be visible.
var updateOverlayIsVisible: Bool { get }
}
/// The main terminal view. This terminal view supports splits.
struct TerminalView<ViewModel: TerminalViewModel>: View {
@ObservedObject var ghostty: Ghostty.App
// The required view model
@ObservedObject var viewModel: ViewModel
// An optional delegate to receive information about terminal changes.
weak var delegate: (any TerminalViewDelegate)?
/// The most recently focused surface, equal to `focusedSurface` when it is non-nil.
@State private var lastFocusedSurface: Weak<Ghostty.SurfaceView>?
// This seems like a crutch after switching from SwiftUI to AppKit lifecycle.
@FocusState private var focused: Bool
// Various state values sent back up from the currently focused terminals.
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
@FocusedValue(\.ghosttySurfacePwd) private var surfacePwd
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
// The pwd of the focused surface as a URL
private var pwdURL: URL? {
guard let surfacePwd, surfacePwd != "" else { return nil }
return URL(fileURLWithPath: surfacePwd)
}
var body: some View {
switch ghostty.readiness {
case .loading:
Text("Loading")
case .error:
ErrorView()
case .ready:
ZStack {
VStack(spacing: 0) {
// If we're running in debug mode we show a warning so that users
// know that performance will be degraded.
if Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE {
DebugBuildWarningView()
}
TerminalSplitTreeView(
tree: viewModel.surfaceTree,
action: { delegate?.performSplitAction($0) })
.environmentObject(ghostty)
.ghosttyLastFocusedSurface(lastFocusedSurface)
.focused($focused)
.onAppear { self.focused = true }
.onChange(of: focusedSurface) { newValue in
// We want to keep track of our last focused surface so even if
// we lose focus we keep this set to the last non-nil value.
if newValue != nil {
lastFocusedSurface = .init(newValue)
self.delegate?.focusedSurfaceDidChange(to: newValue)
}
}
.onChange(of: pwdURL) { newValue in
self.delegate?.pwdDidChange(to: newValue)
}
.onChange(of: cellSize) { newValue in
guard let size = newValue else { return }
self.delegate?.cellSizeDidChange(to: size)
}
.frame(idealWidth: lastFocusedSurface?.value?.initialSize?.width,
idealHeight: lastFocusedSurface?.value?.initialSize?.height)
}
// Ignore safe area to extend up in to the titlebar region if we have the "hidden" titlebar style
.ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == .hidden ? .top : [])
if let surfaceView = lastFocusedSurface?.value {
TerminalCommandPaletteView(
surfaceView: surfaceView,
isPresented: $viewModel.commandPaletteIsShowing,
ghosttyConfig: ghostty.config,
updateViewModel: (NSApp.delegate as? AppDelegate)?.updateViewModel) { action in
self.delegate?.performAction(action, on: surfaceView)
}
}
// Show update information above all else.
if viewModel.updateOverlayIsVisible {
UpdateOverlay()
}
}
.frame(maxWidth: .greatestFiniteMagnitude, maxHeight: .greatestFiniteMagnitude)
}
}
}
private struct UpdateOverlay: View {
var body: some View {
if let appDelegate = NSApp.delegate as? AppDelegate {
VStack {
Spacer()
HStack {
Spacer()
UpdatePill(model: appDelegate.updateViewModel)
.padding(.bottom, 9)
.padding(.trailing, 9)
}
}
}
}
}
struct DebugBuildWarningView: View {
@State private var isPopover = false
var body: some View {
HStack {
Spacer()
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.yellow)
Text("You're running a debug build of Ghostty! Performance will be degraded.")
.padding(.all, 8)
.popover(isPresented: $isPopover, arrowEdge: .bottom) {
Text("""
Debug builds of Ghostty are very slow and you may experience
performance problems. Debug builds are only recommended during
development.
""")
.padding(.all)
}
Spacer()
}
.background(Color(.windowBackgroundColor))
.frame(maxWidth: .infinity)
.accessibilityElement(children: .combine)
.accessibilityLabel("Debug build warning")
.accessibilityValue("Debug builds of Ghostty are very slow and you may experience performance problems. Debug builds are only recommended during development.")
.accessibilityAddTraits(.isStaticText)
.onTapGesture {
isPopover = true
}
}
}