macos: NewTerminalIntent returns Terminal, can split
parent
683b38f62c
commit
bbb69c8f27
|
|
@ -1,5 +1,6 @@
|
|||
import AppKit
|
||||
import AppIntents
|
||||
import GhosttyKit
|
||||
|
||||
/// App intent that allows creating a new terminal window or tab.
|
||||
///
|
||||
|
|
@ -16,6 +17,12 @@ struct NewTerminalIntent: AppIntent {
|
|||
)
|
||||
var location: NewTerminalLocation
|
||||
|
||||
@Parameter(
|
||||
title: "Command",
|
||||
description: "Command to execute instead of the default shell."
|
||||
)
|
||||
var command: String?
|
||||
|
||||
@Parameter(
|
||||
title: "Working Directory",
|
||||
description: "The working directory to open in the terminal.",
|
||||
|
|
@ -36,12 +43,14 @@ struct NewTerminalIntent: AppIntent {
|
|||
static var openAppWhenRun = true
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<TerminalEntity?> {
|
||||
guard let appDelegate = NSApp.delegate as? AppDelegate else {
|
||||
throw GhosttyIntentError.appUnavailable
|
||||
}
|
||||
let ghostty = appDelegate.ghostty
|
||||
|
||||
var config = Ghostty.SurfaceConfiguration()
|
||||
config.command = command
|
||||
|
||||
// If we were given a working directory then open that directory
|
||||
if let url = workingDirectory?.fileURL {
|
||||
|
|
@ -65,19 +74,38 @@ struct NewTerminalIntent: AppIntent {
|
|||
|
||||
switch location {
|
||||
case .window:
|
||||
_ = TerminalController.newWindow(
|
||||
appDelegate.ghostty,
|
||||
let newController = TerminalController.newWindow(
|
||||
ghostty,
|
||||
withBaseConfig: config,
|
||||
withParent: parent?.window)
|
||||
if let view = newController.surfaceTree.root?.leftmostLeaf() {
|
||||
return .result(value: TerminalEntity(view))
|
||||
}
|
||||
|
||||
case .tab:
|
||||
_ = TerminalController.newTab(
|
||||
appDelegate.ghostty,
|
||||
let newController = TerminalController.newTab(
|
||||
ghostty,
|
||||
from: parent?.window,
|
||||
withBaseConfig: config)
|
||||
if let view = newController?.surfaceTree.root?.leftmostLeaf() {
|
||||
return .result(value: TerminalEntity(view))
|
||||
}
|
||||
|
||||
case .splitLeft, .splitRight, .splitUp, .splitDown:
|
||||
guard let parent,
|
||||
let controller = parent.window?.windowController as? BaseTerminalController else {
|
||||
throw GhosttyIntentError.surfaceNotFound
|
||||
}
|
||||
|
||||
if let view = controller.newSplit(
|
||||
at: parent,
|
||||
direction: location.splitDirection!
|
||||
) {
|
||||
return .result(value: TerminalEntity(view))
|
||||
}
|
||||
}
|
||||
|
||||
return .result()
|
||||
return .result(value: .none)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +114,20 @@ struct NewTerminalIntent: AppIntent {
|
|||
enum NewTerminalLocation: String {
|
||||
case tab
|
||||
case window
|
||||
case splitLeft = "split:left"
|
||||
case splitRight = "split:right"
|
||||
case splitUp = "split:up"
|
||||
case splitDown = "split:down"
|
||||
|
||||
var splitDirection: SplitTree<Ghostty.SurfaceView>.NewDirection? {
|
||||
switch self {
|
||||
case .splitLeft: return .left
|
||||
case .splitRight: return .right
|
||||
case .splitUp: return .up
|
||||
case .splitDown: return .down
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NewTerminalLocation: AppEnum {
|
||||
|
|
@ -94,5 +136,9 @@ extension NewTerminalLocation: AppEnum {
|
|||
static var caseDisplayRepresentations: [Self: DisplayRepresentation] = [
|
||||
.tab: .init(title: "Tab"),
|
||||
.window: .init(title: "Window"),
|
||||
.splitLeft: .init(title: "Split Left"),
|
||||
.splitRight: .init(title: "Split Right"),
|
||||
.splitUp: .init(title: "Split Up"),
|
||||
.splitDown: .init(title: "Split Down"),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,6 +193,46 @@ class BaseTerminalController: NSWindowController,
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Create a new split.
|
||||
@discardableResult
|
||||
func newSplit(
|
||||
at oldView: Ghostty.SurfaceView,
|
||||
direction: SplitTree<Ghostty.SurfaceView>.NewDirection,
|
||||
baseConfig config: Ghostty.SurfaceConfiguration? = nil
|
||||
) -> Ghostty.SurfaceView? {
|
||||
// We can only create new splits for surfaces in our tree.
|
||||
guard surfaceTree.root?.node(view: oldView) != nil else { return nil }
|
||||
|
||||
// Create a new surface view
|
||||
guard let ghostty_app = ghostty.app else { return nil }
|
||||
let newView = Ghostty.SurfaceView(ghostty_app, baseConfig: config)
|
||||
|
||||
// Do the split
|
||||
let newTree: SplitTree<Ghostty.SurfaceView>
|
||||
do {
|
||||
newTree = try surfaceTree.insert(
|
||||
view: newView,
|
||||
at: oldView,
|
||||
direction: direction)
|
||||
} catch {
|
||||
// If splitting fails for any reason (it should not), then we just log
|
||||
// and return. The new view we created will be deinitialized and its
|
||||
// no big deal.
|
||||
Ghostty.logger.warning("failed to insert split: \(error)")
|
||||
return nil
|
||||
}
|
||||
|
||||
replaceSurfaceTree(
|
||||
newTree,
|
||||
moveFocusTo: newView,
|
||||
moveFocusFrom: oldView,
|
||||
undoAction: "New Split")
|
||||
|
||||
return newView
|
||||
}
|
||||
|
||||
/// Called when the surfaceTree variable changed.
|
||||
///
|
||||
/// Subclasses should call super first.
|
||||
|
|
@ -477,30 +517,7 @@ class BaseTerminalController: NSWindowController,
|
|||
default: return
|
||||
}
|
||||
|
||||
// Create a new surface view
|
||||
guard let ghostty_app = ghostty.app else { return }
|
||||
let newView = Ghostty.SurfaceView(ghostty_app, baseConfig: config)
|
||||
|
||||
// Do the split
|
||||
let newTree: SplitTree<Ghostty.SurfaceView>
|
||||
do {
|
||||
newTree = try surfaceTree.insert(
|
||||
view: newView,
|
||||
at: oldView,
|
||||
direction: splitDirection)
|
||||
} catch {
|
||||
// If splitting fails for any reason (it should not), then we just log
|
||||
// and return. The new view we created will be deinitialized and its
|
||||
// no big deal.
|
||||
Ghostty.logger.warning("failed to insert split: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
replaceSurfaceTree(
|
||||
newTree,
|
||||
moveFocusTo: newView,
|
||||
moveFocusFrom: oldView,
|
||||
undoAction: "New Split")
|
||||
newSplit(at: oldView, direction: splitDirection, baseConfig: config)
|
||||
}
|
||||
|
||||
@objc private func ghosttyDidEqualizeSplits(_ notification: Notification) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue