native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix Cmd-W terminal tab close fallback

onevcat 012b1dc0 82653f43

+149 -6
+25 -2
supacode/Commands/WindowCommands.swift
··· 3 3 struct WindowCommands: Commands { 4 4 let ghosttyShortcuts: GhosttyShortcutManager 5 5 let resolvedKeybindings: ResolvedKeybindingMap 6 + @FocusedValue(\.closeTabAction) private var closeTabAction 6 7 @FocusedValue(\.closeSurfaceAction) private var closeSurfaceAction 7 8 @FocusedValue(\.selectPreviousTerminalTabAction) private var selectPreviousTerminalTabAction 8 9 @FocusedValue(\.selectNextTerminalTabAction) private var selectNextTerminalTabAction ··· 15 16 16 17 var body: some Commands { 17 18 let closeSurfaceHotkey = ghosttyShortcuts.keyboardShortcut(for: "close_surface") 18 - let isCloseSurfaceOverlapping = closeSurfaceHotkey?.key == "w" && closeSurfaceHotkey?.modifiers == .command 19 + let closeTabHotkey = ghosttyShortcuts.keyboardShortcut(for: "close_tab") 20 + let closeWindowShortcut = WindowCloseShortcutPolicy.closeWindowShortcut( 21 + closeSurfaceShortcut: closeSurfaceHotkey, 22 + closeTabShortcut: closeTabHotkey, 23 + hasTerminalCloseTarget: closeTabAction != nil || closeSurfaceAction != nil 24 + ) 19 25 20 26 CommandGroup(replacing: .saveItem) { 21 27 Button("Close Window", systemImage: "xmark") { ··· 23 29 } 24 30 .modifier( 25 31 KeyboardShortcutModifier( 26 - shortcut: !isCloseSurfaceOverlapping || closeSurfaceAction == nil ? .init("w") : nil 32 + shortcut: closeWindowShortcut 27 33 ) 28 34 ) 29 35 } ··· 122 128 .disabled(selectTerminalPaneRightAction == nil) 123 129 } 124 130 } 131 + } 132 + } 133 + 134 + enum WindowCloseShortcutPolicy { 135 + static func closeWindowShortcut( 136 + closeSurfaceShortcut: KeyboardShortcut?, 137 + closeTabShortcut: KeyboardShortcut?, 138 + hasTerminalCloseTarget: Bool 139 + ) -> KeyboardShortcut? { 140 + if hasTerminalCloseTarget && (isCommandW(closeSurfaceShortcut) || isCommandW(closeTabShortcut)) { 141 + return nil 142 + } 143 + return KeyboardShortcut("w") 144 + } 145 + 146 + private static func isCommandW(_ shortcut: KeyboardShortcut?) -> Bool { 147 + shortcut?.key == "w" && shortcut?.modifiers == .command 125 148 } 126 149 } 127 150
+40 -4
supacode/Features/Repositories/Views/WorktreeDetailView.swift
··· 280 280 content 281 281 .focusedSceneValue(\.openSelectedWorktreeAction, actions.openSelectedWorktree) 282 282 .focusedSceneValue(\.newTerminalAction, actions.newTerminal) 283 - .focusedValue(\.closeTabAction, actions.closeTab) 284 - .focusedValue(\.closeSurfaceAction, actions.closeSurface) 283 + .focusedSceneValue(\.closeTabAction, actions.closeTab) 284 + .focusedSceneValue(\.closeSurfaceAction, actions.closeSurface) 285 285 .focusedSceneValue(\.resetFontSizeAction, actions.resetFontSize) 286 286 .focusedSceneValue(\.increaseFontSizeAction, actions.increaseFontSize) 287 287 .focusedSceneValue(\.decreaseFontSizeAction, actions.decreaseFontSize) ··· 353 353 } 354 354 } 355 355 356 + func closeTabAction() -> (() -> Void)? { 357 + if repositories.isShowingCanvas { 358 + guard let worktreeID = terminalManager.canvasFocusedWorktreeID, 359 + let state = terminalManager.stateIfExists(for: worktreeID), 360 + state.canCloseFocusedTab 361 + else { 362 + return nil 363 + } 364 + return { _ = state.closeFocusedTab() } 365 + } 366 + guard hasActiveWorktree, let selectedWorktree = repositories.selectedTerminalWorktree, 367 + terminalManager.stateIfExists(for: selectedWorktree.id)?.canCloseFocusedTab == true 368 + else { 369 + return nil 370 + } 371 + return { store.send(.closeTab) } 372 + } 373 + 374 + func closeSurfaceAction() -> (() -> Void)? { 375 + if repositories.isShowingCanvas { 376 + guard let worktreeID = terminalManager.canvasFocusedWorktreeID, 377 + let state = terminalManager.stateIfExists(for: worktreeID), 378 + state.canCloseFocusedSurface 379 + else { 380 + return nil 381 + } 382 + return { _ = state.closeFocusedSurface() } 383 + } 384 + guard hasActiveWorktree, let selectedWorktree = repositories.selectedTerminalWorktree, 385 + terminalManager.stateIfExists(for: selectedWorktree.id)?.canCloseFocusedSurface == true 386 + else { 387 + return nil 388 + } 389 + return { store.send(.closeSurface) } 390 + } 391 + 356 392 return FocusedActions( 357 393 openSelectedWorktree: action(.openSelectedWorktree), 358 394 newTerminal: action(.newTerminal), 359 - closeTab: canvasAction { $0.closeFocusedTab() } ?? action(.closeTab), 360 - closeSurface: canvasAction { $0.closeFocusedSurface() } ?? action(.closeSurface), 395 + closeTab: closeTabAction(), 396 + closeSurface: closeSurfaceAction(), 361 397 resetFontSize: fontSizeAction("reset_font_size"), 362 398 increaseFontSize: fontSizeAction("increase_font_size:1"), 363 399 decreaseFontSize: fontSizeAction("decrease_font_size:1"),
+14
supacode/Features/Terminal/Models/WorktreeTerminalState.swift
··· 69 69 let surfaceIds = trees[tabId]?.leaves().map(\.id) ?? [] 70 70 return notifications.contains { !$0.isRead && surfaceIds.contains($0.surfaceId) } 71 71 } 72 + 73 + var canCloseFocusedTab: Bool { 74 + tabManager.selectedTabId != nil 75 + } 76 + 77 + var canCloseFocusedSurface: Bool { 78 + guard let tabId = tabManager.selectedTabId, 79 + let focusedId = focusedSurfaceIdByTab[tabId] 80 + else { 81 + return false 82 + } 83 + return surfaces[focusedId] != nil 84 + } 85 + 72 86 var isSelected: () -> Bool = { false } 73 87 var onNotificationReceived: ((String, String) -> Void)? 74 88 var onNotificationIndicatorChanged: (() -> Void)?
+48
supacodeTests/WindowCloseShortcutPolicyTests.swift
··· 1 + import SwiftUI 2 + import Testing 3 + 4 + @testable import supacode 5 + 6 + struct WindowCloseShortcutPolicyTests { 7 + @Test func closeWindowDoesNotClaimCommandWWhenCloseSurfaceUsesCommandW() { 8 + let shortcut = WindowCloseShortcutPolicy.closeWindowShortcut( 9 + closeSurfaceShortcut: KeyboardShortcut("w"), 10 + closeTabShortcut: nil, 11 + hasTerminalCloseTarget: true 12 + ) 13 + 14 + #expect(shortcut == nil) 15 + } 16 + 17 + @Test func closeWindowDoesNotClaimCommandWWhenCloseTabUsesCommandW() { 18 + let shortcut = WindowCloseShortcutPolicy.closeWindowShortcut( 19 + closeSurfaceShortcut: KeyboardShortcut("w", modifiers: [.option, .command]), 20 + closeTabShortcut: KeyboardShortcut("w"), 21 + hasTerminalCloseTarget: true 22 + ) 23 + 24 + #expect(shortcut == nil) 25 + } 26 + 27 + @Test func closeWindowKeepsCommandWWhenTerminalCloseActionsUseDifferentShortcuts() { 28 + let shortcut = WindowCloseShortcutPolicy.closeWindowShortcut( 29 + closeSurfaceShortcut: KeyboardShortcut("w", modifiers: [.shift, .command]), 30 + closeTabShortcut: KeyboardShortcut("w", modifiers: [.option, .command]), 31 + hasTerminalCloseTarget: true 32 + ) 33 + 34 + #expect(shortcut?.key == "w") 35 + #expect(shortcut?.modifiers == .command) 36 + } 37 + 38 + @Test func closeWindowKeepsCommandWWhenTerminalHasNoCloseTarget() { 39 + let shortcut = WindowCloseShortcutPolicy.closeWindowShortcut( 40 + closeSurfaceShortcut: KeyboardShortcut("w"), 41 + closeTabShortcut: KeyboardShortcut("w"), 42 + hasTerminalCloseTarget: false 43 + ) 44 + 45 + #expect(shortcut?.key == "w") 46 + #expect(shortcut?.modifiers == .command) 47 + } 48 + }
+22
supacodeTests/WorktreeTerminalManagerTests.swift
··· 66 66 #expect(state.onFontSizeAdjusted != nil) 67 67 } 68 68 69 + @Test func closeTargetAvailabilityFollowsTerminalModelState() { 70 + let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) 71 + let worktree = makeWorktree() 72 + let state = manager.state(for: worktree) 73 + 74 + #expect(state.canCloseFocusedTab == false) 75 + #expect(state.canCloseFocusedSurface == false) 76 + 77 + let tabId = state.createTab() 78 + 79 + #expect(tabId != nil) 80 + #expect(state.canCloseFocusedTab == true) 81 + #expect(state.canCloseFocusedSurface == true) 82 + 83 + if let tabId { 84 + state.closeTab(tabId) 85 + } 86 + 87 + #expect(state.canCloseFocusedTab == false) 88 + #expect(state.canCloseFocusedSurface == false) 89 + } 90 + 69 91 @Test func notificationIndicatorUsesCurrentCountOnStreamStart() async { 70 92 let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) 71 93 let worktree = makeWorktree()