Skip to content

Commit

Permalink
Filter scoping and file path status bar view (#18)
Browse files Browse the repository at this point in the history
* scope selection to search current dump or all dumps
* filepath statusbar on code editor to identify origin
* hover text on file row to identify origin when searching all
  • Loading branch information
drewvolz authored Feb 26, 2024
1 parent 4341cd1 commit 78eb269
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 16 deletions.
8 changes: 8 additions & 0 deletions ClassDumper.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
3A2F8F932A46B3D700D9FB26 /* Files in Frameworks */ = {isa = PBXBuildFile; productRef = 3A2F8F922A46B3D700D9FB26 /* Files */; };
3A2F8F952A4D4C5A00D9FB26 /* FolderRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F8F942A4D4C5A00D9FB26 /* FolderRowView.swift */; };
3A5863712B84564F00487B13 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5863702B84564F00487B13 /* URL.swift */; };
3A5863F02B8C069C00487B13 /* FilterScopeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5863EF2B8C069C00487B13 /* FilterScopeView.swift */; };
3A5863F22B8C14A400487B13 /* FilePathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5863F12B8C14A400487B13 /* FilePathView.swift */; };
3A67D7642A511F7600516FF2 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67D7632A511F7600516FF2 /* App.swift */; };
3A67D7662A511FFA00516FF2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67D7652A511FFA00516FF2 /* AppDelegate.swift */; };
3A67D7682A51566B00516FF2 /* String+ConsoleOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67D7672A51566B00516FF2 /* String+ConsoleOutput.swift */; };
Expand Down Expand Up @@ -91,6 +93,8 @@
3A2F8F912A46B3C500D9FB26 /* Files */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Files; path = Packages/Files; sourceTree = "<group>"; };
3A2F8F942A4D4C5A00D9FB26 /* FolderRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderRowView.swift; sourceTree = "<group>"; };
3A5863702B84564F00487B13 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
3A5863EF2B8C069C00487B13 /* FilterScopeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterScopeView.swift; sourceTree = "<group>"; };
3A5863F12B8C14A400487B13 /* FilePathView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePathView.swift; sourceTree = "<group>"; };
3A67D7632A511F7600516FF2 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
3A67D7652A511FFA00516FF2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
3A67D7672A51566B00516FF2 /* String+ConsoleOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ConsoleOutput.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -180,6 +184,8 @@
children = (
3A2F8F942A4D4C5A00D9FB26 /* FolderRowView.swift */,
3A2F8F6D2A397BAF00D9FB26 /* FileRowView.swift */,
3A5863EF2B8C069C00487B13 /* FilterScopeView.swift */,
3A5863F12B8C14A400487B13 /* FilePathView.swift */,
3A67D7772A56542300516FF2 /* FileContentsView.swift */,
3A2F8F702A397CF100D9FB26 /* DatabaseButtons.swift */,
3A2F8F722A397CF100D9FB26 /* InformationStyle.swift */,
Expand Down Expand Up @@ -458,9 +464,11 @@
3A67D7662A511FFA00516FF2 /* AppDelegate.swift in Sources */,
3A67D7722A54913100516FF2 /* GeneralTabView.swift in Sources */,
3A67D7782A56542300516FF2 /* FileContentsView.swift in Sources */,
3A5863F02B8C069C00487B13 /* FilterScopeView.swift in Sources */,
3A2F8F952A4D4C5A00D9FB26 /* FolderRowView.swift in Sources */,
3A67D7762A549F6700516FF2 /* Styles.swift in Sources */,
3AA565492A25BD6F00EBD7FB /* String.swift in Sources */,
3A5863F22B8C14A400487B13 /* FilePathView.swift in Sources */,
3A2F8F692A397A6B00D9FB26 /* Database.swift in Sources */,
3A2F8F6B2A397ABB00D9FB26 /* AppView.swift in Sources */,
3AAED1F72A3975490036217E /* Database+SwiftUI.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions ClassDumper/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ struct AppView: View {

} detail: {

}
.toolbar {
if fileCount != 0 {
ToolbarItemGroup(placement: .navigation) {
FilterScopeView()
}
}
}
.onReceive(NotificationCenter.default.publisher(for: Notification.Name.folderSelectedFromFinderNotification)) { _ in
parseDirectory()
Expand Down
4 changes: 3 additions & 1 deletion ClassDumper/Extension/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
}

func resetState() {
UserDefaults.standard.removePersistentDomain(forName: PlistKey.Identifier.rawValue)
if let bundleID = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: bundleID)
}
}
}
}
20 changes: 20 additions & 0 deletions ClassDumper/Shared/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,25 @@ struct Preferences {
// source code viewer
static var themeName: CodeEditor.ThemeName = .default
static var fontSize: Int = Int(NSFont.systemFontSize)

// scoped search view
static var scopedSearch = Preferences.FilterScope.default
}
}


extension Preferences {
struct FilterScope: TypedString {
public let rawValue : String

@inlinable
public init(rawValue: String) { self.rawValue = rawValue }

static var `default` = Preferences.FilterScope(rawValue: "scoped")
static var all = Preferences.FilterScope(rawValue: "all")

static var allCases: [FilterScope] {
return [.default, .all]
}
}
}
6 changes: 6 additions & 0 deletions ClassDumper/Shared/Keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@ struct Keys {

struct Detail {
static let CodeViewer = "CodeViewer"
static let PathBar = "PathBar"
static let PathBarFile = "PathBarFile"
}

struct Filters {
static let FilterFiles = "FilterFiles"
}
}
8 changes: 8 additions & 0 deletions ClassDumper/Views/FileContentsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ struct DetailView: View {
@AppStorage("codeViewerFontSize") var fontSize: Int = Preferences.Defaults.fontSize

var fileContents: String
var folderName: String
var fileName: String

var body: some View {
CodeEditor(source: fileContents,
Expand All @@ -15,5 +17,11 @@ struct DetailView: View {
set: { fontSize = Int($0) }),
flags: [.defaultViewerFlags])
.accessibilityIdentifier(Keys.Detail.CodeViewer)

FilePathView(folderName: folderName, fileName: fileName)
}
}

#Preview {
DetailView(fileContents: "File contents", folderName: "TestApp", fileName: "TestFile.Swift")
}
43 changes: 43 additions & 0 deletions ClassDumper/Views/FilePathView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import SwiftUI

struct FilePathView: View {
var folderName: String
var fileName: String

var body: some View {
ScrollView(.horizontal) {
HStack {
HStack(spacing: 4) {
Image(systemName: "folder.fill")
.foregroundColor(.accentColor)
Text(folderName)
}

Image(systemName: "chevron.right")
.imageScale(.small)

HStack(spacing: 4) {
Image(systemName: "doc.fill")
.foregroundColor(.accentColor)
Text(fileName)
.accessibilityIdentifier(Keys.Detail.PathBarFile)
}
}
.padding(.horizontal, 15)
.padding(.bottom, 5)
.frame(maxWidth: .infinity, alignment: .leading)
}
.accessibilityIdentifier(Keys.Detail.PathBar)
}
}

#Preview {
FilePathView(folderName: "Test App", fileName: "TestFile.swift")
}

#Preview {
FilePathView(
folderName: "Test App",
fileName: "TestFileWithReallyLongNameThatShouldTruncateInTheMiddleIfItGetsLongerThanExpected.swift"
)
}
40 changes: 26 additions & 14 deletions ClassDumper/Views/FileRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,22 @@ struct FileRowRequest: Queryable {
}

struct FileRowView: View {
@AppStorage("scopedSearchPreference") var scopedSearchPreference = Preferences.Defaults.scopedSearch

// query to fetch all data
@Query(FileRowRequest())
var fileRows: FileRowResponse
var allFileRows: FileRowResponse

// filtering the active directory
var filteredFileRows: FileRowResponse {
allFileRows.filter({$0.1 == selectedFolder})
}

// data to render based upon scoped preference
var fileRows: FileRowResponse {
scopedSearchPreference == .all ? allFileRows : filteredFileRows
}

var selectedFolder: String

@State private var searchText = ""
Expand All @@ -42,23 +56,21 @@ struct FileRowView: View {

var body: some View {
List(filteredFileNames, id:\.0) { fileId, folderName, fileName, content in
if folderName == selectedFolder {
NavigationLink(destination: DetailView(fileContents: content)) {
Label {
Text(fileName)
.truncationMode(.middle)
.lineLimit(1)
} icon: {
Image(systemName: "doc")
.symbolVariant(.fill)
.foregroundColor(.accentColor)
}
NavigationLink(destination: DetailView(fileContents: content, folderName: folderName, fileName: fileName)) {
Label {
Text(fileName)
.truncationMode(.middle)
.lineLimit(1)
} icon: {
Image(systemName: "doc")
.symbolVariant(.fill)
.foregroundColor(.accentColor)
}
.accessibilityIdentifier(Keys.Middle.Row)
}
.accessibilityIdentifier(Keys.Middle.Row)
.help(scopedSearchPreference == .all ? folderName : "")
}
.accessibilityIdentifier(Keys.Middle.List)
.searchable(text: $searchText, prompt: "Search files")
}
}

28 changes: 28 additions & 0 deletions ClassDumper/Views/FilterScopeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import SwiftUI

struct FilterScopeView: View {
@AppStorage("scopedSearchPreference") var scopedSearchPreference = Preferences.Defaults.scopedSearch

func getFilterText(_ filter: Preferences.FilterScope) -> String {
switch filter {
case .default: "Show selected"
case .all: "Show all"
default: "Unhandled filter"
}
}

var body: some View {
Picker("", selection: $scopedSearchPreference) {
ForEach(Preferences.FilterScope.allCases) { filter in
Text(getFilterText(filter)).tag(filter)
}
}
.accessibilityIdentifier(Keys.Filters.FilterFiles)
.pickerStyle(.menu)
.fixedSize()
}
}

#Preview {
FilterScopeView()
}
7 changes: 7 additions & 0 deletions ClassDumperUITests/ClassDumperUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final class ClassDumperUITests: UITestCase {
.resetState()
.check(.folder, exists: false)
.check(.file, exists: false)
.check(.filterToggle, exists: false)
.openApp(named: "Automator")
.tapFirst(.folder, containing: "class-dump")
.check(.folder, exists: true)
Expand All @@ -29,5 +30,11 @@ final class ClassDumperUITests: UITestCase {
.tapFirst(.file, containing: "CDClassDumpVisitor.h")
.check(.code, exists: true)
.tapFirst(.code, containing: "CDTextClassDumpVisitor.h")
.check(.pathbar, exists: true)
.tapFirst(.pathbar, containing: "CDClassDumpVisitor.h")
.check(.filterToggle, exists: true)
.tapFirst(.filterToggle, containing: "Show selected")
// TODO: CI is failing this test although it is working locally
// .selectPopupButton("Show all")
}
}
45 changes: 44 additions & 1 deletion ClassDumperUITests/ImportFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ struct ImportFlow: Screen {
case navbar
case content
case detail
case filter

}

enum Component {
case folder
case file
case code
case pathbar
case filterToggle
}

struct TestComponent {
Expand Down Expand Up @@ -47,6 +51,20 @@ struct ImportFlow: Screen {
section: .detail,
component: app.scrollViews[Keys.Detail.CodeViewer])
}

var pathbar: TestComponent {
TestComponent(id: Keys.Detail.PathBar,
rowId: "",
section: .detail,
component: app.scrollViews[Keys.Detail.PathBar])
}

var filterToggle: TestComponent {
TestComponent(id: Keys.Filters.FilterFiles,
rowId: "",
section: .filter,
component: app.popUpButtons[Keys.Filters.FilterFiles])
}

func getComponent(for element: Component) -> TestComponent {
switch element {
Expand All @@ -56,6 +74,10 @@ struct ImportFlow: Screen {
return filelist
case .code:
return codeviewer
case .pathbar:
return pathbar
case .filterToggle:
return filterToggle
}
}

Expand All @@ -75,13 +97,23 @@ struct ImportFlow: Screen {
func tapFirst(_ element: Component, containing: String) -> Self {
let forElement = getComponent(for: element)

tapFirstRow(label: containing,
tapFirstRow(element,
label: containing,
parent: forElement.component,
target: forElement.section,
rowId: forElement.rowId)

return self
}

@discardableResult
func selectPopupButton(_ containing: String) -> Self {
let firstPredicate = NSPredicate(format: "title BEGINSWITH '\(containing)'")
let desiredOption = filterToggle.component.menuItems.element(matching: firstPredicate)
desiredOption.tap()

return self
}

func resetState() -> Self {
open(.settings)
Expand Down Expand Up @@ -113,6 +145,7 @@ struct ImportFlow: Screen {

@discardableResult
private func tapFirstRow(
_ component: Component,
label: String,
parent: XCUIElement,
target: Section,
Expand All @@ -133,12 +166,22 @@ struct ImportFlow: Screen {
break
case .detail:
row = parent.textViews

if component == .pathbar {
row = parent.staticTexts.matching(identifier: Keys.Detail.PathBarFile)
}

guard let found = row.firstMatch.value as? String else {
fatalError("Could not tap or locate: {label:\(label), parent:\(parent), target: \(target), row: \(rowId)")
}
XCTAssertTrue(found.contains(label))
row.firstMatch.tap()
break
case .filter:
row = app.popUpButtons
XCTAssertTrue(row[Keys.Filters.FilterFiles].value as? String == label)
row.firstMatch.tap()
break
}

return self
Expand Down

0 comments on commit 78eb269

Please sign in to comment.