diff --git a/CodeEdit/Documents/WorkspaceDocument.swift b/CodeEdit/Documents/WorkspaceDocument.swift index ed7418b195..8d0744e5dd 100644 --- a/CodeEdit/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Documents/WorkspaceDocument.swift @@ -18,9 +18,14 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { @Published var selectedId: String? @Published var openFileItems: [WorkspaceClient.FileItem] = [] - @Published var sortFoldersOnTop: Bool = true + @Published var sortFoldersOnTop: Bool = true @Published var fileItems: [WorkspaceClient.FileItem] = [] + var selected: WorkspaceClient.FileItem? { + guard let selectedId = selectedId else { return nil } + return fileItems.first(where: { $0.id == selectedId }) + } + var quickOpenState: QuickOpenState? var openedCodeFiles: [WorkspaceClient.FileItem: CodeFileDocument] = [:] @@ -49,6 +54,27 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { } } + func closeFileTabs(items: Items) where Items: Collection, Items.Element == WorkspaceClient.FileItem { + // TODO: Could potentially be optimized + for item in items { + closeFileTab(item: item) + } + } + + func closeFileTab(where predicate: (WorkspaceClient.FileItem) -> Bool) { + closeFileTabs(items: openFileItems.filter(predicate)) + } + + func closeFileTabs(after item: WorkspaceClient.FileItem) { + guard let startIdx = openFileItems.firstIndex(where: { $0.id == item.id }) else { + assert(false, "Expected file item to be present in openFileItems") + return + } + + let range = openFileItems[(startIdx+1)...] + closeFileTabs(items: range) + } + func openFile(item: WorkspaceClient.FileItem) { do { let codeFile = try CodeFileDocument( diff --git a/CodeEdit/TabBar/TabBar.swift b/CodeEdit/TabBar/TabBar.swift index 9a7aad6a96..38153972d6 100644 --- a/CodeEdit/TabBar/TabBar.swift +++ b/CodeEdit/TabBar/TabBar.swift @@ -31,38 +31,10 @@ struct TabBar: View { ScrollViewReader { value in HStack(alignment: .center, spacing: 0.0) { ForEach(workspace.openFileItems, id: \.id) { item in - let isActive = workspace.selectedId == item.id - - Button( - action: { workspace.selectedId = item.id }, - label: { - if isActive { - TabBarItem( - item: item, - windowController: windowController, - workspace: workspace - ) - .background( - BlurView( - material: NSVisualEffectView.Material.titlebar, - blendingMode: NSVisualEffectView.BlendingMode.withinWindow - ) - ) - } else { - TabBarItem( - item: item, - windowController: windowController, - workspace: workspace - ) - } - } - ) - .animation(.easeOut(duration: 0.2), value: workspace.openFileItems) - .buttonStyle(.plain) - .id(item.id) - .keyboardShortcut( - self.getTabId(fileName: item.fileName), - modifiers: [.command] + TabBarItem( + item: item, + windowController: windowController, + workspace: workspace ) } } @@ -75,15 +47,4 @@ struct TabBar: View { .background(BlurView(material: NSVisualEffectView.Material.windowBackground, blendingMode: NSVisualEffectView.BlendingMode.withinWindow)) } - - func getTabId(fileName: String) -> KeyEquivalent { - for counter in 0..<9 where workspace.openFileItems.count > counter && - workspace.openFileItems[counter].fileName == fileName { - return KeyEquivalent.init( - Character.init("\(counter + 1)") - ) - } - - return "0" - } } diff --git a/CodeEdit/TabBar/TabBarItem.swift b/CodeEdit/TabBar/TabBarItem.swift index 647f3949fa..13c22fb869 100644 --- a/CodeEdit/TabBar/TabBarItem.swift +++ b/CodeEdit/TabBar/TabBarItem.swift @@ -10,12 +10,17 @@ import WorkspaceClient struct TabBarItem: View { @State var isHovering: Bool = false - var item: WorkspaceClient.FileItem + var item: WorkspaceClient.FileItem var windowController: NSWindowController @ObservedObject var workspace: WorkspaceDocument var tabBarHeight: Double = 28.0 - var body: some View { - let isActive = item.id == workspace.selectedId + + var isActive: Bool { + item.id == workspace.selectedId + } + + @ViewBuilder + var content: some View { HStack(spacing: 0.0) { FileTabRow(fileItem: item, isSelected: isActive, isHovering: isHovering) { withAnimation { @@ -42,4 +47,55 @@ struct TabBarItem: View { } } } + + var body: some View { + Button( + action: { workspace.selectedId = item.id }, + label: { + content + .background( + isActive ? AnyView(BlurView( + material: NSVisualEffectView.Material.titlebar, + blendingMode: NSVisualEffectView.BlendingMode.withinWindow + )) : AnyView(EmptyView()) + ) + }) + .animation(.easeOut(duration: 0.2), value: workspace.openFileItems) + .buttonStyle(.plain) + .id(item.id) + .keyboardShortcut( + workspace.getTabKeyEquivalent(item: item), + modifiers: [.command] + ) + .contextMenu { + Button("Close Tab") { + withAnimation { + workspace.closeFileTab(item: item) + } + } + Button("Close Other Tab") { + withAnimation { + workspace.closeFileTab(where: { $0.id != item.id }) + } + } + Button("Close Tabs to the Right") { + withAnimation { + workspace.closeFileTabs(after: item) + } + } + } + } +} + +fileprivate extension WorkspaceDocument { + func getTabKeyEquivalent(item: WorkspaceClient.FileItem) -> KeyEquivalent { + for counter in 0..<9 where self.openFileItems.count > counter && + self.openFileItems[counter].fileName == item.fileName { + return KeyEquivalent.init( + Character.init("\(counter + 1)") + ) + } + + return "0" + } }