From 62fffc72218f2bf9796d36974e09f81f285082f1 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:12:49 -0800 Subject: [PATCH] Use command palette flow for workspace rename shortcut --- Sources/AppDelegate.swift | 12 +++- Sources/ContentView.swift | 18 ++++++ Sources/TabManager.swift | 1 + Sources/cmuxApp.swift | 2 +- .../AppDelegateShortcutRoutingTests.swift | 59 +++++++++++++++++++ 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 70d3e4af..a771d24d 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -4560,8 +4560,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .renameWorkspace)) { - _ = promptRenameSelectedWorkspace() - return true + return requestRenameWorkspaceViaCommandPalette( + preferredWindow: commandPaletteTargetWindow ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow + ) } if normalizedFlags == [.command, .option], (chars == "t" || event.keyCode == 17) { @@ -5248,6 +5249,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent handleCustomShortcut(event: event) } + @discardableResult + func requestRenameWorkspaceViaCommandPalette(preferredWindow: NSWindow? = nil) -> Bool { + let targetWindow = preferredWindow ?? NSApp.keyWindow ?? NSApp.mainWindow + NotificationCenter.default.post(name: .commandPaletteRenameWorkspaceRequested, object: targetWindow) + return true + } + #if DEBUG // Debug/test hook: allow socket-driven shortcut simulation to reuse the same shortcut routing // logic as the local NSEvent monitor, without relying on AppKit event monitor behavior for diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 57f19f42..9ccb8895 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -2213,6 +2213,17 @@ struct ContentView: View { openCommandPaletteRenameTabInput() }) + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .commandPaletteRenameWorkspaceRequested)) { notification in + let requestedWindow = notification.object as? NSWindow + guard Self.shouldHandleCommandPaletteRequest( + observedWindow: observedWindow, + requestedWindow: requestedWindow, + keyWindow: NSApp.keyWindow, + mainWindow: NSApp.mainWindow + ) else { return } + openCommandPaletteRenameWorkspaceInput() + }) + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .commandPaletteMoveSelection)) { notification in guard isCommandPalettePresented else { return } guard case .commands = commandPaletteMode else { return } @@ -4327,6 +4338,13 @@ struct ContentView: View { beginRenameTabFlow() } + private func openCommandPaletteRenameWorkspaceInput() { + if !isCommandPalettePresented { + presentCommandPalette(initialQuery: Self.commandPaletteCommandsPrefix) + } + beginRenameWorkspaceFlow() + } + static func shouldHandleCommandPaletteRequest( observedWindow: NSWindow?, requestedWindow: NSWindow?, diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index e3d7efaa..24960559 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -3414,6 +3414,7 @@ extension Notification.Name { static let commandPaletteRequested = Notification.Name("cmux.commandPaletteRequested") static let commandPaletteSwitcherRequested = Notification.Name("cmux.commandPaletteSwitcherRequested") static let commandPaletteRenameTabRequested = Notification.Name("cmux.commandPaletteRenameTabRequested") + static let commandPaletteRenameWorkspaceRequested = Notification.Name("cmux.commandPaletteRenameWorkspaceRequested") static let commandPaletteMoveSelection = Notification.Name("cmux.commandPaletteMoveSelection") static let commandPaletteRenameInputInteractionRequested = Notification.Name("cmux.commandPaletteRenameInputInteractionRequested") static let commandPaletteRenameInputDeleteBackwardRequested = Notification.Name("cmux.commandPaletteRenameInputDeleteBackwardRequested") diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 9a151237..dc3de77d 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -538,7 +538,7 @@ struct cmuxApp: App { } splitCommandButton(title: "Rename Workspace…", shortcut: renameWorkspaceMenuShortcut) { - _ = AppDelegate.shared?.promptRenameSelectedWorkspace() + _ = AppDelegate.shared?.requestRenameWorkspaceViaCommandPalette() } Divider() diff --git a/cmuxTests/AppDelegateShortcutRoutingTests.swift b/cmuxTests/AppDelegateShortcutRoutingTests.swift index c5a9435a..eaf8fb61 100644 --- a/cmuxTests/AppDelegateShortcutRoutingTests.swift +++ b/cmuxTests/AppDelegateShortcutRoutingTests.swift @@ -311,6 +311,65 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { XCTAssertTrue(appDelegate.tabManager === secondManager, "Shortcut routing should retarget active manager to event window") } + func testCmdShiftRRequestsRenameWorkspaceInCommandPalette() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let workspaceExpectation = expectation(description: "Expected command palette rename workspace notification") + var observedWorkspaceWindow: NSWindow? + let workspaceToken = NotificationCenter.default.addObserver( + forName: .commandPaletteRenameWorkspaceRequested, + object: nil, + queue: nil + ) { notification in + observedWorkspaceWindow = notification.object as? NSWindow + workspaceExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(workspaceToken) } + + let renameTabExpectation = expectation(description: "Rename tab notification should not fire for Cmd+Shift+R") + renameTabExpectation.isInverted = true + let renameTabToken = NotificationCenter.default.addObserver( + forName: .commandPaletteRenameTabRequested, + object: nil, + queue: nil + ) { _ in + renameTabExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(renameTabToken) } + + guard let event = makeKeyDownEvent( + key: "r", + modifiers: [.command, .shift], + keyCode: 15, // kVK_ANSI_R + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Cmd+Shift+R event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [workspaceExpectation, renameTabExpectation], timeout: 1.0) + XCTAssertEqual(observedWorkspaceWindow?.windowNumber, window.windowNumber) + } + func testCmdDigitDoesNotFallbackToOtherWindowWhenEventWindowContextIsMissing() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared")