Skip to content

Commit c3ad843

Browse files
committed
Filter the directory change stream
1 parent ebbcf03 commit c3ad843

7 files changed

Lines changed: 94 additions & 18 deletions

File tree

CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+DirectoryEvents.swift

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,35 @@ import Foundation
99

1010
/// This extension handles the file system events triggered by changes in the root folder.
1111
extension CEWorkspaceFileManager {
12+
struct ResolvedFSEvent: Hashable {
13+
let file: CEWorkspaceFile
14+
let eventType: FSEvent
15+
}
16+
1217
/// Called by `fsEventStream` when an event occurs.
1318
///
1419
/// This method may be called on a background thread, but all work done by this function will be queued on the main
1520
/// thread.
1621
/// - Parameter events: An array of events that occurred.
1722
func fileSystemEventReceived(events: [DirectoryEventStream.Event]) {
1823
DispatchQueue.main.async {
19-
var files: Set<CEWorkspaceFile> = []
24+
var files: Set<ResolvedFSEvent> = []
2025
for event in events {
2126
// Event returns file/folder that was changed, but in tree we need to update it's parent
22-
guard let parentUrl = URL(string: event.path, relativeTo: self.folderUrl)?.deletingLastPathComponent(),
23-
let parentFileItem = self.flattenedFileItems[parentUrl.path] else {
27+
guard let eventFileUrl = URL(string: event.path, relativeTo: self.folderUrl) else {
28+
continue
29+
}
30+
let parentUrl = eventFileUrl.deletingLastPathComponent()
31+
guard let parentFileItem = self.flattenedFileItems[parentUrl.path] else {
2432
continue
2533
}
2634

2735
switch event.eventType {
2836
case .changeInDirectory, .itemChangedOwner, .itemModified:
29-
// Can be ignored for now, these I think not related to tree changes
3037
continue
38+
// if let fileItem = self.flattenedFileItems[eventFileUrl.path] {
39+
// files.insert(ResolvedFSEvent(file: fileItem, eventType: event.eventType))
40+
// }
3141
case .rootChanged:
3242
// TODO: #1880 - Handle workspace root changing.
3343
continue
@@ -38,7 +48,7 @@ extension CEWorkspaceFileManager {
3848
// swiftlint:disable:next line_length
3949
self.logger.error("Failed to rebuild files for event: \(event.eventType.rawValue), path: \(event.path, privacy: .sensitive)")
4050
}
41-
files.insert(parentFileItem)
51+
files.insert(ResolvedFSEvent(file: parentFileItem, eventType: event.eventType))
4252
}
4353
}
4454
if !files.isEmpty {
@@ -182,13 +192,17 @@ extension CEWorkspaceFileManager {
182192
}
183193

184194
/// Notify observers that an update occurred in the watched files.
185-
func notifyObservers(updatedItems: Set<CEWorkspaceFile>) {
195+
func notifyObservers(updatedItems: Set<ResolvedFSEvent>) {
186196
observers.allObjects.reversed().forEach { delegate in
187197
guard let delegate = delegate as? CEWorkspaceFileManagerObserver else {
188198
observers.remove(delegate)
189199
return
190200
}
191-
delegate.fileManagerUpdated(updatedItems: updatedItems)
201+
let eventsFilter = delegate.fileManagerEventsFilter()
202+
let events = updatedItems.compactMap({ eventsFilter.contains($0.eventType) ? $0.file : nil })
203+
if !events.isEmpty {
204+
delegate.fileManagerUpdated(updatedItems: Set(events))
205+
}
192206
}
193207
}
194208

CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ extension CEWorkspaceFileManager {
3838
)
3939

4040
try rebuildFiles(fromItem: file.isFolder ? file : file.parent ?? file)
41-
notifyObservers(updatedItems: [file.isFolder ? file : file.parent ?? file])
41+
notifyObservers(updatedItems: [ResolvedFSEvent(
42+
file: file.isFolder ? file : file.parent ?? file,
43+
eventType: .itemCreated
44+
)])
4245

4346
guard let newFolder = getFile(folderUrl.path(), createIfNotFound: true) else {
4447
throw FileManagerError.fileNotFound
@@ -103,7 +106,10 @@ extension CEWorkspaceFileManager {
103106
}
104107

105108
try rebuildFiles(fromItem: file.isFolder ? file : file.parent ?? file)
106-
notifyObservers(updatedItems: [file.isFolder ? file : file.parent ?? file])
109+
notifyObservers(updatedItems: [ResolvedFSEvent(
110+
file: file.isFolder ? file : file.parent ?? file,
111+
eventType: .itemCreated
112+
)])
107113

108114
// Create if not found here because this should be indexed if we're creating it.
109115
// It's not often a user makes a file and then doesn't use it.
@@ -284,13 +290,13 @@ extension CEWorkspaceFileManager {
284290

285291
if let parent = file.parent {
286292
try rebuildFiles(fromItem: parent)
287-
notifyObservers(updatedItems: [parent])
293+
notifyObservers(updatedItems: [ResolvedFSEvent(file: parent, eventType: .changeInDirectory)])
288294
}
289295

290296
// If we have the new parent file, let's rebuild that directory too
291297
if let newFileParent = getFile(newLocation.deletingLastPathComponent().path) {
292298
try rebuildFiles(fromItem: newFileParent)
293-
notifyObservers(updatedItems: [newFileParent])
299+
notifyObservers(updatedItems: [ResolvedFSEvent(file: newFileParent, eventType: .changeInDirectory)])
294300
}
295301

296302
return getFile(newLocation.absoluteURL.path)

CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@ import AppKit
1111
import OSLog
1212

1313
protocol CEWorkspaceFileManagerObserver: AnyObject {
14+
func fileManagerEventsFilter() -> Set<FSEvent>
1415
func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>)
1516
}
1617

18+
extension CEWorkspaceFileManagerObserver {
19+
func fileManagerEventsFilter() -> Set<FSEvent> {
20+
FSEvent.all()
21+
}
22+
}
23+
1724
/// This class is used to load, modify, and listen to files on a user's machine.
1825
///
1926
/// The workspace file manager provides an API for:

CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ enum FSEvent: String {
1616
case itemModified
1717
case itemRemoved
1818
case itemRenamed
19+
20+
static func all() -> Set<FSEvent> {
21+
[
22+
.changeInDirectory,
23+
.rootChanged,
24+
.itemChangedOwner,
25+
.itemCreated,
26+
.itemCloned,
27+
.itemModified,
28+
.itemRemoved,
29+
.itemRenamed
30+
]
31+
}
1932
}
2033

2134
/// Creates a stream of events using the File System Events API.

CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
164164
)
165165
}
166166

167+
workspaceFileManager?.addObserver(undoRegistration)
167168
editorManager?.restoreFromState(self)
168169
utilityAreaModel?.restoreFromState(self)
169170
}

CodeEdit/Features/Editor/Models/Restoration/UndoManagerRegistration.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,55 @@ import CodeEditTextView
1616
/// - `CodeFileDocument` is released once there are no editors viewing it.
1717
/// Undo stacks need to be retained for the duration of a workspace session, enduring editor closes..
1818
final class UndoManagerRegistration: ObservableObject {
19-
private var managerMap: [CEWorkspaceFile.ID: CEUndoManager] = [:]
19+
private var managerMap: [String: CEUndoManager] = [:]
2020

2121
init() { }
2222

2323
/// Find or create a new undo manager.
2424
/// - Parameter file: The file to create for.
2525
/// - Returns: The undo manager for the given file.
2626
func manager(forFile file: CEWorkspaceFile) -> CEUndoManager {
27-
if let manager = managerMap[file.id] {
27+
manager(forFile: file.url)
28+
}
29+
30+
/// Find or create a new undo manager.
31+
/// - Parameter path: The path of the file to create for.
32+
/// - Returns: The undo manager for the given file.
33+
func manager(forFile path: URL) -> CEUndoManager {
34+
if let manager = managerMap[path.absolutePath] {
2835
return manager
2936
} else {
3037
let newManager = CEUndoManager()
31-
managerMap[file.id] = newManager
38+
managerMap[path.absolutePath] = newManager
3239
return newManager
3340
}
3441
}
42+
43+
/// Find or create a new undo manager.
44+
/// - Parameter path: The path of the file to create for.
45+
/// - Returns: The undo manager for the given file.
46+
func managerIfExists(forFile path: URL) -> CEUndoManager? {
47+
managerMap[path.absolutePath]
48+
}
49+
}
50+
51+
extension UndoManagerRegistration: CEWorkspaceFileManagerObserver {
52+
func fileManagerEventsFilter() -> Set<FSEvent> {
53+
[.itemModified, .itemRemoved]
54+
}
55+
56+
/// Managers need to be cleared when the following is true:
57+
/// - The file is not open in any editors
58+
/// - The file is updated externally
59+
///
60+
/// To handle this?
61+
/// - When we receive a file update, if the file is not open in any editors we clear the undo stack
62+
func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>) {
63+
for file in updatedItems where file.fileDocument == nil {
64+
if managerMap[file.url.absolutePath] != nil {
65+
print("Clearing undo stack for file \(file.fileName())")
66+
}
67+
managerMap.removeValue(forKey: file.url.absolutePath)
68+
}
69+
}
3570
}

CodeEdit/Features/SourceControl/SourceControlManager+GitClient.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ extension SourceControlManager {
135135
return
136136
}
137137

138-
var updatedStatusFor: Set<CEWorkspaceFile> = []
138+
var updatedStatusFor: Set<CEWorkspaceFileManager.ResolvedFSEvent> = []
139139
// Refresh status of file manager files
140140
for changedFile in changedFiles {
141141
guard let file = fileManager.getFile(changedFile.ceFileKey) else {
@@ -144,13 +144,13 @@ extension SourceControlManager {
144144
if file.gitStatus != changedFile.anyStatus() {
145145
file.gitStatus = changedFile.anyStatus()
146146
}
147-
updatedStatusFor.insert(file)
147+
updatedStatusFor.insert(.init(file: file, eventType: .itemModified))
148148
}
149149

150150
for (_, file) in fileManager.flattenedFileItems
151-
where !updatedStatusFor.contains(file) && file.gitStatus != nil {
151+
where !updatedStatusFor.contains(.init(file: file, eventType: .itemModified)) && file.gitStatus != nil {
152152
file.gitStatus = nil
153-
updatedStatusFor.insert(file)
153+
updatedStatusFor.insert(.init(file: file, eventType: .itemModified))
154154
}
155155

156156
if updatedStatusFor.isEmpty {

0 commit comments

Comments
 (0)