Skip to content

Commit 05ee9ae

Browse files
committed
macos: implement goto_window:next/previousu
1 parent 1a117c4 commit 05ee9ae

File tree

1 file changed

+61
-0
lines changed

1 file changed

+61
-0
lines changed

macos/Sources/Ghostty/Ghostty.App.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,9 @@ extension Ghostty {
501501
case GHOSTTY_ACTION_GOTO_SPLIT:
502502
return gotoSplit(app, target: target, direction: action.action.goto_split)
503503

504+
case GHOSTTY_ACTION_GOTO_WINDOW:
505+
return gotoWindow(app, target: target, direction: action.action.goto_window)
506+
504507
case GHOSTTY_ACTION_RESIZE_SPLIT:
505508
resizeSplit(app, target: target, resize: action.action.resize_split)
506509

@@ -1149,6 +1152,64 @@ extension Ghostty {
11491152
}
11501153
}
11511154

1155+
private static func gotoWindow(
1156+
_ app: ghostty_app_t,
1157+
target: ghostty_target_s,
1158+
direction: ghostty_action_goto_window_e
1159+
) -> Bool {
1160+
// Collect candidate windows: visible terminal windows that are either
1161+
// standalone or the currently selected tab in their tab group. This
1162+
// treats each native tab group as a single "window" for navigation
1163+
// purposes, since goto_tab handles per-tab navigation.
1164+
let candidates: [NSWindow] = NSApplication.shared.windows.filter { window in
1165+
guard window.windowController is BaseTerminalController else { return false }
1166+
guard window.isVisible, !window.isMiniaturized else { return false }
1167+
// For native tabs, only include the selected tab in each group
1168+
if let group = window.tabGroup, group.selectedWindow !== window {
1169+
return false
1170+
}
1171+
return true
1172+
}
1173+
1174+
// Need at least two windows to navigate between
1175+
guard candidates.count > 1 else { return false }
1176+
1177+
// Find starting index from the current key/main window
1178+
let startIndex = candidates.firstIndex(where: { $0.isKeyWindow })
1179+
?? candidates.firstIndex(where: { $0.isMainWindow })
1180+
?? 0
1181+
1182+
let step: Int
1183+
switch direction {
1184+
case GHOSTTY_GOTO_WINDOW_NEXT:
1185+
step = 1
1186+
case GHOSTTY_GOTO_WINDOW_PREVIOUS:
1187+
step = -1
1188+
default:
1189+
return false
1190+
}
1191+
1192+
// Iterate with wrap-around until we find a valid window or return to start
1193+
let count = candidates.count
1194+
var index = (startIndex + step + count) % count
1195+
1196+
while index != startIndex {
1197+
let candidate = candidates[index]
1198+
if candidate.isVisible, !candidate.isMiniaturized {
1199+
candidate.makeKeyAndOrderFront(nil)
1200+
// Also focus the terminal surface within the window
1201+
if let controller = candidate.windowController as? BaseTerminalController,
1202+
let surface = controller.focusedSurface {
1203+
Ghostty.moveFocus(to: surface)
1204+
}
1205+
return true
1206+
}
1207+
index = (index + step + count) % count
1208+
}
1209+
1210+
return false
1211+
}
1212+
11521213
private static func resizeSplit(
11531214
_ app: ghostty_app_t,
11541215
target: ghostty_target_s,

0 commit comments

Comments
 (0)