From bdd4c672fc8cf6b9227a4b38a387bf06beaf905b Mon Sep 17 00:00:00 2001 From: Morten Trydal Date: Wed, 1 Apr 2026 09:04:08 +0200 Subject: [PATCH 1/2] Add Cmd+Up/Down workspace navigation Navigate to the previous/next workspace using Cmd+Up and Cmd+Down arrows, wrapping around at the edges. Shortcuts are also exposed in the Workspace menu for discoverability. --- Sources/Shellraiser/App/ShellraiserApp.swift | 14 ++++++++++++++ .../WorkspaceManager+Shortcuts.swift | 13 +++++++++++++ .../WorkspaceManager+WorkspaceLifecycle.swift | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+) 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..6901de5 100644 --- a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift +++ b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift @@ -56,6 +56,19 @@ extension WorkspaceManager { } } + if hasCommand, !hasOption, !hasControl, !hasShift { + switch event.keyCode { + case 126: + selectPreviousWorkspace() + return true + case 125: + selectNextWorkspace() + return true + 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..0c92d91 100644 --- a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift +++ b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift @@ -162,6 +162,24 @@ extension WorkspaceManager { index > 0 && index <= workspaces.count } + /// Selects the workspace before the current one, wrapping to the last. + func selectPreviousWorkspace() { + guard workspaces.count > 1, + let currentId = window.selectedWorkspaceId, + let currentIndex = workspaces.firstIndex(where: { $0.id == currentId }) else { return } + let previousIndex = (currentIndex - 1 + workspaces.count) % workspaces.count + selectWorkspace(workspaces[previousIndex].id) + } + + /// Selects the workspace after the current one, wrapping to the first. + func selectNextWorkspace() { + guard workspaces.count > 1, + let currentId = window.selectedWorkspaceId, + let currentIndex = workspaces.firstIndex(where: { $0.id == currentId }) else { return } + let nextIndex = (currentIndex + 1) % workspaces.count + selectWorkspace(workspaces[nextIndex].id) + } + /// Restores first-responder focus to the selected workspace's active terminal surface. func restoreSelectedWorkspaceTerminalFocus() { guard let workspaceId = window.selectedWorkspaceId, From b03a42b9a4c447ebca0c5137d472601d151c6eb5 Mon Sep 17 00:00:00 2001 From: Morten Trydal Date: Wed, 1 Apr 2026 09:10:58 +0200 Subject: [PATCH 2/2] Don't swallow Cmd+Up/Down when no workspace switch occurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return Bool from selectPreviousWorkspace/selectNextWorkspace so the shortcut handler only consumes the event when a switch actually happened — e.g. with a single workspace the key is now passed through to the terminal. --- .../Workspaces/WorkspaceManager+Shortcuts.swift | 6 ++---- .../WorkspaceManager+WorkspaceLifecycle.swift | 14 ++++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift index 6901de5..00d81f3 100644 --- a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift +++ b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+Shortcuts.swift @@ -59,11 +59,9 @@ extension WorkspaceManager { if hasCommand, !hasOption, !hasControl, !hasShift { switch event.keyCode { case 126: - selectPreviousWorkspace() - return true + return selectPreviousWorkspace() case 125: - selectNextWorkspace() - return true + return selectNextWorkspace() default: break } diff --git a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift index 0c92d91..7f60c2a 100644 --- a/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift +++ b/Sources/Shellraiser/Services/Workspaces/WorkspaceManager+WorkspaceLifecycle.swift @@ -163,21 +163,27 @@ extension WorkspaceManager { } /// Selects the workspace before the current one, wrapping to the last. - func selectPreviousWorkspace() { + /// 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 } + 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. - func selectNextWorkspace() { + /// 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 } + 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.