diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 133a837a61..1b579444d2 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -895,6 +895,14 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr } } + private func closeNativeTab(windowNumber: Int) { + guard let window else { return } + let candidateWindows = window.tabGroup?.windows ?? [window] + guard let targetWindow = candidateWindows.first(where: { $0.windowNumber == windowNumber }) else { return } + guard let targetController = targetWindow.windowController as? TerminalController else { return } + targetController.closeTab(nil) + } + private func moveNativeTabBefore(movingWindowNumber: Int, targetWindowNumber: Int) { guard movingWindowNumber != targetWindowNumber else { return } guard let window else { return } @@ -1458,6 +1466,9 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr focusNativeTab: { [weak self] windowNumber in self?.focusNativeTab(windowNumber: windowNumber) }, + closeNativeTab: { [weak self] windowNumber in + self?.closeNativeTab(windowNumber: windowNumber) + }, moveNativeTabBefore: { [weak self] moving, target in self?.moveNativeTabBefore(movingWindowNumber: moving, targetWindowNumber: target) }, diff --git a/macos/Sources/Features/Terminal/TerminalWorkspaceView.swift b/macos/Sources/Features/Terminal/TerminalWorkspaceView.swift index 11ecf7a34d..778480ba76 100644 --- a/macos/Sources/Features/Terminal/TerminalWorkspaceView.swift +++ b/macos/Sources/Features/Terminal/TerminalWorkspaceView.swift @@ -14,6 +14,7 @@ struct TerminalWorkspaceView: View { let openWorktreeAgent: (String, WorktrunkAgent) -> Void let resumeSession: ((AISession) -> Void)? let focusNativeTab: (Int) -> Void + let closeNativeTab: (Int) -> Void let moveNativeTabBefore: (Int, Int) -> Void let moveNativeTabAfter: (Int, Int) -> Void let onSidebarWidthChange: (CGFloat) -> Void @@ -36,6 +37,7 @@ struct TerminalWorkspaceView: View { openWorktreeAgent: openWorktreeAgent, resumeSession: resumeSession, focusNativeTab: focusNativeTab, + closeNativeTab: closeNativeTab, moveNativeTabBefore: moveNativeTabBefore, moveNativeTabAfter: moveNativeTabAfter, onSelectWorktree: { path in diff --git a/macos/Sources/Features/Terminal/TerminalWorkspaceViewContainer.swift b/macos/Sources/Features/Terminal/TerminalWorkspaceViewContainer.swift index dc13b6a4c7..5551360679 100644 --- a/macos/Sources/Features/Terminal/TerminalWorkspaceViewContainer.swift +++ b/macos/Sources/Features/Terminal/TerminalWorkspaceViewContainer.swift @@ -20,6 +20,7 @@ class TerminalWorkspaceViewContainer: NSView { openWorktreeAgent: @escaping (String, WorktrunkAgent) -> Void, resumeSession: ((AISession) -> Void)? = nil, focusNativeTab: @escaping (Int) -> Void, + closeNativeTab: @escaping (Int) -> Void, moveNativeTabBefore: @escaping (Int, Int) -> Void, moveNativeTabAfter: @escaping (Int, Int) -> Void, onSidebarWidthChange: @escaping (CGFloat) -> Void, @@ -38,6 +39,7 @@ class TerminalWorkspaceViewContainer: NSView { openWorktreeAgent: openWorktreeAgent, resumeSession: resumeSession, focusNativeTab: focusNativeTab, + closeNativeTab: closeNativeTab, moveNativeTabBefore: moveNativeTabBefore, moveNativeTabAfter: moveNativeTabAfter, onSidebarWidthChange: onSidebarWidthChange, diff --git a/macos/Sources/Features/Worktrunk/WorktrunkSidebarView.swift b/macos/Sources/Features/Worktrunk/WorktrunkSidebarView.swift index 5f883c3a8a..49fddf6906 100644 --- a/macos/Sources/Features/Worktrunk/WorktrunkSidebarView.swift +++ b/macos/Sources/Features/Worktrunk/WorktrunkSidebarView.swift @@ -11,6 +11,7 @@ struct WorktrunkSidebarView: View { let openWorktreeAgent: (String, WorktrunkAgent) -> Void var resumeSession: ((AISession) -> Void)? let focusNativeTab: (Int) -> Void + let closeNativeTab: (Int) -> Void let moveNativeTabBefore: (Int, Int) -> Void let moveNativeTabAfter: (Int, Int) -> Void var onSelectWorktree: ((String?) -> Void)? @@ -367,23 +368,27 @@ struct WorktrunkSidebarView: View { defaultAction: defaultAction, availableAgents: availableAgents, alwaysVisibleWorktreePaths: alwaysVisibleWorktreePaths, - focusNativeTab: focusNativeTab, - moveBefore: moveBeforePreservingScroll, - moveAfter: moveAfterPreservingScroll, - windowNumberByWorktreePath: windowNumberByWorktreePath - ) - } + focusNativeTab: focusNativeTab, + closeNativeTab: closeNativeTab, + onRemoveWorktree: { worktree in + removeWorktreeConfirm = worktree + }, + moveBefore: moveBeforePreservingScroll, + moveAfter: moveAfterPreservingScroll, + windowNumberByWorktreePath: windowNumberByWorktreePath + ) + } - if let last = shownTabs.last?.tab { - Rectangle() - .fill(Color.clear) - .frame(maxWidth: .infinity) - .frame(height: 1) - .contentShape(Rectangle()) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - .listRowSeparator(.hidden) - .overlay(alignment: .center) { - if sidebarTabsEndDropTarget { + if let last = shownTabs.last?.tab { + Rectangle() + .fill(Color.clear) + .frame(maxWidth: .infinity) + .frame(height: 1) + .contentShape(Rectangle()) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) + .overlay(alignment: .center) { + if sidebarTabsEndDropTarget { SidebarInsertionIndicatorLine() } } @@ -870,6 +875,8 @@ private struct WorktreeTabDisclosureGroup: View { let availableAgents: [WorktrunkAgent] let alwaysVisibleWorktreePaths: Set let focusNativeTab: (Int) -> Void + let closeNativeTab: (Int) -> Void + let onRemoveWorktree: (WorktrunkStore.Worktree) -> Void let moveBefore: (Int, Int) -> Void let moveAfter: (Int, Int) -> Void let windowNumberByWorktreePath: [String: Int] @@ -926,6 +933,12 @@ private struct WorktreeTabDisclosureGroup: View { sidebarState.selection = .worktree(repoID: worktree.repositoryID, path: worktree.path) focusNativeTab(tab.windowNumber) }, + onClose: { + closeNativeTab(tab.windowNumber) + }, + onRemoveWorktree: { + onRemoveWorktree(worktree) + }, onDropBefore: { moving in guard moving != tab.windowNumber else { return } moveBefore(moving, tab.windowNumber) @@ -951,6 +964,8 @@ private struct WorktreeTabRowLabel: View { let openWorktree: (String) -> Void let openWorktreeAgent: (String, WorktrunkAgent) -> Void let onActivate: () -> Void + let onClose: () -> Void + let onRemoveWorktree: () -> Void let onDropBefore: (Int) -> Void let windowNumberByWorktreePath: [String: Int] @@ -1085,6 +1100,18 @@ private struct WorktreeTabRowLabel: View { SidebarInsertionIndicatorLine() } } + .contextMenu { + Button("Close Tab") { + onClose() + } + Button("Remove Worktree…") { + onRemoveWorktree() + } + .disabled(worktree.isMain) + Button("Reveal in Finder") { + NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: worktree.path)]) + } + } .onDrop(of: [UTType.fileURL.identifier], isTargeted: isDropTargetBinding) { providers in return SidebarFileURLDrop.loadURL(from: providers) { url in guard let url else { return }