diff --git a/macos/Ghostty.sdef b/macos/Ghostty.sdef index 47fae636f..7cbe898a1 100644 --- a/macos/Ghostty.sdef +++ b/macos/Ghostty.sdef @@ -182,6 +182,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift b/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift index e8fdbb0a9..3f240cf6e 100644 --- a/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift @@ -31,3 +31,63 @@ final class ScriptCloseCommand: NSScriptCommand { return nil } } + +/// Handler for the `close tab` AppleScript command defined in `Ghostty.sdef`. +@MainActor +@objc(GhosttyScriptCloseTabCommand) +final class ScriptCloseTabCommand: NSScriptCommand { + override func performDefaultImplementation() -> Any? { + guard let tab = evaluatedArguments?["tab"] as? ScriptTab else { + scriptErrorNumber = errAEParamMissed + scriptErrorString = "Missing tab target." + return nil + } + + guard let controller = tab.parentController else { + scriptErrorNumber = errAEEventFailed + scriptErrorString = "Tab is no longer available." + return nil + } + + if let terminalController = controller as? TerminalController { + terminalController.closeTabImmediately(registerRedo: false) + return nil + } + + guard let targetWindow = tab.parentWindow else { + scriptErrorNumber = errAEEventFailed + scriptErrorString = "Tab window is no longer available." + return nil + } + + targetWindow.close() + return nil + } +} + +/// Handler for the `close window` AppleScript command defined in `Ghostty.sdef`. +@MainActor +@objc(GhosttyScriptCloseWindowCommand) +final class ScriptCloseWindowCommand: NSScriptCommand { + override func performDefaultImplementation() -> Any? { + guard let window = evaluatedArguments?["window"] as? ScriptWindow else { + scriptErrorNumber = errAEParamMissed + scriptErrorString = "Missing window target." + return nil + } + + if let terminalController = window.preferredController as? TerminalController { + terminalController.closeWindowImmediately() + return nil + } + + guard let targetWindow = window.preferredParentWindow else { + scriptErrorNumber = errAEEventFailed + scriptErrorString = "Window is no longer available." + return nil + } + + targetWindow.close() + return nil + } +} diff --git a/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift b/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift index 024aee4de..edda43a45 100644 --- a/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift @@ -31,3 +31,49 @@ final class ScriptFocusCommand: NSScriptCommand { return nil } } + +/// Handler for the `activate window` AppleScript command defined in `Ghostty.sdef`. +@MainActor +@objc(GhosttyScriptActivateWindowCommand) +final class ScriptActivateWindowCommand: NSScriptCommand { + override func performDefaultImplementation() -> Any? { + guard let window = evaluatedArguments?["window"] as? ScriptWindow else { + scriptErrorNumber = errAEParamMissed + scriptErrorString = "Missing window target." + return nil + } + + guard let targetWindow = window.preferredParentWindow else { + scriptErrorNumber = errAEEventFailed + scriptErrorString = "Window is no longer available." + return nil + } + + targetWindow.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + return nil + } +} + +/// Handler for the `select tab` AppleScript command defined in `Ghostty.sdef`. +@MainActor +@objc(GhosttyScriptSelectTabCommand) +final class ScriptSelectTabCommand: NSScriptCommand { + override func performDefaultImplementation() -> Any? { + guard let tab = evaluatedArguments?["tab"] as? ScriptTab else { + scriptErrorNumber = errAEParamMissed + scriptErrorString = "Missing tab target." + return nil + } + + guard let targetWindow = tab.parentWindow else { + scriptErrorNumber = errAEEventFailed + scriptErrorString = "Tab is no longer available." + return nil + } + + targetWindow.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + return nil + } +} diff --git a/macos/Sources/Features/AppleScript/ScriptTab.swift b/macos/Sources/Features/AppleScript/ScriptTab.swift index aa643db5a..bda694ebc 100644 --- a/macos/Sources/Features/AppleScript/ScriptTab.swift +++ b/macos/Sources/Features/AppleScript/ScriptTab.swift @@ -63,6 +63,16 @@ final class ScriptTab: NSObject { return window?.tabIsSelected(controller) ?? false } + /// Best-effort native window containing this tab. + var parentWindow: NSWindow? { + controller?.window + } + + /// Live controller backing this tab wrapper. + var parentController: BaseTerminalController? { + controller + } + /// Exposed as the AppleScript `terminals` element on a tab. /// /// Returns all terminal surfaces (split panes) within this tab. diff --git a/macos/Sources/Features/AppleScript/ScriptWindow.swift b/macos/Sources/Features/AppleScript/ScriptWindow.swift index 089ea5e18..97822e973 100644 --- a/macos/Sources/Features/AppleScript/ScriptWindow.swift +++ b/macos/Sources/Features/AppleScript/ScriptWindow.swift @@ -116,6 +116,11 @@ final class ScriptWindow: NSObject { selectedController?.window ?? controllers.first?.window } + /// Best-effort controller to use for window-scoped AppleScript commands. + var preferredController: BaseTerminalController? { + selectedController ?? controllers.first + } + /// Resolves a previously generated tab ID back to a live controller. private func controller(tabID: String) -> BaseTerminalController? { controllers.first(where: { ScriptTab.stableID(controller: $0) == tabID })