diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift index 1151e6484..57421c563 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift @@ -17,10 +17,6 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { @StateObject var prefs: Settings = .shared - // This is mainly just used to trigger a view update. - @Binding - var selection: CEWorkspaceFile? - typealias NSViewControllerType = ProjectNavigatorViewController func makeNSViewController(context: Context) -> ProjectNavigatorViewController { @@ -29,6 +25,7 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { controller.iconColor = prefs.preferences.general.fileIconStyle workspace.workspaceFileManager?.onRefresh = { controller.outlineView.reloadData() + controller.updateSelection(itemID: workspace.tabManager.activeTabGroup.selected?.id) } context.coordinator.controller = controller @@ -42,7 +39,8 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { nsViewController.fileExtensionsVisibility = prefs.preferences.general.fileExtensionsVisibility nsViewController.shownFileExtensions = prefs.preferences.general.shownFileExtensions nsViewController.hiddenFileExtensions = prefs.preferences.general.hiddenFileExtensions - nsViewController.updateSelection() + /// if the window becomes active from background, it will restore the selection to outline view. + nsViewController.updateSelection(itemID: workspace.tabManager.activeTabGroup.selected?.id) return } @@ -55,21 +53,24 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { self.workspace = workspace super.init() - listener = workspace.listenerModel.$highlightedFileItem + workspace.listenerModel.$highlightedFileItem .sink(receiveValue: { [weak self] fileItem in - guard let fileItem else { - return + guard let fileItem else { + return + } + self?.controller?.reveal(fileItem) + }) + .store(in: &cancellables) + workspace.tabManager.tabBarItemIdSubject + .sink { [weak self] itemID in + self?.controller?.updateSelection(itemID: itemID) } - self?.controller?.reveal(fileItem) - }) + .store(in: &cancellables) } - var listener: AnyCancellable? + var cancellables: Set = [] var workspace: WorkspaceDocument var controller: ProjectNavigatorViewController? - deinit { - listener?.cancel() - } } } diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index f71743036..a39d403ea 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -89,8 +89,9 @@ final class ProjectNavigatorViewController: NSViewController { /// Updates the selection of the ``outlineView`` whenever it changes. /// /// Most importantly when the `id` changes from an external view. - func updateSelection() { - guard let itemID = workspace?.tabManager.activeTabGroup.selected?.id else { + /// - Parameter itemID: The id of the file or folder. + func updateSelection(itemID: String?) { + guard let itemID else { outlineView.deselectRow(outlineView.selectedRow) return } @@ -282,7 +283,18 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { rowHeight // This can be changed to 20 to match Xcode's row height. } - func outlineViewItemDidExpand(_ notification: Notification) {} + func outlineViewItemDidExpand(_ notification: Notification) { + guard + let id = workspace?.tabManager.activeTabGroup.selected?.id, + let item = content.find(by: .codeEditor(id)) + else { + return + } + /// select active file under collapsed folder only if its parent is expanding + if outlineView.isItemExpanded(item.parent) { + updateSelection(itemID: item.id) + } + } func outlineViewItemDidCollapse(_ notification: Notification) {} @@ -302,20 +314,13 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { /// - id: the id of the item item /// - collection: the array to search for private func select(by id: TabBarItemID, from collection: [CEWorkspaceFile]) { + guard let item = collection.find(by: id) else { + return + } // If the user has set "Reveal file on selection change" to on, we need to reveal the item before // selecting the row. if Settings.shared.preferences.general.revealFileOnFocusChange { - if case let .codeEditor(id) = id, - let fileItem = try? workspace?.workspaceFileManager?.getFile(id as CEWorkspaceFile.ID) { - reveal(fileItem) - } - } - - guard let item = collection.first(where: { $0.tabID == id }) else { - for item in collection { - select(by: id, from: item.children ?? []) - } - return + reveal(item) } let row = outlineView.row(forItem: item) if row == -1 { diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift index 3e7fc61eb..23536c06e 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift @@ -15,14 +15,10 @@ import SwiftUI /// When selecting a file it will open in the editor. /// struct ProjectNavigatorView: View { - - @EnvironmentObject var tabManager: TabManager - var body: some View { - ProjectNavigatorOutlineView(selection: $tabManager.activeTabGroup.selected) + ProjectNavigatorOutlineView() .safeAreaInset(edge: .bottom, spacing: 0) { ProjectNavigatorToolbarBottom() } } - } diff --git a/CodeEdit/Features/Tabs/Models/TabManager.swift b/CodeEdit/Features/Tabs/Models/TabManager.swift index 964b4ed6e..6e474bb1c 100644 --- a/CodeEdit/Features/Tabs/Models/TabManager.swift +++ b/CodeEdit/Features/Tabs/Models/TabManager.swift @@ -5,9 +5,10 @@ // Created by Wouter Hennen on 03/03/2023. // +import Combine import Foundation -import OrderedCollections import DequeModule +import OrderedCollections class TabManager: ObservableObject { /// Collection of all the tabgroups. @@ -19,6 +20,7 @@ class TabManager: ObservableObject { var activeTabGroup: TabGroupData { didSet { activeTabGroupHistory.prepend { [weak oldValue] in oldValue } + switchToActiveTabGroup() } } @@ -27,11 +29,16 @@ class TabManager: ObservableObject { var fileDocuments: [CEWorkspaceFile: CodeFileDocument] = [:] + /// notify listeners whenever tab selection changes on the active tab group. + var tabBarItemIdSubject = PassthroughSubject() + var cancellable: AnyCancellable? + init() { let tab = TabGroupData() self.activeTabGroup = tab self.activeTabGroupHistory.prepend { [weak tab] in tab } self.tabGroups = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) + switchToActiveTabGroup() } /// Flattens the splitviews. @@ -49,4 +56,14 @@ class TabManager: ObservableObject { let tabgroup = tabgroup ?? activeTabGroup tabgroup.openTab(item: item) } + + /// bind active tap group to listen to file selection changes. + func switchToActiveTabGroup() { + cancellable?.cancel() + cancellable = nil + cancellable = activeTabGroup.$selected + .sink { [weak self] tab in + self?.tabBarItemIdSubject.send(tab?.id) + } + } } diff --git a/CodeEdit/Utils/Extensions/Array/Array+CEWorkspaceFile.swift b/CodeEdit/Utils/Extensions/Array/Array+CEWorkspaceFile.swift index 5bd9a2e20..af85343d3 100644 --- a/CodeEdit/Utils/Extensions/Array/Array+CEWorkspaceFile.swift +++ b/CodeEdit/Utils/Extensions/Array/Array+CEWorkspaceFile.swift @@ -27,6 +27,20 @@ extension Array where Element == CEWorkspaceFile { } } + /// Search for the `CEWorkspaceFile` element that matches the specified `tabID`. + /// - Parameter tabID: A `tabID` to search for. + /// - Returns: The `CEWorkspaceFile` element with a matching `tabID` if available. + func find(by tabID: TabBarItemID) -> CEWorkspaceFile? { + guard let item = first(where: { $0.tabID == tabID }) else { + for element in self { + if let item = element.children?.find(by: tabID) { + return item + } + } + return nil + } + return item + } } extension Array where Element: Hashable { diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 7d55ca632..81fe951dc 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -59,7 +59,8 @@ struct WorkspaceView: View { .environmentObject(workspace.statusBarModel) .frame(maxWidth: .infinity, maxHeight: .infinity) .onChange(of: focusedEditor) { newValue in - if let newValue { + /// update active tab group only if the new one is not the same with it. + if let newValue, tabManager.activeTabGroup != newValue { tabManager.activeTabGroup = newValue } }