Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
# cmux-owned Swift file length budget.
# Format: max_lines<TAB>relative path
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
20474 CLI/cmux.swift
17257 Sources/TerminalController.swift
16009 Sources/ContentView.swift
14268 Sources/AppDelegate.swift
13972 Sources/Workspace.swift
13403 Sources/GhosttyTerminalView.swift
10603 Sources/Panels/BrowserPanel.swift
8454 Sources/cmuxApp.swift
7495 Sources/TabManager.swift
20461 CLI/cmux.swift
17215 Sources/TerminalController.swift
16007 Sources/ContentView.swift
14338 Sources/AppDelegate.swift
13964 Sources/Workspace.swift
13428 Sources/GhosttyTerminalView.swift
10597 Sources/Panels/BrowserPanel.swift
8389 Sources/cmuxApp.swift
7492 Sources/TabManager.swift
6811 Sources/Panels/BrowserPanelView.swift
5252 cmuxTests/AppDelegateShortcutRoutingTests.swift
5491 cmuxTests/AppDelegateShortcutRoutingTests.swift
4661 cmuxTests/WorkspaceUnitTests.swift
4639 cmuxTests/TerminalAndGhosttyTests.swift
4557 cmuxTests/GhosttyConfigTests.swift
4469 cmuxTests/WorkspaceRemoteConnectionTests.swift
4403 Sources/BrowserWindowPortal.swift
4412 cmuxTests/WorkspaceRemoteConnectionTests.swift
4305 Sources/BrowserWindowPortal.swift
3933 Sources/Panels/FilePreviewPanel.swift
3840 Sources/Feed/FeedPanelView.swift
3660 cmuxTests/BrowserConfigTests.swift
3329 cmuxTests/BrowserPanelTests.swift
2850 Sources/CmuxConfig.swift
3588 cmuxTests/BrowserConfigTests.swift
3145 cmuxTests/BrowserPanelTests.swift
2863 cmuxTests/WindowAndDragTests.swift
2687 Sources/KeyboardShortcutSettings.swift
2830 Sources/CmuxConfig.swift
2609 Sources/SessionIndexView.swift
2491 Sources/Panels/CmuxWebView.swift
2325 Sources/KeyboardShortcutSettings.swift
2317 cmuxTests/TabManagerUnitTests.swift
2189 Sources/TerminalWindowPortal.swift
2172 Sources/TerminalWindowPortal.swift
2153 cmuxTests/SessionPersistenceTests.swift
2112 Sources/Update/UpdateTitlebarAccessory.swift
2026 cmuxTests/CJKIMEInputTests.swift
1949 Sources/FileExplorerView.swift
1949 Sources/Panels/BrowserWebAuthnSupport.swift
1941 Sources/SessionIndexStore.swift
1879 Sources/FileExplorerView.swift
1784 cmuxTests/ShortcutAndCommandPaletteTests.swift
1692 cmuxTests/CmuxConfigTests.swift
1681 Sources/KeyboardShortcutSettingsFileStore.swift
1679 Sources/KeyboardShortcutSettingsFileStore.swift
1517 cmuxUITests/BrowserPaneNavigationKeybindUITests.swift
1454 Sources/TerminalNotificationStore.swift
1365 Sources/Feed/FeedButtonStyleDebugWindowController.swift
Expand All @@ -48,22 +48,22 @@
1165 Sources/Auth/AuthManager.swift
1003 Sources/FileExplorerStore.swift
1001 cmuxTests/SidebarOrderingTests.swift
995 Sources/DockPanelView.swift
987 Sources/CommandPalette/CommandPaletteSearch.swift
976 cmuxTests/OmnibarAndToolsTests.swift
943 Sources/DockPanelView.swift
908 cmuxTests/CommandPaletteSearchEngineTests.swift
846 Sources/WorkspaceContentView.swift
834 Sources/RestorableAgentSession.swift
829 cmuxUITests/TerminalCmdClickUITests.swift
809 cmuxUITests/BonsplitTabDragUITests.swift
807 Sources/TerminalSSHSessionDetector.swift
785 Sources/App/TerminalDirectoryOpenSupport.swift
784 Sources/GhosttyConfig.swift
778 Sources/MainWindowFocusController.swift
752 cmuxUITests/CloseWorkspaceCmdDUITests.swift
720 Sources/App/MenuBarExtraController.swift
719 cmuxUITests/BrowserOmnibarSuggestionsUITests.swift
718 Sources/App/MenuBarExtraController.swift
714 Sources/AppleScriptSupport.swift
701 cmuxUITests/BonsplitTabDragUITests.swift
683 Sources/SocketControlSettings.swift
674 Sources/App/ShortcutRoutingSupport.swift
643 Sources/Panels/BrowserPopupWindowController.swift
Expand Down
73 changes: 72 additions & 1 deletion Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11145,7 +11145,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return true
}

// Split actions: Cmd+D / Cmd+Shift+D
// Configured split actions.
if matchConfiguredShortcut(event: event, action: .splitRight) {
#if DEBUG
cmuxDebugLog("shortcut.action name=splitRight \(debugShortcutRouteSnapshot(event: event))")
Expand Down Expand Up @@ -12169,6 +12169,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}

private func matchConfiguredShortcut(event: NSEvent, shortcut: StoredShortcut) -> Bool {
guard !shortcut.isUnbound else { return false }
if let prefix = activeConfiguredShortcutChordPrefixForCurrentEvent {
guard let secondStroke = shortcut.secondStroke,
shortcut.firstStroke == prefix else {
Expand All @@ -12189,6 +12190,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
action: KeyboardShortcutSettings.Action
) -> Int? {
let shortcut = KeyboardShortcutSettings.shortcut(for: action)
guard !shortcut.isUnbound else { return nil }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant isUnbound guard check is dead code

Low Severity

The !shortcut.isUnbound condition in the guard at line 12201 is redundant because the early guard at line 12193 already returns nil if shortcut.isUnbound is true. Any execution reaching line 12201 is guaranteed to have isUnbound == false, making that part of the compound guard dead code that adds confusion about whether the property could change between the two checks.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6e0278d. Configure here.

if let prefix = activeConfiguredShortcutChordPrefixForCurrentEvent {
guard let secondStroke = shortcut.secondStroke,
shortcut.firstStroke == prefix else {
Expand All @@ -12207,6 +12209,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
arrowKeyCode: UInt16
) -> Bool {
let shortcut = KeyboardShortcutSettings.shortcut(for: action)
guard !shortcut.isUnbound else { return false }
if let prefix = activeConfiguredShortcutChordPrefixForCurrentEvent {
guard let secondStroke = shortcut.secondStroke,
shortcut.firstStroke == prefix else {
Expand Down Expand Up @@ -12349,6 +12352,44 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
shortcut.matches(event: event, layoutCharacterProvider: shortcutLayoutCharacterProvider)
}

private func matchesKeyboardShortcutEvent(
_ event: NSEvent,
action: KeyboardShortcutSettings.Action,
shortcut: StoredShortcut
) -> Bool {
guard !shortcut.isUnbound else { return false }
if action.usesNumberedDigitMatching {
return numberedShortcutDigit(event: event, shortcut: shortcut) != nil
}
guard !shortcut.hasChord else { return false }
return matchShortcut(event: event, shortcut: shortcut)
}

func shouldSuppressStaleCmuxMenuShortcut(event: NSEvent) -> Bool {
guard event.type == .keyDown else { return false }
if event.window is NSPanel || NSApp.keyWindow is NSPanel || NSApp.modalWindow != nil || NSApp.keyWindow?.attachedSheet != nil {
return false
}
let flags = event.modifierFlags
.intersection(.deviceIndependentFlagsMask)
.subtracting([.numericPad, .function, .capsLock])
guard flags.contains(.command) else { return false }

for action in KeyboardShortcutSettings.Action.allCases where action != .showHideAllWindows {
let currentShortcut = KeyboardShortcutSettings.shortcut(for: action)
if matchesKeyboardShortcutEvent(event, action: action, shortcut: currentShortcut) {
return false
}
}

for action in KeyboardShortcutSettings.Action.allCases where action != .showHideAllWindows {
if matchesKeyboardShortcutEvent(event, action: action, shortcut: action.defaultShortcut) {
return true
}
}
return false
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private func numberedShortcutDigit(event: NSEvent, stroke: ShortcutStroke) -> Int? {
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
.subtracting([.numericPad, .function, .capsLock])
Expand Down Expand Up @@ -13468,6 +13509,22 @@ private extension NSApplication {
AppDelegate.shared?.handleMinimalModeTitlebarDoubleClickMouseDown(event: event) == true {
return
}
if AppDelegate.shared?.shouldSuppressStaleCmuxMenuShortcut(event: event) == true {
let responder = event.window?.firstResponder
?? keyWindow?.firstResponder
?? mainWindow?.firstResponder
if let ghosttyView = cmuxOwningGhosttyView(for: responder) {
ghosttyView.keyDown(with: event)
#if DEBUG
cmuxDebugLog("app.sendEvent suppressed stale cmux menu shortcut and forwarded to terminal")
#endif
} else {
#if DEBUG
cmuxDebugLog("app.sendEvent suppressed stale cmux menu shortcut")
#endif
}
return
}
cmux_applicationSendEvent(event)
}
}
Expand Down Expand Up @@ -13814,6 +13871,20 @@ private extension NSWindow {
)
return true
}
if AppDelegate.shared?.shouldSuppressStaleCmuxMenuShortcut(event: event) == true {
if let firstResponderGhosttyView {
firstResponderGhosttyView.keyDown(with: event)
#if DEBUG
cmuxDebugLog(" → terminal received command equivalent bypassing stale cmux menu shortcut")
#endif
return true
}
#if DEBUG
cmuxDebugLog(" → suppressed stale cmux menu shortcut")
#endif
return false
}

if let ghosttyView = firstResponderGhosttyView {
// If the IME is composing and the key has no Cmd modifier, don't intercept —
// let it flow through normal AppKit event dispatch so the input method can
Expand Down
6 changes: 5 additions & 1 deletion Sources/CmuxConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ struct CmuxConfigActionDefinition: Codable, Sendable, Hashable {
throw DecodingError.dataCorruptedError(
forKey: key,
in: container,
debugDescription: "shortcut must use modifier+key syntax like 'cmd+shift+t'"
debugDescription: "shortcut must use modifier+key syntax like 'cmd+shift+t' or be empty to unbind"
)
}
return shortcut
Expand Down Expand Up @@ -969,6 +969,10 @@ struct CmuxConfigActionDefinition: Codable, Sendable, Hashable {
in container: inout KeyedEncodingContainer<CodingKeys>
) throws {
guard let shortcut else { return }
if shortcut.isUnbound {
try container.encode("", forKey: key)
return
}
if let secondStroke = shortcut.secondStroke {
try container.encode(
[shortcut.firstStroke.configString(), secondStroke.configString()],
Expand Down
36 changes: 32 additions & 4 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,7 @@ class GhosttyApp {
prefix: "cmux-shell-integration-override",
logLabel: "shell integration override (fallback)"
)
loadCmuxOwnedGhosttyKeybindOverrides(fallbackConfig)
let fallbackRenderingModeChanged = setUsesHostLayerBackground(
true,
source: "initialize.fallbackConfig"
Expand Down Expand Up @@ -2119,11 +2120,27 @@ class GhosttyApp {
prefix: "cmux-shell-integration-override",
logLabel: "shell integration override"
)
loadCmuxOwnedGhosttyKeybindOverrides(config)

ghostty_config_finalize(config)
return renderingModeChanged
}

private func loadCmuxOwnedGhosttyKeybindOverrides(_ config: ghostty_config_t) {
// cmux owns these split shortcuts through KeyboardShortcutSettings.
// Remove Ghostty's default fallbacks so remapped or cleared shortcuts
// can reach the focused terminal instead of creating a split.
loadInlineGhosttyConfig(
"""
keybind = super+d=unbind
keybind = super+shift+d=unbind
""",
into: config,
prefix: "cmux-owned-split-keybind-overrides",
logLabel: "cmux-owned split keybind overrides"
)
}

/// When the user has not configured `font-codepoint-map` for CJK ranges
/// and has not already provided an explicit multi-entry `font-family`
/// fallback chain, Ghostty's `CTFontCollection` scoring may pick an
Expand Down Expand Up @@ -6762,6 +6779,17 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
_ = performBindingAction("paste_from_clipboard")
}

private func applyConfiguredMenuShortcut(_ shortcut: StoredShortcut, to item: NSMenuItem) {
guard let keyEquivalent = shortcut.menuItemKeyEquivalent else {
item.keyEquivalent = ""
item.keyEquivalentModifierMask = []
return
}

item.keyEquivalent = keyEquivalent
item.keyEquivalentModifierMask = shortcut.modifierFlags
}

/// Validates whether edit menu items (copy, paste, split) should be enabled.
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
switch item.action {
Expand Down Expand Up @@ -8762,10 +8790,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
let splitHorizontallyItem = menu.addItem(
withTitle: String(localized: "terminalContextMenu.splitHorizontally", defaultValue: "Split Horizontally"),
action: #selector(splitHorizontally(_:)),
keyEquivalent: "d"
keyEquivalent: ""
)
splitHorizontallyItem.target = self
splitHorizontallyItem.keyEquivalentModifierMask = [.command, .shift]
applyConfiguredMenuShortcut(KeyboardShortcutSettings.shortcut(for: .splitDown), to: splitHorizontallyItem)
splitHorizontallyItem.image = NSImage(
systemSymbolName: "rectangle.bottomhalf.inset.filled",
accessibilityDescription: nil
Expand All @@ -8774,10 +8802,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
let splitVerticallyItem = menu.addItem(
withTitle: String(localized: "terminalContextMenu.splitVertically", defaultValue: "Split Vertically"),
action: #selector(splitVertically(_:)),
keyEquivalent: "d"
keyEquivalent: ""
)
splitVerticallyItem.target = self
splitVerticallyItem.keyEquivalentModifierMask = [.command]
applyConfiguredMenuShortcut(KeyboardShortcutSettings.shortcut(for: .splitRight), to: splitVerticallyItem)
splitVerticallyItem.image = NSImage(
systemSymbolName: "rectangle.righthalf.inset.filled",
accessibilityDescription: nil
Expand Down
9 changes: 4 additions & 5 deletions Sources/KeyboardShortcutSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,6 @@ enum KeyboardShortcutSettings {
}

private static let hardcodedSystemWideHotkeyConflicts: [StoredShortcut] = [
StoredShortcut(key: "d", command: true, shift: false, option: false, control: false),
StoredShortcut(key: "\t", command: false, shift: false, option: false, control: true),
StoredShortcut(key: "\t", command: false, shift: true, option: false, control: true),
StoredShortcut(key: "`", command: true, shift: false, option: false, control: false),
Expand Down Expand Up @@ -726,6 +725,8 @@ enum KeyboardShortcutSettings {
postDidChangeNotification(action: action)
}

static func clearShortcut(for action: Action) { setShortcut(.unbound, for: action) }

static func resetAll() {
for action in Action.allCases {
UserDefaults.standard.removeObject(forKey: action.defaultsKey)
Expand Down Expand Up @@ -2172,9 +2173,7 @@ extension StoredShortcut {
}

var configIdentifier: String {
if isUnbound {
return "none"
}
if isUnbound { return "none" }
if let secondStroke {
return "\(firstStroke.configString()) \(secondStroke.configString())"
}
Expand All @@ -2183,7 +2182,7 @@ extension StoredShortcut {

private static func isUnboundConfigToken(_ rawValue: String) -> Bool {
let normalized = rawValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return normalized.isEmpty || normalized == "none" || normalized == "unbound"
return normalized.isEmpty || normalized == "none" || normalized == "clear" || normalized == "unbound"
}
}

Expand Down
13 changes: 11 additions & 2 deletions Sources/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12789,6 +12789,10 @@ class TerminalController {
action = .focusUp
case "focus_down", "focusdown":
action = .focusDown
case "split_right", "splitright":
action = .splitRight
case "split_down", "splitdown":
action = .splitDown
case "workspace_digits", "workspace_number", "select_workspace_by_number":
action = .selectWorkspaceByNumber
case "surface_digits", "surface_number", "select_surface_by_number":
Expand All @@ -12798,10 +12802,15 @@ class TerminalController {
}

guard let action else {
return "ERROR: Unknown shortcut name. Supported: focus_left, focus_right, focus_up, focus_down, workspace_digits, surface_digits"
return "ERROR: Unknown shortcut name. Supported: focus_left, focus_right, focus_up, focus_down, split_right, split_down, workspace_digits, surface_digits"
}

if combo.lowercased() == "clear" || combo.lowercased() == "default" || combo.lowercased() == "reset" {
if combo.lowercased() == "clear" || combo.lowercased() == "unbound" || combo.lowercased() == "none" {
KeyboardShortcutSettings.clearShortcut(for: action)
return "OK"
}

if combo.lowercased() == "default" || combo.lowercased() == "reset" {
KeyboardShortcutSettings.resetShortcut(for: action)
return "OK"
}
Expand Down
Loading