mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 01:06:08 +03:00
macos: make goto_split, goto_tab, and move_tab performable (#5740)
Fixes #5552 This makes the mentioned actions performable. This isn't perfect, but it does so in a way that resolves the user issue in #5552. This commit returns an action is NOT performed if it doesn't have splits or tabs (respectiely for the actions), but also reports its ALWAYS performed if it does. This latter logic isn't accurate: we should only return performable if it was actually done. So for example, goto_split:top should do nothing if we're already at the top. But, we report it as performed today. This is good enough to resolve the issue and fix the core problem faced for 1.1.0.
This commit is contained in:
@ -455,13 +455,13 @@ extension Ghostty {
|
||||
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
|
||||
|
||||
case GHOSTTY_ACTION_MOVE_TAB:
|
||||
moveTab(app, target: target, move: action.action.move_tab)
|
||||
return moveTab(app, target: target, move: action.action.move_tab)
|
||||
|
||||
case GHOSTTY_ACTION_GOTO_TAB:
|
||||
gotoTab(app, target: target, tab: action.action.goto_tab)
|
||||
return gotoTab(app, target: target, tab: action.action.goto_tab)
|
||||
|
||||
case GHOSTTY_ACTION_GOTO_SPLIT:
|
||||
gotoSplit(app, target: target, direction: action.action.goto_split)
|
||||
return gotoSplit(app, target: target, direction: action.action.goto_split)
|
||||
|
||||
case GHOSTTY_ACTION_RESIZE_SPLIT:
|
||||
resizeSplit(app, target: target, resize: action.action.resize_split)
|
||||
@ -721,15 +721,19 @@ extension Ghostty {
|
||||
private static func moveTab(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
move: ghostty_action_move_tab_s) {
|
||||
move: ghostty_action_move_tab_s) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("move tab does nothing with an app target")
|
||||
return
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
|
||||
// See gotoTab for notes on this check.
|
||||
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyMoveTab,
|
||||
object: surfaceView,
|
||||
@ -741,20 +745,27 @@ extension Ghostty {
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func gotoTab(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
tab: ghostty_action_goto_tab_e) {
|
||||
tab: ghostty_action_goto_tab_e) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("goto tab does nothing with an app target")
|
||||
return
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
|
||||
// Similar to goto_split (see comment there) about our performability,
|
||||
// we should make this more accurate later.
|
||||
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.ghosttyGotoTab,
|
||||
object: surfaceView,
|
||||
@ -766,20 +777,31 @@ extension Ghostty {
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func gotoSplit(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
direction: ghostty_action_goto_split_e) {
|
||||
direction: ghostty_action_goto_split_e) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("goto split does nothing with an app target")
|
||||
return
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else { return false }
|
||||
|
||||
// For now, we return false if the window has no splits and we return
|
||||
// true if the window has ANY splits. This isn't strictly correct because
|
||||
// we should only be returning true if we actually performed the action,
|
||||
// but this handles the most common case of caring about goto_split performability
|
||||
// which is the no-split case.
|
||||
guard controller.surfaceTree?.isSplit ?? false else { return false }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.ghosttyFocusSplit,
|
||||
object: surfaceView,
|
||||
@ -791,6 +813,8 @@ extension Ghostty {
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func resizeSplit(
|
||||
|
@ -38,6 +38,15 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the tree is split.
|
||||
var isSplit: Bool {
|
||||
return if case .leaf = self {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
func topLeft() -> SurfaceView {
|
||||
switch (self) {
|
||||
case .leaf(let leaf):
|
||||
@ -120,14 +129,7 @@ extension Ghostty {
|
||||
|
||||
/// Returns true if the split tree contains the given view.
|
||||
func contains(view: SurfaceView) -> Bool {
|
||||
switch (self) {
|
||||
case .leaf(let leaf):
|
||||
return leaf.surface == view
|
||||
|
||||
case .split(let container):
|
||||
return container.topLeft.contains(view: view) ||
|
||||
container.bottomRight.contains(view: view)
|
||||
}
|
||||
return leaf(for: view) != nil
|
||||
}
|
||||
|
||||
/// Find a surface view by UUID.
|
||||
@ -164,6 +166,22 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the node for the given view if its in the tree.
|
||||
func leaf(for view: SurfaceView) -> Leaf? {
|
||||
switch (self) {
|
||||
case .leaf(let leaf):
|
||||
if leaf.surface == view {
|
||||
return leaf
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
case .split(let container):
|
||||
return container.topLeft.leaf(for: view) ??
|
||||
container.bottomRight.leaf(for: view)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sequence
|
||||
|
||||
func makeIterator() -> IndexingIterator<[Leaf]> {
|
||||
|
Reference in New Issue
Block a user