macos: Close Terminal Intent
parent
0a27aef508
commit
f096675eaf
|
|
@ -13,6 +13,7 @@
|
|||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
|
||||
A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; };
|
||||
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; };
|
||||
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
|
||||
|
|
@ -149,6 +150,7 @@
|
|||
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
|
||||
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
|
||||
A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = "<group>"; };
|
||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
|
||||
A51544FD2DFB1110009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitlebarTabsTahoeTerminalWindow.swift; sourceTree = "<group>"; };
|
||||
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebarTahoe.xib; sourceTree = "<group>"; };
|
||||
|
|
@ -625,6 +627,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
A5E408412E0453370035FEAC /* Entities */,
|
||||
A511940E2E050590007258CC /* CloseTerminalIntent.swift */,
|
||||
A5E4082D2E0237410035FEAC /* NewTerminalIntent.swift */,
|
||||
A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */,
|
||||
A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */,
|
||||
|
|
@ -793,6 +796,7 @@
|
|||
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
|
||||
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
|
||||
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */,
|
||||
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */,
|
||||
A5E408382E03C7DA0035FEAC /* Ghostty.Surface.swift in Sources */,
|
||||
A5593FE72DF927D200B47B10 /* TransparentTitlebarTerminalWindow.swift in Sources */,
|
||||
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import AppKit
|
||||
import AppIntents
|
||||
import GhosttyKit
|
||||
|
||||
struct CloseTerminalIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Close Terminal"
|
||||
static var description = IntentDescription("Close an existing terminal.")
|
||||
|
||||
@Parameter(
|
||||
title: "Terminal",
|
||||
description: "The terminal to close.",
|
||||
)
|
||||
var terminal: TerminalEntity
|
||||
|
||||
@Parameter(
|
||||
title: "Command",
|
||||
description: "Command to execute instead of the default shell.",
|
||||
default: true
|
||||
)
|
||||
var confirm: Bool
|
||||
|
||||
@available(macOS 26.0, *)
|
||||
static var supportedModes: IntentModes = .background
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
guard let surfaceView = terminal.surfaceView else {
|
||||
throw GhosttyIntentError.surfaceNotFound
|
||||
}
|
||||
|
||||
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else {
|
||||
return .result()
|
||||
}
|
||||
|
||||
controller.closeSurface(surfaceView, withConfirmation: confirm)
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
|
|
@ -218,19 +218,19 @@ class QuickTerminalController: BaseTerminalController {
|
|||
}
|
||||
}
|
||||
|
||||
override func closeSurfaceNode(
|
||||
override func closeSurface(
|
||||
_ node: SplitTree<Ghostty.SurfaceView>.Node,
|
||||
withConfirmation: Bool = true
|
||||
) {
|
||||
// If this isn't the root then we're dealing with a split closure.
|
||||
if surfaceTree.root != node {
|
||||
super.closeSurfaceNode(node, withConfirmation: withConfirmation)
|
||||
super.closeSurface(node, withConfirmation: withConfirmation)
|
||||
return
|
||||
}
|
||||
|
||||
// If this isn't a final leaf then we're dealing with a split closure
|
||||
guard case .leaf(let surface) = node else {
|
||||
super.closeSurfaceNode(node, withConfirmation: withConfirmation)
|
||||
super.closeSurface(node, withConfirmation: withConfirmation)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -300,6 +300,46 @@ class BaseTerminalController: NSWindowController,
|
|||
self.alert = alert
|
||||
}
|
||||
|
||||
/// Close a surface from a view.
|
||||
func closeSurface(
|
||||
_ view: Ghostty.SurfaceView,
|
||||
withConfirmation: Bool = true
|
||||
) {
|
||||
guard let node = surfaceTree.root?.node(view: view) else { return }
|
||||
closeSurface(node, withConfirmation: withConfirmation)
|
||||
}
|
||||
|
||||
/// Close a surface node (which may contain splits), requesting confirmation if necessary.
|
||||
///
|
||||
/// This will also insert the proper undo stack information in.
|
||||
func closeSurface(
|
||||
_ node: SplitTree<Ghostty.SurfaceView>.Node,
|
||||
withConfirmation: Bool = true
|
||||
) {
|
||||
// This node must be part of our tree
|
||||
guard surfaceTree.contains(node) else { return }
|
||||
|
||||
// If the child process is not alive, then we exit immediately
|
||||
guard withConfirmation else {
|
||||
removeSurfaceNode(node)
|
||||
return
|
||||
}
|
||||
|
||||
// Confirm close. We use an NSAlert instead of a SwiftUI confirmationDialog
|
||||
// due to SwiftUI bugs (see Ghostty #560). To repeat from #560, the bug is that
|
||||
// confirmationDialog allows the user to Cmd-W close the alert, but when doing
|
||||
// so SwiftUI does not update any of the bindings to note that window is no longer
|
||||
// being shown, and provides no callback to detect this.
|
||||
confirmClose(
|
||||
messageText: "Close Terminal?",
|
||||
informativeText: "The terminal still has a running process. If you close the terminal the process will be killed."
|
||||
) { [weak self] in
|
||||
if let self {
|
||||
self.removeSurfaceNode(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Split Tree Management
|
||||
|
||||
/// Find the next surface to focus when a node is being closed.
|
||||
|
|
@ -460,42 +500,11 @@ class BaseTerminalController: NSWindowController,
|
|||
@objc private func ghosttyDidCloseSurface(_ notification: Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard let node = surfaceTree.root?.node(view: target) else { return }
|
||||
closeSurfaceNode(
|
||||
closeSurface(
|
||||
node,
|
||||
withConfirmation: (notification.userInfo?["process_alive"] as? Bool) ?? false)
|
||||
}
|
||||
|
||||
/// Close a surface node (which may contain splits), requesting confirmation if necessary.
|
||||
///
|
||||
/// This will also insert the proper undo stack information in.
|
||||
func closeSurfaceNode(
|
||||
_ node: SplitTree<Ghostty.SurfaceView>.Node,
|
||||
withConfirmation: Bool = true
|
||||
) {
|
||||
// This node must be part of our tree
|
||||
guard surfaceTree.contains(node) else { return }
|
||||
|
||||
// If the child process is not alive, then we exit immediately
|
||||
guard withConfirmation else {
|
||||
removeSurfaceNode(node)
|
||||
return
|
||||
}
|
||||
|
||||
// Confirm close. We use an NSAlert instead of a SwiftUI confirmationDialog
|
||||
// due to SwiftUI bugs (see Ghostty #560). To repeat from #560, the bug is that
|
||||
// confirmationDialog allows the user to Cmd-W close the alert, but when doing
|
||||
// so SwiftUI does not update any of the bindings to note that window is no longer
|
||||
// being shown, and provides no callback to detect this.
|
||||
confirmClose(
|
||||
messageText: "Close Terminal?",
|
||||
informativeText: "The terminal still has a running process. If you close the terminal the process will be killed."
|
||||
) { [weak self] in
|
||||
if let self {
|
||||
self.removeSurfaceNode(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func ghosttyDidNewSplit(_ notification: Notification) {
|
||||
// The target must be within our tree
|
||||
guard let oldView = notification.object as? Ghostty.SurfaceView else { return }
|
||||
|
|
|
|||
|
|
@ -519,13 +519,13 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||
}
|
||||
|
||||
/// This is called anytime a node in the surface tree is being removed.
|
||||
override func closeSurfaceNode(
|
||||
override func closeSurface(
|
||||
_ node: SplitTree<Ghostty.SurfaceView>.Node,
|
||||
withConfirmation: Bool = true
|
||||
) {
|
||||
// If this isn't the root then we're dealing with a split closure.
|
||||
if surfaceTree.root != node {
|
||||
super.closeSurfaceNode(node, withConfirmation: withConfirmation)
|
||||
super.closeSurface(node, withConfirmation: withConfirmation)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue