import AppKit /// AppleScript-facing wrapper around a live Ghostty terminal surface. /// /// This class is intentionally ObjC-visible because Cocoa scripting resolves /// AppleScript objects through Objective-C runtime names/selectors, not Swift /// protocol conformance. /// /// Mapping from `Ghostty.sdef`: /// - `class terminal` -> this class (`@objc(GhosttyAppleScriptTerminal)`). /// - `property id` -> `@objc(id)` getter below. /// - `property title` -> `@objc(title)` getter below. /// - `property working directory` -> `@objc(workingDirectory)` getter below. /// /// We keep only a weak reference to the underlying `SurfaceView` so this /// wrapper never extends the terminal's lifetime. @MainActor @objc(GhosttyScriptTerminal) final class ScriptTerminal: NSObject { /// 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 } /// Exposed as the AppleScript `id` property. /// /// This is a stable UUID string for the life of a surface and is also used /// by `NSUniqueIDSpecifier` to re-identify a terminal object in scripts. @objc(id) var stableID: String { guard NSApp.isAppleScriptEnabled else { return "" } return surfaceView?.id.uuidString ?? "" } /// Exposed as the AppleScript `title` property. @objc(title) var title: String { guard NSApp.isAppleScriptEnabled else { return "" } return surfaceView?.title ?? "" } /// Exposed as the AppleScript `working directory` property. /// /// The `sdef` uses a spaced name, but Cocoa scripting maps that to the /// camel-cased selector name `workingDirectory`. @objc(workingDirectory) var workingDirectory: String { guard NSApp.isAppleScriptEnabled else { return "" } return surfaceView?.pwd ?? "" } /// Used by command handling (`perform action ... on `). func perform(action: String) -> Bool { guard NSApp.isAppleScriptEnabled else { return false } guard let surfaceModel = surfaceView?.surfaceModel else { return false } return surfaceModel.perform(action: action) } /// Provides Cocoa scripting with a canonical "path" back to this object. /// /// Without an object specifier, returned terminal objects can't be reliably /// referenced in follow-up script statements because AppleScript cannot /// express where the object came from (`application.terminals[id]`). override var objectSpecifier: NSScriptObjectSpecifier? { guard NSApp.isAppleScriptEnabled else { return nil } guard let appClassDescription = NSApplication.shared.classDescription as? NSScriptClassDescription else { return nil } return NSUniqueIDSpecifier( containerClassDescription: appClassDescription, containerSpecifier: nil, key: "terminals", uniqueID: stableID ) } }