Skip to content

Commit 37b451b

Browse files
Sync file selection state between tab group and outline view (#1296)
* feat: exclude file extension when renaming filename * feat: add background layer while editing * fix: sync file highlight in outline view with active tab group * feat: select active file when expanding folder * feat: remove duplicate events from publisher * feat: use item from from collection * chore: remove debug print * feat: restore file selection if folder is expanded * chore: update selection after reloading outline datasource * chore: move array extension under util extension folder * Update CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift * Update CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift * Update CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift --------- Co-authored-by: Austin Condiff <austin.condiff@gmail.com>
1 parent 1789fbf commit 37b451b

File tree

6 files changed

+69
-35
lines changed

6 files changed

+69
-35
lines changed

CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable {
1717
@StateObject
1818
var prefs: Settings = .shared
1919

20-
// This is mainly just used to trigger a view update.
21-
@Binding
22-
var selection: CEWorkspaceFile?
23-
2420
typealias NSViewControllerType = ProjectNavigatorViewController
2521

2622
func makeNSViewController(context: Context) -> ProjectNavigatorViewController {
@@ -29,6 +25,7 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable {
2925
controller.iconColor = prefs.preferences.general.fileIconStyle
3026
workspace.workspaceFileManager?.onRefresh = {
3127
controller.outlineView.reloadData()
28+
controller.updateSelection(itemID: workspace.tabManager.activeTabGroup.selected?.id)
3229
}
3330

3431
context.coordinator.controller = controller
@@ -42,7 +39,8 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable {
4239
nsViewController.fileExtensionsVisibility = prefs.preferences.general.fileExtensionsVisibility
4340
nsViewController.shownFileExtensions = prefs.preferences.general.shownFileExtensions
4441
nsViewController.hiddenFileExtensions = prefs.preferences.general.hiddenFileExtensions
45-
nsViewController.updateSelection()
42+
/// if the window becomes active from background, it will restore the selection to outline view.
43+
nsViewController.updateSelection(itemID: workspace.tabManager.activeTabGroup.selected?.id)
4644
return
4745
}
4846

@@ -55,21 +53,24 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable {
5553
self.workspace = workspace
5654
super.init()
5755

58-
listener = workspace.listenerModel.$highlightedFileItem
56+
workspace.listenerModel.$highlightedFileItem
5957
.sink(receiveValue: { [weak self] fileItem in
60-
guard let fileItem else {
61-
return
58+
guard let fileItem else {
59+
return
60+
}
61+
self?.controller?.reveal(fileItem)
62+
})
63+
.store(in: &cancellables)
64+
workspace.tabManager.tabBarItemIdSubject
65+
.sink { [weak self] itemID in
66+
self?.controller?.updateSelection(itemID: itemID)
6267
}
63-
self?.controller?.reveal(fileItem)
64-
})
68+
.store(in: &cancellables)
6569
}
6670

67-
var listener: AnyCancellable?
71+
var cancellables: Set<AnyCancellable> = []
6872
var workspace: WorkspaceDocument
6973
var controller: ProjectNavigatorViewController?
7074

71-
deinit {
72-
listener?.cancel()
73-
}
7475
}
7576
}

CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ final class ProjectNavigatorViewController: NSViewController {
8989
/// Updates the selection of the ``outlineView`` whenever it changes.
9090
///
9191
/// Most importantly when the `id` changes from an external view.
92-
func updateSelection() {
93-
guard let itemID = workspace?.tabManager.activeTabGroup.selected?.id else {
92+
/// - Parameter itemID: The id of the file or folder.
93+
func updateSelection(itemID: String?) {
94+
guard let itemID else {
9495
outlineView.deselectRow(outlineView.selectedRow)
9596
return
9697
}
@@ -282,7 +283,18 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate {
282283
rowHeight // This can be changed to 20 to match Xcode's row height.
283284
}
284285

285-
func outlineViewItemDidExpand(_ notification: Notification) {}
286+
func outlineViewItemDidExpand(_ notification: Notification) {
287+
guard
288+
let id = workspace?.tabManager.activeTabGroup.selected?.id,
289+
let item = content.find(by: .codeEditor(id))
290+
else {
291+
return
292+
}
293+
/// select active file under collapsed folder only if its parent is expanding
294+
if outlineView.isItemExpanded(item.parent) {
295+
updateSelection(itemID: item.id)
296+
}
297+
}
286298

287299
func outlineViewItemDidCollapse(_ notification: Notification) {}
288300

@@ -302,20 +314,13 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate {
302314
/// - id: the id of the item item
303315
/// - collection: the array to search for
304316
private func select(by id: TabBarItemID, from collection: [CEWorkspaceFile]) {
317+
guard let item = collection.find(by: id) else {
318+
return
319+
}
305320
// If the user has set "Reveal file on selection change" to on, we need to reveal the item before
306321
// selecting the row.
307322
if Settings.shared.preferences.general.revealFileOnFocusChange {
308-
if case let .codeEditor(id) = id,
309-
let fileItem = try? workspace?.workspaceFileManager?.getFile(id as CEWorkspaceFile.ID) {
310-
reveal(fileItem)
311-
}
312-
}
313-
314-
guard let item = collection.first(where: { $0.tabID == id }) else {
315-
for item in collection {
316-
select(by: id, from: item.children ?? [])
317-
}
318-
return
323+
reveal(item)
319324
}
320325
let row = outlineView.row(forItem: item)
321326
if row == -1 {

CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@ import SwiftUI
1515
/// When selecting a file it will open in the editor.
1616
///
1717
struct ProjectNavigatorView: View {
18-
19-
@EnvironmentObject var tabManager: TabManager
20-
2118
var body: some View {
22-
ProjectNavigatorOutlineView(selection: $tabManager.activeTabGroup.selected)
19+
ProjectNavigatorOutlineView()
2320
.safeAreaInset(edge: .bottom, spacing: 0) {
2421
ProjectNavigatorToolbarBottom()
2522
}
2623
}
27-
2824
}

CodeEdit/Features/Tabs/Models/TabManager.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
// Created by Wouter Hennen on 03/03/2023.
66
//
77

8+
import Combine
89
import Foundation
9-
import OrderedCollections
1010
import DequeModule
11+
import OrderedCollections
1112

1213
class TabManager: ObservableObject {
1314
/// Collection of all the tabgroups.
@@ -19,6 +20,7 @@ class TabManager: ObservableObject {
1920
var activeTabGroup: TabGroupData {
2021
didSet {
2122
activeTabGroupHistory.prepend { [weak oldValue] in oldValue }
23+
switchToActiveTabGroup()
2224
}
2325
}
2426

@@ -27,11 +29,16 @@ class TabManager: ObservableObject {
2729

2830
var fileDocuments: [CEWorkspaceFile: CodeFileDocument] = [:]
2931

32+
/// notify listeners whenever tab selection changes on the active tab group.
33+
var tabBarItemIdSubject = PassthroughSubject<String?, Never>()
34+
var cancellable: AnyCancellable?
35+
3036
init() {
3137
let tab = TabGroupData()
3238
self.activeTabGroup = tab
3339
self.activeTabGroupHistory.prepend { [weak tab] in tab }
3440
self.tabGroups = .horizontal(.init(.horizontal, tabgroups: [.one(tab)]))
41+
switchToActiveTabGroup()
3542
}
3643

3744
/// Flattens the splitviews.
@@ -49,4 +56,14 @@ class TabManager: ObservableObject {
4956
let tabgroup = tabgroup ?? activeTabGroup
5057
tabgroup.openTab(item: item)
5158
}
59+
60+
/// bind active tap group to listen to file selection changes.
61+
func switchToActiveTabGroup() {
62+
cancellable?.cancel()
63+
cancellable = nil
64+
cancellable = activeTabGroup.$selected
65+
.sink { [weak self] tab in
66+
self?.tabBarItemIdSubject.send(tab?.id)
67+
}
68+
}
5269
}

CodeEdit/Utils/Extensions/Array/Array+CEWorkspaceFile.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ extension Array where Element == CEWorkspaceFile {
2727
}
2828
}
2929

30+
/// Search for the `CEWorkspaceFile` element that matches the specified `tabID`.
31+
/// - Parameter tabID: A `tabID` to search for.
32+
/// - Returns: The `CEWorkspaceFile` element with a matching `tabID` if available.
33+
func find(by tabID: TabBarItemID) -> CEWorkspaceFile? {
34+
guard let item = first(where: { $0.tabID == tabID }) else {
35+
for element in self {
36+
if let item = element.children?.find(by: tabID) {
37+
return item
38+
}
39+
}
40+
return nil
41+
}
42+
return item
43+
}
3044
}
3145

3246
extension Array where Element: Hashable {

CodeEdit/WorkspaceView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ struct WorkspaceView: View {
6565
.edgesIgnoringSafeArea(.top)
6666
.frame(maxWidth: .infinity, maxHeight: .infinity)
6767
.onChange(of: focusedEditor) { newValue in
68-
if let newValue {
68+
/// update active tab group only if the new one is not the same with it.
69+
if let newValue, tabManager.activeTabGroup != newValue {
6970
tabManager.activeTabGroup = newValue
7071
}
7172
}

0 commit comments

Comments
 (0)