mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
macos: add AppleScript split command
Add a new `split` command to the AppleScript scripting dictionary that splits a terminal in a given direction (right, left, down, up) and returns the newly created terminal. The command is exposed as: split terminal <terminal> direction <direction> Also adds a `fourCharCode` String extension for converting four-character ASCII strings to their FourCharCode (UInt32) representation.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
<responds-to command="perform action">
|
||||
<cocoa method="handlePerformActionScriptCommand:"/>
|
||||
</responds-to>
|
||||
|
||||
<element type="terminal" access="r">
|
||||
<cocoa key="terminals"/>
|
||||
</element>
|
||||
@@ -25,6 +26,24 @@
|
||||
<parameter name="on" code="GonT" type="terminal" description="Target terminal."/>
|
||||
<result type="boolean" description="True when the action was performed."/>
|
||||
</command>
|
||||
|
||||
<enumeration name="split direction" code="GSpD" description="Direction for a new split.">
|
||||
<enumerator name="right" code="GSrt" description="Split to the right."/>
|
||||
<enumerator name="left" code="GSlf" description="Split to the left."/>
|
||||
<enumerator name="down" code="GSdn" description="Split downward."/>
|
||||
<enumerator name="up" code="GSup" description="Split upward."/>
|
||||
</enumeration>
|
||||
|
||||
<command name="split" code="GhstSplt" description="Split a terminal in the given direction.">
|
||||
<cocoa class="GhosttyScriptSplitCommand"/>
|
||||
<parameter name="terminal" code="GSpT" type="terminal" description="The terminal to split.">
|
||||
<cocoa key="terminal"/>
|
||||
</parameter>
|
||||
<parameter name="direction" code="GSpd" type="split direction" description="The direction to split.">
|
||||
<cocoa key="direction"/>
|
||||
</parameter>
|
||||
<result type="terminal" description="The newly created terminal."/>
|
||||
</command>
|
||||
</suite>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -137,7 +137,8 @@
|
||||
"Features/App Intents/NewTerminalIntent.swift",
|
||||
"Features/App Intents/QuickTerminalIntent.swift",
|
||||
"Features/AppleScript/AppDelegate+AppleScript.swift",
|
||||
Features/AppleScript/AppleScriptTerminal.swift,
|
||||
Features/AppleScript/ScriptSplitCommand.swift,
|
||||
Features/AppleScript/ScriptTerminal.swift,
|
||||
Features/ClipboardConfirmation/ClipboardConfirmation.xib,
|
||||
Features/ClipboardConfirmation/ClipboardConfirmationController.swift,
|
||||
Features/ClipboardConfirmation/ClipboardConfirmationView.swift,
|
||||
|
||||
74
macos/Sources/Features/AppleScript/ScriptSplitCommand.swift
Normal file
74
macos/Sources/Features/AppleScript/ScriptSplitCommand.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
import AppKit
|
||||
|
||||
/// Handler for the `split` AppleScript command defined in `Ghostty.sdef`.
|
||||
///
|
||||
/// Cocoa scripting instantiates this class because the command's `<cocoa>` element
|
||||
/// specifies `class="GhosttyScriptSplitCommand"`. The runtime calls
|
||||
/// `performDefaultImplementation()` to execute the command.
|
||||
@MainActor
|
||||
@objc(GhosttyScriptSplitCommand)
|
||||
final class ScriptSplitCommand: 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 directionCode = evaluatedArguments?["direction"] as? UInt32,
|
||||
let direction = ScriptSplitDirection(code: directionCode) else {
|
||||
scriptErrorNumber = errAEParamMissed
|
||||
scriptErrorString = "Missing or unknown split direction."
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the window controller that owns this surface.
|
||||
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else {
|
||||
scriptErrorNumber = errAEEventFailed
|
||||
scriptErrorString = "Terminal is not in a splittable window."
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let newView = controller.newSplit(at: surfaceView, direction: direction.splitDirection) else {
|
||||
scriptErrorNumber = errAEEventFailed
|
||||
scriptErrorString = "Failed to create split."
|
||||
return nil
|
||||
}
|
||||
|
||||
return ScriptTerminal(surfaceView: newView)
|
||||
}
|
||||
}
|
||||
|
||||
/// Four-character codes matching the `split direction` enumeration in `Ghostty.sdef`.
|
||||
private enum ScriptSplitDirection {
|
||||
case right
|
||||
case left
|
||||
case down
|
||||
case up
|
||||
|
||||
init?(code: UInt32) {
|
||||
switch code {
|
||||
case "GSrt".fourCharCode: self = .right
|
||||
case "GSlf".fourCharCode: self = .left
|
||||
case "GSdn".fourCharCode: self = .down
|
||||
case "GSup".fourCharCode: self = .up
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var splitDirection: SplitTree<Ghostty.SurfaceView>.NewDirection {
|
||||
switch self {
|
||||
case .right: .right
|
||||
case .left: .left
|
||||
case .down: .down
|
||||
case .up: .up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ import AppKit
|
||||
@MainActor
|
||||
@objc(GhosttyScriptTerminal)
|
||||
final class ScriptTerminal: NSObject {
|
||||
private weak var surfaceView: Ghostty.SurfaceView?
|
||||
/// Weak reference to the underlying surface. Package-visible so that
|
||||
/// other AppleScript command handlers (e.g. `ScriptSplitCommand`) can
|
||||
/// access the live surface without exposing it to ObjC/AppleScript.
|
||||
weak var surfaceView: Ghostty.SurfaceView?
|
||||
|
||||
init(surfaceView: Ghostty.SurfaceView) {
|
||||
self.surfaceView = surfaceView
|
||||
@@ -27,4 +27,13 @@ extension String {
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Converts a four-character ASCII string to its `FourCharCode` (`UInt32`) value.
|
||||
var fourCharCode: UInt32 {
|
||||
assert(count <= 4, "FourCharCode string must be at most 4 characters")
|
||||
var result: UInt32 = 0
|
||||
for byte in utf8.prefix(4) {
|
||||
result = (result << 8) | UInt32(byte)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user