macos: implement goto_window:next/previous (#9899)

pull/9900/head
Mitchell Hashimoto 2025-12-13 14:40:40 -08:00 committed by GitHub
commit 2cc7341b08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 61 additions and 0 deletions

View File

@ -501,6 +501,9 @@ extension Ghostty {
case GHOSTTY_ACTION_GOTO_SPLIT:
return gotoSplit(app, target: target, direction: action.action.goto_split)
case GHOSTTY_ACTION_GOTO_WINDOW:
return gotoWindow(app, target: target, direction: action.action.goto_window)
case GHOSTTY_ACTION_RESIZE_SPLIT:
resizeSplit(app, target: target, resize: action.action.resize_split)
@ -1149,6 +1152,64 @@ extension Ghostty {
}
}
private static func gotoWindow(
_ app: ghostty_app_t,
target: ghostty_target_s,
direction: ghostty_action_goto_window_e
) -> Bool {
// Collect candidate windows: visible terminal windows that are either
// standalone or the currently selected tab in their tab group. This
// treats each native tab group as a single "window" for navigation
// purposes, since goto_tab handles per-tab navigation.
let candidates: [NSWindow] = NSApplication.shared.windows.filter { window in
guard window.windowController is BaseTerminalController else { return false }
guard window.isVisible, !window.isMiniaturized else { return false }
// For native tabs, only include the selected tab in each group
if let group = window.tabGroup, group.selectedWindow !== window {
return false
}
return true
}
// Need at least two windows to navigate between
guard candidates.count > 1 else { return false }
// Find starting index from the current key/main window
let startIndex = candidates.firstIndex(where: { $0.isKeyWindow })
?? candidates.firstIndex(where: { $0.isMainWindow })
?? 0
let step: Int
switch direction {
case GHOSTTY_GOTO_WINDOW_NEXT:
step = 1
case GHOSTTY_GOTO_WINDOW_PREVIOUS:
step = -1
default:
return false
}
// Iterate with wrap-around until we find a valid window or return to start
let count = candidates.count
var index = (startIndex + step + count) % count
while index != startIndex {
let candidate = candidates[index]
if candidate.isVisible, !candidate.isMiniaturized {
candidate.makeKeyAndOrderFront(nil)
// Also focus the terminal surface within the window
if let controller = candidate.windowController as? BaseTerminalController,
let surface = controller.focusedSurface {
Ghostty.moveFocus(to: surface)
}
return true
}
index = (index + step + count) % count
}
return false
}
private static func resizeSplit(
_ app: ghostty_app_t,
target: ghostty_target_s,