Customizable number shortcuts (#1951)

* Allow customizing numbered workspace and surface shortcuts

* Update bonsplit submodule to squashed main commit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Austin Wang 2026-03-22 14:54:54 -07:00 committed by GitHub
parent 661a4e8c7f
commit 33dcc606bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 272 additions and 59 deletions

View file

@ -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": {

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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"
}

View file

@ -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)
}

2
vendor/bonsplit vendored

@ -1 +1 @@
Subproject commit efa23f4c3c7d00688d8448dc7e4d08b4d847548d
Subproject commit 1610b457bc44bb1d50dd246792f8724ce21a7c81