diff --git a/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift index 1b96e4fca..a47fdbba8 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift @@ -16,6 +16,8 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { @Published var sortFoldersOnTop: Bool = true /// A string used to filter the displayed files and folders in the project navigator area based on user input. @Published var navigatorFilter: String = "" + /// Whether the workspace only shows files with changes. + @Published var sourceControlFilter = false private var workspaceState: [String: Any] { get { diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift index 21cf99dd0..529280d09 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift @@ -67,6 +67,10 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { .throttle(for: 0.1, scheduler: RunLoop.main, latest: true) .sink { [weak self] _ in self?.controller?.handleFilterChange() } .store(in: &cancellables) + workspace.$sourceControlFilter + .throttle(for: 0.1, scheduler: RunLoop.main, latest: true) + .sink { [weak self] _ in self?.controller?.handleFilterChange() } + .store(in: &cancellables) } var cancellables: Set = [] diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift index 9cd4f4e3d..727899663 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift @@ -15,8 +15,11 @@ extension ProjectNavigatorViewController: NSOutlineViewDataSource { } if let children = workspace?.workspaceFileManager?.childrenOfFile(item) { - if let filter = workspace?.navigatorFilter, !filter.isEmpty { - let filteredChildren = children.filter { fileSearchMatches(filter, for: $0) } + if let filter = workspace?.navigatorFilter, let sourceControlFilter = workspace?.sourceControlFilter, + !filter.isEmpty || sourceControlFilter { + let filteredChildren = children.filter { + fileSearchMatches(filter, for: $0, sourceControlFilter: sourceControlFilter) + } filteredContentChildren[item] = filteredChildren return filteredChildren } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index f76f07efc..ea9e2ee25 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -64,6 +64,10 @@ final class ProjectNavigatorViewController: NSViewController { /// to open the file a second time. var shouldSendSelectionUpdate: Bool = true + var filterIsEmpty: Bool { + workspace?.navigatorFilter.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true + } + /// Setup the ``scrollView`` and ``outlineView`` override func loadView() { self.scrollView = NSScrollView() @@ -193,13 +197,13 @@ final class ProjectNavigatorViewController: NSViewController { guard let workspace else { return } /// If the filter is empty, show all items and restore the expanded state. - if workspace.navigatorFilter.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - restoreExpandedState() - outlineView.autosaveExpandedItems = true - } else { + if workspace.sourceControlFilter || !filterIsEmpty { outlineView.autosaveExpandedItems = false /// Expand all items for search. outlineView.expandItem(outlineView.item(atRow: 0), expandChildren: true) + } else { + restoreExpandedState() + outlineView.autosaveExpandedItems = true } if let root = content.first(where: { $0.isRoot }), let children = filteredContentChildren[root] { @@ -213,16 +217,24 @@ final class ProjectNavigatorViewController: NSViewController { } /// Checks if the given filter matches the name of the item or any of its children. - func fileSearchMatches(_ filter: String, for item: CEWorkspaceFile) -> Bool { - guard !filter.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return true } + func fileSearchMatches(_ filter: String, for item: CEWorkspaceFile, sourceControlFilter: Bool) -> Bool { + guard !filterIsEmpty || sourceControlFilter else { + return true + } - if item.name.localizedLowercase.contains(filter.localizedLowercase) { + if sourceControlFilter { + if item.gitStatus != nil && item.gitStatus != GitStatus.none && + (filterIsEmpty || item.name.localizedCaseInsensitiveContains(filter)) { + saveAllContentChildren(for: item) + return true + } + } else if item.name.localizedCaseInsensitiveContains(filter) { saveAllContentChildren(for: item) return true } if let children = workspace?.workspaceFileManager?.childrenOfFile(item) { - return children.contains { fileSearchMatches(filter, for: $0) } + return children.contains { fileSearchMatches(filter, for: $0, sourceControlFilter: sourceControlFilter) } } return false diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift index e2977bcac..c4a7758eb 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift @@ -18,7 +18,6 @@ struct ProjectNavigatorToolbarBottom: View { @EnvironmentObject var editorManager: EditorManager @State var recentsFilter: Bool = false - @State var sourceControlFilter: Bool = false var body: some View { HStack(spacing: 5) { @@ -48,7 +47,7 @@ struct ProjectNavigatorToolbarBottom: View { Image(systemName: "clock") } .help("Show only recent files") - Toggle(isOn: $sourceControlFilter) { + Toggle(isOn: $workspace.sourceControlFilter) { Image(systemName: "plusminus.circle") } .help("Show only files with source-control status") @@ -57,7 +56,7 @@ struct ProjectNavigatorToolbarBottom: View { .padding(.trailing, 2.5) }, clearable: true, - hasValue: !workspace.navigatorFilter.isEmpty || recentsFilter || sourceControlFilter + hasValue: !workspace.navigatorFilter.isEmpty || recentsFilter || workspace.sourceControlFilter ) } .padding(.horizontal, 5)