diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index ec8cde84..be46bab0 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -57395,13 +57395,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "Show Cmd/Ctrl-Hold Shortcut Hints" + "value": "Show Shortcut Hints While Holding Modifiers" } }, "ja": { "stringUnit": { "state": "translated", - "value": "Cmd/Ctrl長押しのショートカットヒントを表示" + "value": "修飾キー長押しでショートカットヒントを表示" } }, "zh-Hans": { @@ -57508,13 +57508,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "Holding Cmd or Ctrl keeps shortcut hint pills hidden." + "value": "Holding shortcut modifiers keeps shortcut hint pills hidden." } }, "ja": { "stringUnit": { "state": "translated", - "value": "CmdまたはCtrlを長押ししてもショートカットヒントピルは非表示のままです。" + "value": "ショートカットの修飾キーを長押ししてもショートカットヒントピルは非表示のままです。" } }, "zh-Hans": { @@ -57621,13 +57621,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills." + "value": "Holding the configured shortcut modifiers shows shortcut hint pills." } }, "ja": { "stringUnit": { "state": "translated", - "value": "Cmd(サイドバー/タイトルバー)またはCtrl/Cmd(ペインタブ)を長押しするとショートカットヒントピルが表示されます。" + "value": "設定されたショートカットの修飾キーを長押しするとショートカットヒントピルが表示されます。" } }, "zh-Hans": { @@ -61457,6 +61457,40 @@ } } }, + "shortcut.selectSurfaceByNumber.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select Surface 1…9" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サーフェス 1…9 を選択" + } + } + } + }, + "shortcut.selectWorkspaceByNumber.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select Workspace 1…9" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース 1…9 を選択" + } + } + } + }, "shortcut.showBrowserJSConsole.label": { "extractionState": "manual", "localizations": { diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index d7da8180..1e103d3d 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1114,9 +1114,9 @@ final class ServeWebOutputCollector { } enum WorkspaceShortcutMapper { - /// Maps Cmd+digit workspace shortcuts to a zero-based workspace index. - /// Cmd+1...Cmd+8 target fixed indices; Cmd+9 always targets the last workspace. - static func workspaceIndex(forCommandDigit digit: Int, workspaceCount: Int) -> Int? { + /// Maps numbered workspace shortcuts to a zero-based workspace index. + /// 1...8 target fixed indices; 9 always targets the last workspace. + static func workspaceIndex(forDigit digit: Int, workspaceCount: Int) -> Int? { guard workspaceCount > 0 else { return nil } guard (1...9).contains(digit) else { return nil } @@ -1128,12 +1128,12 @@ enum WorkspaceShortcutMapper { return index < workspaceCount ? index : nil } - /// Returns the primary Cmd+digit badge to display for a workspace row. + /// Returns the primary digit badge to display for a workspace row. /// Picks the lowest digit that maps to that row index. - static func commandDigitForWorkspace(at index: Int, workspaceCount: Int) -> Int? { + static func digitForWorkspace(at index: Int, workspaceCount: Int) -> Int? { guard index >= 0 && index < workspaceCount else { return nil } for digit in 1...9 { - if workspaceIndex(forCommandDigit: digit, workspaceCount: workspaceCount) == index { + if workspaceIndex(forDigit: digit, workspaceCount: workspaceCount) == index { return digit } } @@ -9474,30 +9474,33 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } - // Numeric shortcuts for specific sidebar tabs: Cmd+1-9 (9 = last workspace) - if flags == [.command], - let manager = tabManager, - let num = Int(chars), - let targetIndex = WorkspaceShortcutMapper.workspaceIndex(forCommandDigit: num, workspaceCount: manager.tabs.count) { + // Numeric shortcuts for specific workspaces (9 = last workspace) + if let manager = tabManager, + let digit = numberedShortcutDigit( + event: event, + shortcut: KeyboardShortcutSettings.shortcut(for: .selectWorkspaceByNumber) + ), + let targetIndex = WorkspaceShortcutMapper.workspaceIndex(forDigit: digit, workspaceCount: manager.tabs.count) { #if DEBUG dlog( - "shortcut.action name=workspaceDigit digit=\(num) targetIndex=\(targetIndex) manager=\(debugManagerToken(manager)) \(debugShortcutRouteSnapshot(event: event))" + "shortcut.action name=workspaceDigit digit=\(digit) targetIndex=\(targetIndex) manager=\(debugManagerToken(manager)) \(debugShortcutRouteSnapshot(event: event))" ) #endif manager.selectTab(at: targetIndex) return true } - // Numeric shortcuts for surfaces within pane: Ctrl+1-9 (9 = last) - if flags == [.control] { - if let num = Int(chars), num >= 1 && num <= 9 { - if num == 9 { - tabManager?.selectLastSurface() - } else { - tabManager?.selectSurface(at: num - 1) - } - return true + // Numeric shortcuts for surfaces within the focused pane (9 = last) + if let digit = numberedShortcutDigit( + event: event, + shortcut: KeyboardShortcutSettings.shortcut(for: .selectSurfaceByNumber) + ) { + if digit == 9 { + tabManager?.selectLastSurface() + } else { + tabManager?.selectSurface(at: digit - 1) } + return true } // Pane focus navigation (defaults to Cmd+Option+Arrow, but can be customized to letter/number keys). @@ -10524,6 +10527,46 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return false } + private func numberedShortcutDigit(event: NSEvent, shortcut: StoredShortcut) -> Int? { + let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + .subtracting([.numericPad, .function, .capsLock]) + guard flags == shortcut.modifierFlags else { return nil } + + if let digit = numberedShortcutDigit( + eventCharacter: event.charactersIgnoringModifiers, + applyShiftSymbolNormalization: flags.contains(.shift), + eventKeyCode: event.keyCode + ) { + return digit + } + + let layoutCharacter = shortcutLayoutCharacterProvider(event.keyCode, event.modifierFlags) + if let digit = numberedShortcutDigit( + eventCharacter: layoutCharacter, + applyShiftSymbolNormalization: false, + eventKeyCode: event.keyCode + ) { + return digit + } + + return digitForNumberKeyCode(event.keyCode) + } + + private func numberedShortcutDigit( + eventCharacter: String?, + applyShiftSymbolNormalization: Bool, + eventKeyCode: UInt16 + ) -> Int? { + guard let eventCharacter, !eventCharacter.isEmpty else { return nil } + let normalized = normalizedShortcutEventCharacter( + eventCharacter, + applyShiftSymbolNormalization: applyShiftSymbolNormalization, + eventKeyCode: eventKeyCode + ) + guard let digit = Int(normalized), (1...9).contains(digit) else { return nil } + return digit + } + private func shouldRequireCharacterMatchForCommandShortcut(shortcutKey: String) -> Bool { guard shortcutKey.count == 1, let scalar = shortcutKey.unicodeScalars.first else { return false @@ -10643,6 +10686,22 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + private func digitForNumberKeyCode(_ keyCode: UInt16) -> Int? { + switch keyCode { + case 18: return 1 // kVK_ANSI_1 + case 19: return 2 // kVK_ANSI_2 + case 20: return 3 // kVK_ANSI_3 + case 21: return 4 // kVK_ANSI_4 + case 23: return 5 // kVK_ANSI_5 + case 22: return 6 // kVK_ANSI_6 + case 26: return 7 // kVK_ANSI_7 + case 28: return 8 // kVK_ANSI_8 + case 25: return 9 // kVK_ANSI_9 + default: + return nil + } + } + /// Match arrow key shortcuts using keyCode /// Arrow keys include .numericPad and .function in their modifierFlags, so strip those before comparing. private func matchArrowShortcut(event: NSEvent, shortcut: StoredShortcut, keyCode: UInt16) -> Bool { diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 6a4963dc..2cc83ee2 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -8187,6 +8187,8 @@ struct VerticalTabsSidebar: View { private var sidebarShowNotificationMessage = SidebarWorkspaceDetailSettings.defaultShowNotificationMessage @AppStorage(WorkspacePresentationModeSettings.modeKey) private var workspacePresentationMode = WorkspacePresentationModeSettings.defaultMode.rawValue + @AppStorage(KeyboardShortcutSettings.Action.selectWorkspaceByNumber.defaultsKey) + private var selectWorkspaceByNumberShortcutData = Data() /// Space at top of sidebar for traffic light buttons private let trafficLightPadding: CGFloat = 28 @@ -8204,9 +8206,25 @@ struct VerticalTabsSidebar: View { ) } + private var workspaceNumberShortcut: StoredShortcut { + decodeShortcut( + from: selectWorkspaceByNumberShortcutData, + fallback: KeyboardShortcutSettings.Action.selectWorkspaceByNumber.defaultShortcut + ) + } + + private func decodeShortcut(from data: Data, fallback: StoredShortcut) -> StoredShortcut { + guard !data.isEmpty, + let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else { + return fallback + } + return shortcut + } + var body: some View { let workspaceCount = tabManager.tabs.count let canCloseWorkspace = workspaceCount > 1 + let workspaceNumberShortcut = self.workspaceNumberShortcut VStack(spacing: 0) { GeometryReader { proxy in @@ -8231,10 +8249,11 @@ struct VerticalTabsSidebar: View { tab: tab, index: index, isActive: tabManager.selectedTabId == tab.id, - workspaceShortcutDigit: WorkspaceShortcutMapper.commandDigitForWorkspace( + workspaceShortcutDigit: WorkspaceShortcutMapper.digitForWorkspace( at: index, workspaceCount: workspaceCount ), + workspaceShortcutModifierSymbol: workspaceNumberShortcut.modifierDisplayString, canCloseWorkspace: canCloseWorkspace, accessibilityWorkspaceCount: workspaceCount, unreadCount: notificationStore.unreadCount(forTabId: tab.id), @@ -8378,7 +8397,8 @@ enum ShortcutHintModifierPolicy { defaults: UserDefaults = .standard ) -> Bool { let normalized = modifierFlags.intersection(.deviceIndependentFlagsMask) - guard normalized == [.command] else { + .subtracting([.numericPad, .function, .capsLock]) + guard normalized == KeyboardShortcutSettings.shortcut(for: .selectWorkspaceByNumber).modifierFlags else { return false } return ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults) @@ -10637,6 +10657,7 @@ private struct TabItemView: View, Equatable { lhs.index == rhs.index && lhs.isActive == rhs.isActive && lhs.workspaceShortcutDigit == rhs.workspaceShortcutDigit && + lhs.workspaceShortcutModifierSymbol == rhs.workspaceShortcutModifierSymbol && lhs.canCloseWorkspace == rhs.canCloseWorkspace && lhs.accessibilityWorkspaceCount == rhs.accessibilityWorkspaceCount && lhs.unreadCount == rhs.unreadCount && @@ -10658,6 +10679,7 @@ private struct TabItemView: View, Equatable { let index: Int let isActive: Bool let workspaceShortcutDigit: Int? + let workspaceShortcutModifierSymbol: String let canCloseWorkspace: Bool let accessibilityWorkspaceCount: Int let unreadCount: Int @@ -10772,7 +10794,7 @@ private struct TabItemView: View, Equatable { private var workspaceShortcutLabel: String? { guard let workspaceShortcutDigit else { return nil } - return "⌘\(workspaceShortcutDigit)" + return "\(workspaceShortcutModifierSymbol)\(workspaceShortcutDigit)" } private var showsWorkspaceShortcutHint: Bool { diff --git a/Sources/KeyboardShortcutSettings.swift b/Sources/KeyboardShortcutSettings.swift index 26b41ea3..b044c5c7 100644 --- a/Sources/KeyboardShortcutSettings.swift +++ b/Sources/KeyboardShortcutSettings.swift @@ -21,8 +21,10 @@ enum KeyboardShortcutSettings { // Navigation case nextSurface case prevSurface + case selectSurfaceByNumber case nextSidebarTab case prevSidebarTab + case selectWorkspaceByNumber case renameTab case renameWorkspace case closeWorkspace @@ -60,8 +62,10 @@ enum KeyboardShortcutSettings { case .triggerFlash: return String(localized: "shortcut.flashFocusedPanel.label", defaultValue: "Flash Focused Panel") case .nextSurface: return String(localized: "shortcut.nextSurface.label", defaultValue: "Next Surface") case .prevSurface: return String(localized: "shortcut.previousSurface.label", defaultValue: "Previous Surface") + case .selectSurfaceByNumber: return String(localized: "shortcut.selectSurfaceByNumber.label", defaultValue: "Select Surface 1…9") case .nextSidebarTab: return String(localized: "shortcut.nextWorkspace.label", defaultValue: "Next Workspace") case .prevSidebarTab: return String(localized: "shortcut.previousWorkspace.label", defaultValue: "Previous Workspace") + case .selectWorkspaceByNumber: return String(localized: "shortcut.selectWorkspaceByNumber.label", defaultValue: "Select Workspace 1…9") case .renameTab: return String(localized: "shortcut.renameTab.label", defaultValue: "Rename Tab") case .renameWorkspace: return String(localized: "shortcut.renameWorkspace.label", defaultValue: "Rename Workspace") case .closeWorkspace: return String(localized: "shortcut.closeWorkspace.label", defaultValue: "Close Workspace") @@ -93,6 +97,7 @@ enum KeyboardShortcutSettings { case .showNotifications: return "shortcut.showNotifications" case .jumpToUnread: return "shortcut.jumpToUnread" case .triggerFlash: return "shortcut.triggerFlash" + case .selectWorkspaceByNumber: return "shortcut.selectWorkspaceByNumber" case .nextSidebarTab: return "shortcut.nextSidebarTab" case .prevSidebarTab: return "shortcut.prevSidebarTab" case .renameTab: return "shortcut.renameTab" @@ -109,6 +114,7 @@ enum KeyboardShortcutSettings { case .splitBrowserDown: return "shortcut.splitBrowserDown" case .nextSurface: return "shortcut.nextSurface" case .prevSurface: return "shortcut.prevSurface" + case .selectSurfaceByNumber: return "shortcut.selectSurfaceByNumber" case .newSurface: return "shortcut.newSurface" case .toggleTerminalCopyMode: return "shortcut.toggleTerminalCopyMode" case .openBrowser: return "shortcut.openBrowser" @@ -169,10 +175,14 @@ enum KeyboardShortcutSettings { return StoredShortcut(key: "]", command: true, shift: true, option: false, control: false) case .prevSurface: return StoredShortcut(key: "[", command: true, shift: true, option: false, control: false) + case .selectSurfaceByNumber: + return StoredShortcut(key: "1", command: false, shift: false, option: false, control: true) case .newSurface: return StoredShortcut(key: "t", command: true, shift: false, option: false, control: false) case .toggleTerminalCopyMode: return StoredShortcut(key: "m", command: true, shift: true, option: false, control: false) + case .selectWorkspaceByNumber: + return StoredShortcut(key: "1", command: true, shift: false, option: false, control: false) case .openBrowser: return StoredShortcut(key: "l", command: true, shift: true, option: false, control: false) case .toggleBrowserDeveloperTools: @@ -185,7 +195,37 @@ enum KeyboardShortcutSettings { } func tooltip(_ base: String) -> String { - "\(base) (\(KeyboardShortcutSettings.shortcut(for: self).displayString))" + "\(base) (\(displayedShortcutString(for: KeyboardShortcutSettings.shortcut(for: self))))" + } + + var usesNumberedDigitMatching: Bool { + switch self { + case .selectSurfaceByNumber, .selectWorkspaceByNumber: + return true + default: + return false + } + } + + func displayedShortcutString(for shortcut: StoredShortcut) -> String { + if usesNumberedDigitMatching { + return shortcut.modifierDisplayString + "1…9" + } + return shortcut.displayString + } + + func normalizedRecordedShortcut(_ shortcut: StoredShortcut) -> StoredShortcut? { + guard usesNumberedDigitMatching else { return shortcut } + guard let digit = Int(shortcut.key), (1...9).contains(digit) else { + return nil + } + return StoredShortcut( + key: "1", + command: shortcut.command, + shift: shortcut.shift, + option: shortcut.option, + control: shortcut.control + ) } } @@ -198,7 +238,16 @@ enum KeyboardShortcutSettings { } static func setShortcut(_ shortcut: StoredShortcut, for action: Action) { - if let data = try? JSONEncoder().encode(shortcut) { + let storedShortcut: StoredShortcut + if let normalizedShortcut = action.normalizedRecordedShortcut(shortcut) { + storedShortcut = normalizedShortcut + } else if action.usesNumberedDigitMatching { + return + } else { + storedShortcut = shortcut + } + + if let data = try? JSONEncoder().encode(storedShortcut) { UserDefaults.standard.set(data, forKey: action.defaultsKey) } postDidChangeNotification(action: action) @@ -267,7 +316,9 @@ enum KeyboardShortcutSettings { static func nextSurfaceShortcut() -> StoredShortcut { shortcut(for: .nextSurface) } static func prevSurfaceShortcut() -> StoredShortcut { shortcut(for: .prevSurface) } + static func selectSurfaceByNumberShortcut() -> StoredShortcut { shortcut(for: .selectSurfaceByNumber) } static func newSurfaceShortcut() -> StoredShortcut { shortcut(for: .newSurface) } + static func selectWorkspaceByNumberShortcut() -> StoredShortcut { shortcut(for: .selectWorkspaceByNumber) } static func openBrowserShortcut() -> StoredShortcut { shortcut(for: .openBrowser) } static func toggleBrowserDeveloperToolsShortcut() -> StoredShortcut { shortcut(for: .toggleBrowserDeveloperTools) } @@ -283,22 +334,27 @@ struct StoredShortcut: Codable, Equatable { var control: Bool var displayString: String { + modifierDisplayString + keyDisplayString + } + + var modifierDisplayString: String { var parts: [String] = [] if control { parts.append("⌃") } if option { parts.append("⌥") } if shift { parts.append("⇧") } if command { parts.append("⌘") } - let keyText: String + return parts.joined() + } + + var keyDisplayString: String { switch key { case "\t": - keyText = "TAB" + return "TAB" case "\r": - keyText = "↩" + return "↩" default: - keyText = key.uppercased() + return key.uppercased() } - parts.append(keyText) - return parts.joined() } var modifierFlags: NSEvent.ModifierFlags { @@ -436,6 +492,8 @@ struct StoredShortcut: Codable, Equatable { struct KeyboardShortcutRecorder: View { let label: String @Binding var shortcut: StoredShortcut + var displayString: (StoredShortcut) -> String = { $0.displayString } + var transformRecordedShortcut: (StoredShortcut) -> StoredShortcut? = { $0 } @State private var isRecording = false var body: some View { @@ -444,7 +502,12 @@ struct KeyboardShortcutRecorder: View { Spacer() - ShortcutRecorderButton(shortcut: $shortcut, isRecording: $isRecording) + ShortcutRecorderButton( + shortcut: $shortcut, + isRecording: $isRecording, + displayString: displayString, + transformRecordedShortcut: transformRecordedShortcut + ) .frame(width: 120) } } @@ -453,10 +516,14 @@ struct KeyboardShortcutRecorder: View { private struct ShortcutRecorderButton: NSViewRepresentable { @Binding var shortcut: StoredShortcut @Binding var isRecording: Bool + let displayString: (StoredShortcut) -> String + let transformRecordedShortcut: (StoredShortcut) -> StoredShortcut? func makeNSView(context: Context) -> ShortcutRecorderNSButton { let button = ShortcutRecorderNSButton() button.shortcut = shortcut + button.displayString = displayString + button.transformRecordedShortcut = transformRecordedShortcut button.onShortcutRecorded = { newShortcut in shortcut = newShortcut isRecording = false @@ -469,12 +536,16 @@ private struct ShortcutRecorderButton: NSViewRepresentable { func updateNSView(_ nsView: ShortcutRecorderNSButton, context: Context) { nsView.shortcut = shortcut + nsView.displayString = displayString + nsView.transformRecordedShortcut = transformRecordedShortcut nsView.updateTitle() } } private class ShortcutRecorderNSButton: NSButton { var shortcut: StoredShortcut = KeyboardShortcutSettings.showNotificationsDefault + var displayString: (StoredShortcut) -> String = { $0.displayString } + var transformRecordedShortcut: (StoredShortcut) -> StoredShortcut? = { $0 } var onShortcutRecorded: ((StoredShortcut) -> Void)? var onRecordingChanged: ((Bool) -> Void)? private var isRecording = false @@ -502,7 +573,7 @@ private class ShortcutRecorderNSButton: NSButton { if isRecording { title = String(localized: "shortcut.pressShortcut.prompt", defaultValue: "Press shortcut…") } else { - title = shortcut.displayString + title = displayString(shortcut) } } @@ -528,8 +599,12 @@ private class ShortcutRecorderNSButton: NSButton { } if let newShortcut = StoredShortcut.from(event: event) { - self.shortcut = newShortcut - self.onShortcutRecorded?(newShortcut) + guard let transformedShortcut = self.transformRecordedShortcut(newShortcut) else { + NSSound.beep() + return nil + } + self.shortcut = transformedShortcut + self.onShortcutRecorded?(transformedShortcut) self.stopRecording() return nil } diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 06335850..c6509f46 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -11020,26 +11020,30 @@ class TerminalController { let name = parts[0].lowercased() let combo = parts[1].trimmingCharacters(in: .whitespacesAndNewlines) - let defaultsKey: String? + let action: KeyboardShortcutSettings.Action? switch name { case "focus_left", "focusleft": - defaultsKey = KeyboardShortcutSettings.focusLeftKey + action = .focusLeft case "focus_right", "focusright": - defaultsKey = KeyboardShortcutSettings.focusRightKey + action = .focusRight case "focus_up", "focusup": - defaultsKey = KeyboardShortcutSettings.focusUpKey + action = .focusUp case "focus_down", "focusdown": - defaultsKey = KeyboardShortcutSettings.focusDownKey + action = .focusDown + case "workspace_digits", "workspace_number", "select_workspace_by_number": + action = .selectWorkspaceByNumber + case "surface_digits", "surface_number", "select_surface_by_number": + action = .selectSurfaceByNumber default: - defaultsKey = nil + action = nil } - guard let defaultsKey else { - return "ERROR: Unknown shortcut name. Supported: focus_left, focus_right, focus_up, focus_down" + guard let action else { + return "ERROR: Unknown shortcut name. Supported: focus_left, focus_right, focus_up, focus_down, workspace_digits, surface_digits" } if combo.lowercased() == "clear" || combo.lowercased() == "default" || combo.lowercased() == "reset" { - UserDefaults.standard.removeObject(forKey: defaultsKey) + KeyboardShortcutSettings.resetShortcut(for: action) return "OK" } @@ -11054,10 +11058,13 @@ class TerminalController { option: parsed.modifierFlags.contains(.option), control: parsed.modifierFlags.contains(.control) ) - guard let data = try? JSONEncoder().encode(shortcut) else { - return "ERROR: Failed to encode shortcut" + if action.usesNumberedDigitMatching, + action.normalizedRecordedShortcut(shortcut) == nil { + return "ERROR: Numbered shortcuts must use a digit key (1-9). Example: ctrl+1" } - UserDefaults.standard.set(data, forKey: defaultsKey) + + let storedShortcut = action.normalizedRecordedShortcut(shortcut) ?? shortcut + KeyboardShortcutSettings.setShortcut(storedShortcut, for: action) return "OK" } diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 9ff61772..1cc1f9fc 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -155,6 +155,7 @@ struct cmuxApp: App { @AppStorage(KeyboardShortcutSettings.Action.prevSurface.defaultsKey) private var prevSurfaceShortcutData = Data() @AppStorage(KeyboardShortcutSettings.Action.nextSidebarTab.defaultsKey) private var nextWorkspaceShortcutData = Data() @AppStorage(KeyboardShortcutSettings.Action.prevSidebarTab.defaultsKey) private var prevWorkspaceShortcutData = Data() + @AppStorage(KeyboardShortcutSettings.Action.selectWorkspaceByNumber.defaultsKey) private var selectWorkspaceByNumberShortcutData = Data() @AppStorage(KeyboardShortcutSettings.Action.splitRight.defaultsKey) private var splitRightShortcutData = Data() @AppStorage(KeyboardShortcutSettings.Action.splitDown.defaultsKey) private var splitDownShortcutData = Data() @AppStorage(BrowserToolbarAccessorySpacingDebugSettings.key) private var browserToolbarAccessorySpacingRaw = BrowserToolbarAccessorySpacingDebugSettings.defaultSpacing @@ -799,15 +800,18 @@ struct cmuxApp: App { Divider() - // Cmd+1 through Cmd+9 for workspace selection (9 = last workspace) + // Numbered workspace selection (9 = last workspace) ForEach(1...9, id: \.self) { number in Button(String(localized: "menu.view.workspace", defaultValue: "Workspace \(number)")) { let manager = activeTabManager - if let targetIndex = WorkspaceShortcutMapper.workspaceIndex(forCommandDigit: number, workspaceCount: manager.tabs.count) { + if let targetIndex = WorkspaceShortcutMapper.workspaceIndex(forDigit: number, workspaceCount: manager.tabs.count) { manager.selectTab(at: targetIndex) } } - .keyboardShortcut(KeyEquivalent(Character("\(number)")), modifiers: .command) + .keyboardShortcut( + KeyEquivalent(Character("\(number)")), + modifiers: selectWorkspaceByNumberMenuShortcut.eventModifiers + ) } Divider() @@ -921,6 +925,13 @@ struct cmuxApp: App { ) } + private var selectWorkspaceByNumberMenuShortcut: StoredShortcut { + decodeShortcut( + from: selectWorkspaceByNumberShortcutData, + fallback: KeyboardShortcutSettings.Action.selectWorkspaceByNumber.defaultShortcut + ) + } + private var splitDownMenuShortcut: StoredShortcut { decodeShortcut(from: splitDownShortcutData, fallback: KeyboardShortcutSettings.Action.splitDown.defaultShortcut) } @@ -6129,7 +6140,12 @@ private struct ShortcutSettingRow: View { } var body: some View { - KeyboardShortcutRecorder(label: action.label, shortcut: $shortcut) + KeyboardShortcutRecorder( + label: action.label, + shortcut: $shortcut, + displayString: { action.displayedShortcutString(for: $0) }, + transformRecordedShortcut: { action.normalizedRecordedShortcut($0) } + ) .onChange(of: shortcut) { newValue in KeyboardShortcutSettings.setShortcut(newValue, for: action) } diff --git a/vendor/bonsplit b/vendor/bonsplit index efa23f4c..1610b457 160000 --- a/vendor/bonsplit +++ b/vendor/bonsplit @@ -1 +1 @@ -Subproject commit efa23f4c3c7d00688d8448dc7e4d08b4d847548d +Subproject commit 1610b457bc44bb1d50dd246792f8724ce21a7c81