Skip to content

Commit b3f8c36

Browse files
committed
fix(folder-compare): restore expanded folders after refresh operation
Closes #45
1 parent fa31604 commit b3f8c36

6 files changed

Lines changed: 298 additions & 43 deletions

File tree

Sources/Features/FoldersCompare/Components/FoldersOutlineView/FoldersOutlineView+VisibleItem.swift

Lines changed: 134 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
// Copyright (c) 2021 visualdiffer.com
77
//
88

9-
@objc
109
extension FoldersOutlineView {
1110
func getSelectedVisibleItems(_ includesSelected: Bool) -> [VisibleItem] {
1211
var arr = [VisibleItem]()
1312

14-
// At index 0 there is the last selected row
13+
// at index 0 there is the last selected row
1514
if selectedRow >= 0 {
1615
if let vi = item(atRow: selectedRow) as? VisibleItem {
1716
arr.append(vi)
@@ -71,48 +70,26 @@ extension FoldersOutlineView {
7170
}
7271

7372
func restoreSelectionAndFocusPosition(_ selectedVisibleItems: [VisibleItem]) {
74-
if selectedVisibleItems.isEmpty {
73+
let paths = selectedVisibleItems.compactMap { $0.item.path ?? $0.item.linkedItem?.path }
74+
guard !paths.isEmpty else {
7575
return
7676
}
77-
var indexes = IndexSet()
78-
let focusVI = selectedVisibleItems[0]
7977

80-
// start from 1 to skip focus item
81-
for vi in selectedVisibleItems.dropFirst() {
82-
let row = row(forItem: vi)
83-
if row >= 0 {
84-
indexes.insert(row)
85-
}
86-
}
87-
88-
let focusItem = focusVI.item
89-
var focusRow = row(forItem: focusVI)
78+
var (indexes, focusRow) = indexSet(forPaths: paths)
9079

9180
if focusRow < 0 {
92-
let count = numberOfRows
93-
for row in 0 ..< count {
94-
guard let vi = item(atRow: row) as? VisibleItem else {
95-
continue
96-
}
97-
98-
let res = URL.compare(path: vi.item.toURL(), with: focusItem.toURL())
99-
100-
if res == .orderedSame || res == .orderedDescending {
101-
focusRow = row
102-
if res == .orderedDescending, row > 0 {
103-
focusRow -= 1
104-
}
105-
indexes.insert(focusRow)
106-
break
107-
}
81+
focusRow = findNearest(to: URL(filePath: paths[0]))
82+
if focusRow >= 0 {
83+
indexes.insert(focusRow)
10884
}
109-
} else {
110-
indexes.insert(focusRow)
11185
}
11286

113-
scrollRowToVisible(focusRow)
114-
selectRowIndexes(indexes, byExtendingSelection: false)
115-
linkedView?.selectRowIndexes(indexes, byExtendingSelection: false)
87+
guard !indexes.isEmpty else {
88+
return
89+
}
90+
91+
select(rows: indexes, selectLinked: true)
92+
scrollTo(row: focusRow, center: true)
11693
}
11794

11895
func expandParents(of child: VisibleItem) {
@@ -131,4 +108,125 @@ extension FoldersOutlineView {
131108
expandItem(parent)
132109
}
133110
}
111+
112+
func expandedFolderPaths() -> Set<String> {
113+
var paths = Set<String>()
114+
115+
for row in 0 ..< numberOfRows {
116+
guard let folder = item(atRow: row) as? VisibleItem,
117+
folder.item.isFolder,
118+
isItemExpanded(folder),
119+
let path = folder.item.path ?? folder.item.linkedItem?.path else {
120+
continue
121+
}
122+
123+
paths.insert(path)
124+
}
125+
126+
return paths
127+
}
128+
129+
func expandFolders(with paths: Set<String>, from folder: VisibleItem? = nil) {
130+
if paths.isEmpty {
131+
return
132+
}
133+
134+
if let folder {
135+
expandFolders(with: paths, folder: folder)
136+
return
137+
}
138+
139+
guard let dataSource else {
140+
return
141+
}
142+
143+
let count = dataSource.outlineView?(self, numberOfChildrenOfItem: nil) ?? 0
144+
145+
for index in 0 ..< count {
146+
guard let folder = dataSource.outlineView?(self, child: index, ofItem: nil) as? VisibleItem else {
147+
continue
148+
}
149+
150+
expandFolders(with: paths, folder: folder)
151+
}
152+
}
153+
154+
private func expandFolders(with paths: Set<String>, folder: VisibleItem) {
155+
if folder.item.isFolder,
156+
let path = folder.item.path ?? folder.item.linkedItem?.path,
157+
paths.contains(path),
158+
row(forItem: folder) >= 0 {
159+
expandItem(folder)
160+
}
161+
162+
for child in folder.children {
163+
expandFolders(with: paths, folder: child)
164+
}
165+
}
166+
167+
func restoreSelection(paths: [String]) {
168+
guard !paths.isEmpty else {
169+
return
170+
}
171+
172+
let (indexes, focusRow) = indexSet(forPaths: paths)
173+
guard !indexes.isEmpty else {
174+
return
175+
}
176+
177+
select(rows: indexes)
178+
scrollTo(row: focusRow, center: true)
179+
}
180+
181+
private func indexSet(
182+
forPaths paths: [String]
183+
) -> (indexes: IndexSet, focusRow: Int) {
184+
let focusPath = paths[0]
185+
var indexes = IndexSet()
186+
var focusRow = -1
187+
188+
for row in 0 ..< numberOfRows {
189+
guard let vi = item(atRow: row) as? VisibleItem,
190+
let viPath = vi.item.path ?? vi.item.linkedItem?.path,
191+
paths.contains(viPath) else {
192+
continue
193+
}
194+
195+
indexes.insert(row)
196+
if viPath == focusPath {
197+
focusRow = row
198+
}
199+
}
200+
201+
return (indexes, focusRow)
202+
}
203+
204+
func findNearest(to url: URL) -> Int {
205+
for row in 0 ..< numberOfRows {
206+
guard let vi = item(atRow: row) as? VisibleItem else {
207+
continue
208+
}
209+
210+
let res = URL.compare(path: vi.item.toURL(), with: url)
211+
if res == .orderedSame || res == .orderedDescending {
212+
return res == .orderedDescending && row > 0 ? row - 1 : row
213+
}
214+
}
215+
216+
return -1
217+
}
218+
219+
func captureSelectionForRestore(preserveExistingWhenEmpty: Bool) {
220+
let selectedPaths = getSelectedVisibleItems(true).compactMap {
221+
$0.item.path ?? $0.item.linkedItem?.path
222+
}
223+
if preserveExistingWhenEmpty, selectedPaths.isEmpty {
224+
return
225+
}
226+
selectionRestorePaths = selectedPaths
227+
}
228+
229+
func restoreCapturedSelection() {
230+
restoreSelection(paths: selectionRestorePaths)
231+
}
134232
}

Sources/Features/FoldersCompare/Components/FoldersOutlineView/FoldersOutlineView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ protocol FoldersOutlineViewDelegate: NSOutlineViewDelegate {
1717

1818
public class FoldersOutlineView: NSOutlineView, @preconcurrency DisplayPositionable, ViewLinkable {
1919
private var _selectionInfo: FolderSelectionInfo?
20+
var selectionRestorePaths = [String]()
2021

2122
var selectionInfo: FolderSelectionInfo {
2223
if let selectionInfo = _selectionInfo {

Sources/Features/FoldersCompare/Controller/FoldersWindowController+Comparison.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ public extension FoldersWindowController {
3838
leftSecureURL = SecureBookmark.shared.secure(fromBookmark: leftPath, startSecured: true)
3939
rightSecureURL = SecureBookmark.shared.secure(fromBookmark: rightPath, startSecured: true)
4040

41+
if refreshInfo.expandAllFolders {
42+
expandedFolderPaths.removeAll()
43+
} else {
44+
expandedFolderPaths = leftView.expandedFolderPaths()
45+
}
46+
47+
leftView.captureSelectionForRestore(preserveExistingWhenEmpty: false)
48+
rightView.captureSelectionForRestore(preserveExistingWhenEmpty: false)
49+
4150
if refreshInfo.refreshFolders {
4251
leftItemOriginal = nil
4352
rightItemOriginal = nil
@@ -197,6 +206,8 @@ public extension FoldersWindowController {
197206

198207
if folderReader.refreshInfo.expandAllFolders {
199208
leftView.expandItem(nil, expandChildren: true)
209+
} else {
210+
restoreExpandedFolders()
200211
}
201212
}
202213

@@ -219,18 +230,19 @@ public extension FoldersWindowController {
219230
leftView.linkedView?.reloadItem(item.visibleItem?.linkedItem, reloadChildren: true)
220231
if folderReader.refreshInfo.expandAllFolders {
221232
leftView.expandItem(item.visibleItem, expandChildren: true)
233+
} else {
234+
restoreExpandedFolders(from: item.visibleItem)
222235
}
223236
}
224237

225238
func didComparisonCompleted(_ elapsedTimeText: String) {
226-
let leftSelection = leftView.getSelectedVisibleItems(true)
227-
let rightSelection = rightView.getSelectedVisibleItems(true)
228-
229239
leftView.reloadData()
230240
rightView.reloadData()
231241

232-
leftView.select(visibleItems: leftSelection)
233-
rightView.select(visibleItems: rightSelection)
242+
restoreExpandedFolders()
243+
244+
leftView.restoreCapturedSelection()
245+
rightView.restoreCapturedSelection()
234246

235247
// selectFirstRow(leftSelection: leftSelection,
236248
// rightSelection: rightSelection)
@@ -291,6 +303,14 @@ public extension FoldersWindowController {
291303
rightView.selectRowIndexes(IndexSet(integer: firstVisibleRow), byExtendingSelection: false)
292304
}
293305
}
306+
307+
private func restoreExpandedFolders(from item: VisibleItem? = nil) {
308+
if expandedFolderPaths.isEmpty {
309+
return
310+
}
311+
312+
leftView.expandFolders(with: expandedFolderPaths, from: item)
313+
}
294314
}
295315

296316
extension FoldersWindowController: ItemComparatorDelegate {

Sources/Features/FoldersCompare/Controller/FoldersWindowController+FoldersOutlineView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ extension FoldersWindowController: NSOutlineViewDelegate,
318318
}
319319

320320
public func selectionChanged(in view: FoldersOutlineView) {
321+
if running {
322+
view.captureSelectionForRestore(preserveExistingWhenEmpty: true)
323+
}
324+
321325
// we can't use outlineViewSelectionDidChange because it is called
322326
// before the notification declared into outline subclass
323-
// So we use our notification sent from outline subclass
327+
// so we use our notification sent from outline subclass
324328
updateBottomBar(view)
325329
updateStatusBar()
326330
previewPanel?.reloadData()

Sources/Features/FoldersCompare/Controller/FoldersWindowController.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class FoldersWindowController: NSWindowController,
3535

3636
lazy var lastUsedView = leftPanelView.treeView
3737

38+
var expandedFolderPaths = Set<String>()
39+
3840
// comparatorPopUpButtonCell uses the tag property to select the item, but
3941
// sessionDiff.comparatorFlags bitmask should not match the tag value, so
4042
// sessionDiff.comparatorFlags is bit-masked with comparatorMethod in selection action methods

0 commit comments

Comments
 (0)