macos: add focus and close AppleScript commands for terminals

Add two new AppleScript commands to the scripting dictionary:

- `focus terminal <terminal>` — focuses the given terminal and brings
  its window to the front.
- `close terminal <terminal>` — closes the given terminal without a
  confirmation prompt.

Each command is implemented as an NSScriptCommand subclass following
the same pattern as the existing split command.
This commit is contained in:
Mitchell Hashimoto
2026-03-05 20:57:08 -08:00
parent ef669eeae7
commit 1742aeda50
4 changed files with 88 additions and 7 deletions

View File

@@ -1,19 +1,20 @@
# macOS Ghostty Application
- Use `swiftlint` for formatting and linting Swift code.
- If code outside of this directory is modified, use
- If code outside of `macos/` directory is modified, use
`zig build -Demit-macos-app=false` before building the macOS app to update
the underlying Ghostty library.
- Use `build.nu` to build the macOS app, do not use `zig build`
- Use `macos/build.nu` to build the macOS app, do not use `zig build`
(except to build the underlying library as mentioned above).
- Build: `build.nu [--scheme Ghostty] [--configuration Debug] [--action build]`
- Output: `build/<configuration>/Ghostty.app` (e.g. `build/Debug/Ghostty.app`)
- Run unit tests directly with `build.nu --action test`
- Build: `macos/build.nu [--scheme Ghostty] [--configuration Debug] [--action build]`
- Output: `macos/build/<configuration>/Ghostty.app` (e.g. `macos/build/Debug/Ghostty.app`)
- Run unit tests directly with `macos/build.nu --action test`
## AppleScript
- The AppleScript scripting definition is in `Ghostty.sdef`.
- The AppleScript scripting definition is in `macos/Ghostty.sdef`.
- Test AppleScript support:
(1) Build with `build.nu`
(1) Build with `macos/build.nu`
(2) Launch and activate the app via osascript using the absolute path
to the built app bundle:
`osascript -e 'tell application "<absolute path to build/Debug/Ghostty.app>" to activate'`

View File

@@ -44,6 +44,20 @@
</parameter>
<result type="terminal" description="The newly created terminal."/>
</command>
<command name="focus" code="GhstFcus" description="Focus a terminal, bringing its window to the front.">
<cocoa class="GhosttyScriptFocusCommand"/>
<parameter name="terminal" code="GFcT" type="terminal" description="The terminal to focus.">
<cocoa key="terminal"/>
</parameter>
</command>
<command name="close" code="GhstClos" description="Close a terminal.">
<cocoa class="GhosttyScriptCloseCommand"/>
<parameter name="terminal" code="GClT" type="terminal" description="The terminal to close.">
<cocoa key="terminal"/>
</parameter>
</command>
</suite>
<!--

View File

@@ -0,0 +1,33 @@
import AppKit
/// Handler for the `close` AppleScript command defined in `Ghostty.sdef`.
///
/// Cocoa scripting instantiates this class because the command's `<cocoa>` element
/// specifies `class="GhosttyScriptCloseCommand"`. The runtime calls
/// `performDefaultImplementation()` to execute the command.
@MainActor
@objc(GhosttyScriptCloseCommand)
final class ScriptCloseCommand: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
guard let terminal = evaluatedArguments?["terminal"] as? ScriptTerminal else {
scriptErrorNumber = errAEParamMissed
scriptErrorString = "Missing terminal target."
return nil
}
guard let surfaceView = terminal.surfaceView else {
scriptErrorNumber = errAEEventFailed
scriptErrorString = "Terminal surface is no longer available."
return nil
}
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else {
scriptErrorNumber = errAEEventFailed
scriptErrorString = "Terminal is not in a window."
return nil
}
controller.closeSurface(surfaceView, withConfirmation: false)
return nil
}
}

View File

@@ -0,0 +1,33 @@
import AppKit
/// Handler for the `focus` AppleScript command defined in `Ghostty.sdef`.
///
/// Cocoa scripting instantiates this class because the command's `<cocoa>` element
/// specifies `class="GhosttyScriptFocusCommand"`. The runtime calls
/// `performDefaultImplementation()` to execute the command.
@MainActor
@objc(GhosttyScriptFocusCommand)
final class ScriptFocusCommand: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
guard let terminal = evaluatedArguments?["terminal"] as? ScriptTerminal else {
scriptErrorNumber = errAEParamMissed
scriptErrorString = "Missing terminal target."
return nil
}
guard let surfaceView = terminal.surfaceView else {
scriptErrorNumber = errAEEventFailed
scriptErrorString = "Terminal surface is no longer available."
return nil
}
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else {
scriptErrorNumber = errAEEventFailed
scriptErrorString = "Terminal is not in a window."
return nil
}
controller.focusSurface(surfaceView)
return nil
}
}