Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync file selection state between tab group and outline view #1296

Merged
merged 14 commits into from
Jun 14, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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<AnyCancellable> = []
var workspace: WorkspaceDocument
var controller: ProjectNavigatorViewController?

deinit {
listener?.cancel()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {}

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

}
19 changes: 18 additions & 1 deletion CodeEdit/Features/Tabs/Models/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -19,6 +20,7 @@ class TabManager: ObservableObject {
var activeTabGroup: TabGroupData {
didSet {
activeTabGroupHistory.prepend { [weak oldValue] in oldValue }
switchToActiveTabGroup()
}
}

Expand All @@ -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<String?, Never>()
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.
Expand All @@ -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)
}
}
}
14 changes: 14 additions & 0 deletions CodeEdit/Utils/Extensions/Array/Array+CEWorkspaceFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion CodeEdit/WorkspaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down