diff --git a/Sources/Shellraiser/App/ShellraiserApp.swift b/Sources/Shellraiser/App/ShellraiserApp.swift index f9a2b36..cf6713f 100644 --- a/Sources/Shellraiser/App/ShellraiserApp.swift +++ b/Sources/Shellraiser/App/ShellraiserApp.swift @@ -203,6 +203,20 @@ struct WorkspaceCommands: Commands { Divider() + Button("Previous Workspace") { + manager.selectPreviousWorkspace() + } + .keyboardShortcut(.upArrow, modifiers: [.command]) + .disabled(manager.workspaces.count <= 1) + + Button("Next Workspace") { + manager.selectNextWorkspace() + } + .keyboardShortcut(.downArrow, modifiers: [.command]) + .disabled(manager.workspaces.count <= 1) + + Divider() + Button("Jump to Next Completed Session") { manager.jumpToNextCompletedSession() } diff --git a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift index 7a62589..00d81f3 100644 --- a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift +++ b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift @@ -56,6 +56,17 @@ extension WorkspaceManager { } } + if hasCommand, !hasOption, !hasControl, !hasShift { + switch event.keyCode { + case 126: + return selectPreviousWorkspace() + case 125: + return selectNextWorkspace() + default: + break + } + } + guard hasCommand, !hasOption, !hasControl else { return false } guard let key = event.charactersIgnoringModifiers?.lowercased(), !key.isEmpty else { diff --git a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift index cc124b3..7f60c2a 100644 --- a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift +++ b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift @@ -162,6 +162,30 @@ extension WorkspaceManager { index > 0 && index <= workspaces.count } + /// Selects the workspace before the current one, wrapping to the last. + /// Returns true if a switch occurred. + @discardableResult + func selectPreviousWorkspace() -> Bool { + guard workspaces.count > 1, + let currentId = window.selectedWorkspaceId, + let currentIndex = workspaces.firstIndex(where: { $0.id == currentId }) else { return false } + let previousIndex = (currentIndex - 1 + workspaces.count) % workspaces.count + selectWorkspace(workspaces[previousIndex].id) + return true + } + + /// Selects the workspace after the current one, wrapping to the first. + /// Returns true if a switch occurred. + @discardableResult + func selectNextWorkspace() -> Bool { + guard workspaces.count > 1, + let currentId = window.selectedWorkspaceId, + let currentIndex = workspaces.firstIndex(where: { $0.id == currentId }) else { return false } + let nextIndex = (currentIndex + 1) % workspaces.count + selectWorkspace(workspaces[nextIndex].id) + return true + } + /// Restores first-responder focus to the selected workspace's active terminal surface. func restoreSelectedWorkspaceTerminalFocus() { guard let workspaceId = window.selectedWorkspaceId,