Files
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

179 lines
6.0 KiB
Swift

import AppKit
import AppIntents
import GhosttyKit
/// App intent that allows creating a new terminal window or tab.
///
/// This requires macOS 15 or greater because we use features of macOS 15 here.
@available(macOS 15.0, *)
struct NewTerminalIntent: AppIntent {
static var title: LocalizedStringResource = "New Terminal"
static var description = IntentDescription("Create a new terminal.")
@Parameter(
title: "Location",
description: "The location that the terminal should be created.",
default: .window
)
var location: NewTerminalLocation
@Parameter(
title: "Command",
description: "Command to execute within your configured shell.",
)
var command: String?
@Parameter(
title: "Working Directory",
description: "The working directory to open in the terminal.",
supportedContentTypes: [.folder]
)
var workingDirectory: IntentFile?
@Parameter(
title: "Environment Variables",
description: "Environment variables in `KEY=VALUE` format.",
default: []
)
var env: [String]
@Parameter(
title: "Parent Terminal",
description: "The terminal to inherit the base configuration from."
)
var parent: TerminalEntity?
// Performing in the background can avoid opening multiple windows at the same time
// using `foreground` will cause `perform` and `AppDelegate.applicationDidBecomeActive(_:)`/`AppDelegate.applicationShouldHandleReopen(_:hasVisibleWindows:)` running at the 'same' time
#if compiler(>=6.2)
@available(macOS 26.0, *)
static var supportedModes: IntentModes = .background
#endif
@available(macOS, obsoleted: 26.0, message: "Replaced by supportedModes")
static var openAppWhenRun = false
@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<TerminalEntity?> {
guard await requestIntentPermission() else {
throw GhosttyIntentError.permissionDenied
}
guard let appDelegate = NSApp.delegate as? AppDelegate else {
throw GhosttyIntentError.appUnavailable
}
let ghostty = appDelegate.ghostty
var config = Ghostty.SurfaceConfiguration()
// We don't run command as "command" and instead use "initialInput" so
// that we can get all the login scripts to setup things like PATH.
if let command, !command.isEmpty {
config.initialInput = "\(command); exit\n"
}
// If we were given a working directory then open that directory
if let url = workingDirectory?.fileURL {
let dir = url.hasDirectoryPath ? url : url.deletingLastPathComponent()
config.workingDirectory = dir.path(percentEncoded: false)
}
// Parse environment variables from KEY=VALUE format
for envVar in env {
if let separatorIndex = envVar.firstIndex(of: "=") {
let key = String(envVar[..<separatorIndex])
let value = String(envVar[envVar.index(after: separatorIndex)...])
config.environmentVariables[key] = value
}
}
// Determine if we have a parent and get it
let parent: Ghostty.SurfaceView?
if let parentParam = self.parent {
guard let view = parentParam.surfaceView else {
throw GhosttyIntentError.surfaceNotFound
}
parent = view
} else if let preferred = TerminalController.preferredParent {
parent = preferred.focusedSurface ?? preferred.surfaceTree.root?.leftmostLeaf()
} else {
parent = nil
}
defer {
if !NSApp.isActive {
NSApp.activate(ignoringOtherApps: true)
}
}
switch location {
case .window:
let newController = TerminalController.newWindow(
ghostty,
withBaseConfig: config,
withParent: parent?.window)
if let view = newController.surfaceTree.root?.leftmostLeaf() {
return .result(value: await TerminalEntity(view: view))
}
case .tab:
let newController = TerminalController.newTab(
ghostty,
from: parent?.window,
withBaseConfig: config)
if let view = newController?.surfaceTree.root?.leftmostLeaf() {
return .result(value: await TerminalEntity(view: view))
}
case .splitLeft, .splitRight, .splitUp, .splitDown:
guard let parent,
let controller = parent.window?.windowController as? BaseTerminalController else {
throw GhosttyIntentError.surfaceNotFound
}
if let view = controller.newSplit(
at: parent,
direction: location.splitDirection!,
baseConfig: config
) {
return .result(value: await TerminalEntity(view: view))
}
}
return .result(value: .none)
}
}
// MARK: NewTerminalLocation
enum NewTerminalLocation: String {
case tab
case window
case splitLeft = "split:left"
case splitRight = "split:right"
case splitUp = "split:up"
case splitDown = "split:down"
var splitDirection: SplitTree<Ghostty.SurfaceView>.NewDirection? {
switch self {
case .splitLeft: return .left
case .splitRight: return .right
case .splitUp: return .up
case .splitDown: return .down
default: return nil
}
}
}
extension NewTerminalLocation: AppEnum {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Terminal Location")
static var caseDisplayRepresentations: [Self: DisplayRepresentation] = [
.tab: .init(title: "Tab"),
.window: .init(title: "Window"),
.splitLeft: .init(title: "Split Left"),
.splitRight: .init(title: "Split Right"),
.splitUp: .init(title: "Split Up"),
.splitDown: .init(title: "Split Down"),
]
}