├── .gitignore ├── Code ├── XPC Experiments │ ├── XPCExecutable │ │ ├── Shared │ │ │ ├── XPCExecutable.swift │ │ │ ├── XPCExecutableClientExportedInterface.swift │ │ │ └── XPCExecutableServiceExportedInterface.swift │ │ ├── Service │ │ │ └── Service+main.swift │ │ └── Client │ │ │ ├── ServiceExportedInterface+Conveniences.swift │ │ │ └── TestXPCExecutableForCodeface.swift │ └── ProcessServiceTest │ │ ├── ProcessServiceTestMain.swift │ │ └── RunProcessServiceTest.swift ├── App │ ├── Codebase Analysis │ │ ├── Codebase Analysis View │ │ │ ├── Central View │ │ │ │ ├── ContentViewStyle.swift │ │ │ │ ├── Display Mode │ │ │ │ │ ├── DisplayMode.swift │ │ │ │ │ └── DisplayModePicker.swift │ │ │ │ ├── Architecture View │ │ │ │ │ ├── ArtifactViewModel+SwiftUI.swift │ │ │ │ │ ├── TreeMap.swift │ │ │ │ │ ├── ArtifactHeaderView.swift │ │ │ │ │ ├── DependencyView.swift │ │ │ │ │ ├── ArtifactContentView.swift │ │ │ │ │ └── ArtifactView.swift │ │ │ │ ├── Top Panel │ │ │ │ │ ├── Search │ │ │ │ │ │ ├── Search.swift │ │ │ │ │ │ ├── SearchBarView.swift │ │ │ │ │ │ └── SearchField.swift │ │ │ │ │ ├── CodebaseAnalysisContentPanel.swift │ │ │ │ │ ├── PathBar.swift │ │ │ │ │ ├── PathBarView.swift │ │ │ │ │ └── Path Bar │ │ │ │ │ │ ├── PathBarView.swift │ │ │ │ │ │ └── PathBar.swift │ │ │ │ ├── CodebaseCentralView.swift │ │ │ │ ├── CodeView.swift │ │ │ │ ├── LSPServiceHint.swift │ │ │ │ └── CodebaseContentView.swift │ │ │ ├── CodebaseAnalysisView.swift │ │ │ ├── Navigator View │ │ │ │ ├── SidebarLabel.swift │ │ │ │ └── CodebaseNavigatorView.swift │ │ │ └── Inspector View │ │ │ │ └── CodebaseInspectorView.swift │ │ ├── Architecture View Model │ │ │ ├── ArtifactViewModel+Hashable.swift │ │ │ ├── ArtifactViewModel+BorderColor.swift │ │ │ ├── Layout │ │ │ │ └── ArtifactViewModel+Layout.swift │ │ │ ├── ArtifactViewModel+AddDependencies.swift │ │ │ └── ArtifactViewModel+Search.swift │ │ ├── Artifact Icon │ │ │ ├── ArtifactIconView.swift │ │ │ └── ArtifactIcon.swift │ │ └── CodebaseAnalysis.swift │ ├── Purchase │ │ ├── ProductID+Codeface.swift │ │ ├── Purchase Panel │ │ │ ├── FeatureView.swift │ │ │ └── SubscriptionManagementView.swift │ │ ├── AppStoreClient+SwiftUI.swift │ │ └── PurchaseMenu.swift │ ├── WindowDisplayOptions.swift │ ├── DocumentLink+Codeface.swift │ ├── Codebase Architecture │ │ ├── Metrics │ │ │ ├── CodeArtifact+Metrics.swift │ │ │ ├── CodeFolderArtifact+CalculateMetrics.swift │ │ │ ├── CodeFolderArtifact+LOCInCycles.swift │ │ │ ├── CodeArtifactMetricsCache.swift │ │ │ ├── CodeFolderArtifact+SizeMetrics.swift │ │ │ ├── Metrics.swift │ │ │ ├── CodeFolderArtifact+SortMetric.swift │ │ │ └── GraphNode+Sorting.swift │ │ ├── Concrete Code Artifacts │ │ │ ├── CodeFileArtifact.swift │ │ │ ├── CodeSymbolArtifact.swift │ │ │ └── CodeFolderArtifact.swift │ │ ├── Code Artifact Conformance │ │ │ ├── CodeFolderArtifact+CodeArtifact.swift │ │ │ ├── CodeFileArtifact+CodeArtifact.swift │ │ │ └── CodeSymbolArtifact+CodeArtifact.swift │ │ ├── CodeArtifact.swift │ │ └── Create from Codebase │ │ │ ├── CodeFileArtifact+CodeFile.swift │ │ │ ├── CodeSymbolArtifact+CodeSymbol.swift │ │ │ └── CodeFolderArtifact+CodeFolder.swift │ ├── Codebase Processor │ │ ├── Codebase Processor View │ │ │ ├── ProcessingFailureView.swift │ │ │ ├── LoadingProgressView.swift │ │ │ ├── EmptyProcesorView.swift │ │ │ └── CodebaseProcessorView.swift │ │ └── Codebase Processor │ │ │ ├── CodebaseProcessorState.swift │ │ │ └── CodebaseProcessorSteps.swift │ ├── Codebase │ │ ├── CodeFile.swift │ │ ├── CodeFolder.swift │ │ ├── Load │ │ │ ├── CodebaseFileDocument.swift │ │ │ └── CodeFolder+File System.swift │ │ └── CodeSymbol.swift │ ├── URL+Codeface.swift │ ├── GlobalSettings.swift │ ├── Codebase Window │ │ ├── Codebase Window View │ │ │ ├── SecondaryToolbarButtons.swift │ │ │ ├── PrimaryToolbarButtons.swift │ │ │ ├── CodebaseWindowView.swift │ │ │ └── CodebaseLocator.swift │ │ ├── CodebaseLocationPersister.swift │ │ └── CodebaseWindow.swift │ ├── Main Menu │ │ ├── FindAndFilterMenuOptions.swift │ │ └── ViewMenuOptions.swift │ ├── CodefaceAppDelegate.swift │ └── TestingDashboard.swift ├── Framework-Candidates │ ├── User Interface │ │ ├── DocumentLink.swift │ │ ├── Center.swift │ │ ├── LoggingText.swift │ │ ├── AppIcon.swift │ │ ├── NSApplication+NSWindow.swift │ │ ├── LargeButton.swift │ │ ├── AboutPanel.swift │ │ └── LogView.swift │ ├── SwiftUINodes │ │ ├── AnimatableLine.swift │ │ └── Arrow.swift │ └── RelativeFilePath.swift └── Proofs of Concept │ ├── ConcurrencyPOC.swift │ ├── AnimationTestView.swift │ └── NavigationAndFocusPOCView.swift ├── Icons ├── App │ ├── 16.png │ ├── 32.png │ ├── 64.png │ ├── 1024.png │ ├── 128.png │ ├── 256.png │ ├── 512.png │ ├── 1024_mono.png │ ├── 1028_mono_bg.png │ ├── App Icon 2.graffle │ └── Templates │ │ ├── developer.png │ │ ├── background.png │ │ └── testflight.png └── Document │ ├── 1024.png │ ├── 1024_rendered.png │ ├── Document Icon.graffle │ ├── Templates │ ├── empty.png │ ├── xcodeproj.png │ └── xcworkspace.png │ └── refresh_finder_icons.command ├── Documentation ├── Context_Diagram.jpg ├── Context_Diagram.graffle └── Architecture_Diagram.graffle ├── XCodeProject ├── Codeface │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 64.png │ │ │ ├── 1024.png │ │ │ ├── 128.png │ │ │ ├── 256-1.png │ │ │ ├── 256.png │ │ │ ├── 32-1.png │ │ │ ├── 512-1.png │ │ │ ├── 512.png │ │ │ └── Contents.json │ │ ├── CodebaseDocument.iconset │ │ │ ├── icon_16x16.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_128x128.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_256x256@2x.png │ │ │ └── icon_512x512@2x.png │ │ ├── kotlin.imageset │ │ │ ├── Contents.json │ │ │ └── Kotlin_Icon_2021.svg │ │ ├── dart.imageset │ │ │ ├── Contents.json │ │ │ └── dart-programming-language-icon.svg │ │ └── codefaceAccentColor.colorset │ │ │ └── Contents.json │ ├── Codeface.entitlements │ └── Info.plist ├── Codeface.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Codeface.xcscheme ├── CodefaceHelper │ ├── CodefaceHelper.entitlements │ └── Info.plist ├── CodefaceTests │ ├── Info.plist │ └── CodefaceTests.swift ├── App Store Testing.storekit └── App Store Synced.storekit ├── CI ├── ReleaseBot │ ├── .gitignore │ ├── Sources │ │ ├── ZShell.swift │ │ ├── main.swift │ │ ├── FileSystem.swift │ │ ├── AppStoreCredentials.swift │ │ └── Upload.swift │ └── Package.swift ├── test.sh └── Upload │ └── ExportOptions.plist └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | swiftpm/ 2 | *xcuserdata/ 3 | Package.resolved 4 | CI/Upload/Byproducts/ -------------------------------------------------------------------------------- /Code/XPC Experiments/XPCExecutable/Shared/XPCExecutable.swift: -------------------------------------------------------------------------------- 1 | enum XPCExecutable {} 2 | -------------------------------------------------------------------------------- /Icons/App/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/16.png -------------------------------------------------------------------------------- /Icons/App/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/32.png -------------------------------------------------------------------------------- /Icons/App/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/64.png -------------------------------------------------------------------------------- /Icons/App/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/1024.png -------------------------------------------------------------------------------- /Icons/App/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/128.png -------------------------------------------------------------------------------- /Icons/App/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/256.png -------------------------------------------------------------------------------- /Icons/App/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/512.png -------------------------------------------------------------------------------- /Icons/App/1024_mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/1024_mono.png -------------------------------------------------------------------------------- /Icons/Document/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/Document/1024.png -------------------------------------------------------------------------------- /Icons/App/1028_mono_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/1028_mono_bg.png -------------------------------------------------------------------------------- /Icons/App/App Icon 2.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/App Icon 2.graffle -------------------------------------------------------------------------------- /Documentation/Context_Diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Documentation/Context_Diagram.jpg -------------------------------------------------------------------------------- /Icons/App/Templates/developer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/Templates/developer.png -------------------------------------------------------------------------------- /Icons/Document/1024_rendered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/Document/1024_rendered.png -------------------------------------------------------------------------------- /Icons/App/Templates/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/Templates/background.png -------------------------------------------------------------------------------- /Icons/App/Templates/testflight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/App/Templates/testflight.png -------------------------------------------------------------------------------- /Icons/Document/Document Icon.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/Document/Document Icon.graffle -------------------------------------------------------------------------------- /Icons/Document/Templates/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/Document/Templates/empty.png -------------------------------------------------------------------------------- /Documentation/Context_Diagram.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Documentation/Context_Diagram.graffle -------------------------------------------------------------------------------- /Icons/Document/Templates/xcodeproj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/Document/Templates/xcodeproj.png -------------------------------------------------------------------------------- /Icons/Document/Templates/xcworkspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Icons/Document/Templates/xcworkspace.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Documentation/Architecture_Diagram.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/Documentation/Architecture_Diagram.graffle -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/ContentViewStyle.swift: -------------------------------------------------------------------------------- 1 | enum CentralViewStyle 2 | { 3 | static let fontSize: Double = 15 4 | } 5 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/256-1.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/32-1.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/512-1.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /Code/App/Purchase/ProductID+Codeface.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | extension AppStoreClient.ProductID 4 | { 5 | static let subscriptionLevel1 = Self("io.codeface.subscription.level1") 6 | } 7 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_16x16.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_32x32.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_128x128.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_256x256.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_512x512.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeface-io/Codeface/HEAD/XCodeProject/Codeface/Assets.xcassets/CodebaseDocument.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Display Mode/DisplayMode.swift: -------------------------------------------------------------------------------- 1 | enum DisplayMode: String, CaseIterable, Identifiable 2 | { 3 | var id: Self { self } 4 | 5 | case treeMap, code 6 | } 7 | -------------------------------------------------------------------------------- /CI/ReleaseBot/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /XCodeProject/Codeface.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XCodeProject/CodefaceHelper/CodefaceHelper.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Icons/Document/refresh_finder_icons.command: -------------------------------------------------------------------------------- 1 | sudo rm -rfv /Library/Caches/com.apple.iconservices.store; sudo find /private/var/folders/ \( -name com.apple.dock.iconcache -or -name com.apple.iconservices \) -exec rm -rfv {} \; ; sleep 3;sudo touch /Applications/* ; killall Dock; killall Finder -------------------------------------------------------------------------------- /Code/App/WindowDisplayOptions.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | class WindowDisplayOptions: ObservableObject 4 | { 5 | @Published var showsSubscriptionPanel = false 6 | @Published var showsLeftSidebar = true 7 | @Published var showsRightSidebar = false 8 | @Published var showsLinesOfCode = false 9 | } 10 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Architecture View/ArtifactViewModel+SwiftUI.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension ArtifactViewModel 4 | { 5 | var fontDesign: Font.Design 6 | { 7 | if case .symbol = kind { return .monospaced } 8 | return .default 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Code/App/DocumentLink+Codeface.swift: -------------------------------------------------------------------------------- 1 | extension DocumentLink 2 | { 3 | static let lspService = DocumentLink("How to Setup LSPService", 4 | url: .lspService) 5 | 6 | static let wiki = DocumentLink("Codeface Wiki & Documentation", 7 | url: .wiki) 8 | } 9 | -------------------------------------------------------------------------------- /XCodeProject/Codeface.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/kotlin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Kotlin_Icon_2021.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/dart.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "dart-programming-language-icon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/CodeArtifact+Metrics.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | @BackgroundActor 4 | extension CodeArtifact 5 | { 6 | var linesOfCode: Int { metrics.linesOfCode ?? 0 } 7 | 8 | var metrics: Metrics 9 | { 10 | get { CodeArtifactMetricsCache.shared[id] } 11 | set { CodeArtifactMetricsCache.shared[id] = newValue } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CI/ReleaseBot/Sources/ZShell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @discardableResult 4 | func run(command: String) throws { 5 | let zShell = Process() 6 | 7 | zShell.launchPath = "/bin/zsh" 8 | zShell.arguments = ["-c", command] 9 | zShell.launch() 10 | zShell.waitUntilExit() 11 | 12 | if zShell.terminationStatus != 0 { 13 | throw "Shell command failed" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/Search/Search.swift: -------------------------------------------------------------------------------- 1 | @MainActor 2 | struct Search 3 | { 4 | var barIsShown = false 5 | var fieldIsFocused = false 6 | var term = "" 7 | 8 | static let toggleAnimationDuration: Double = 0.15 9 | static let layoutAnimationDuration: Double = 1.0 10 | static let filterUpdateAnimationDuration: Double = 0.15 11 | } 12 | -------------------------------------------------------------------------------- /CI/ReleaseBot/Sources/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | try changeDirectory(to: "/Users/seb/Desktop/GitHub Repos/Codeface") 4 | 5 | let codefaceScheme = XcodeSchemeLocation(projectFolderPath: "XcodeProject", 6 | projectName: "Codeface", 7 | name: "Codeface") 8 | 9 | try uploadBuild(of: codefaceScheme, withCredentials: .retrieve()) 10 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Architecture View Model/ArtifactViewModel+Hashable.swift: -------------------------------------------------------------------------------- 1 | extension ArtifactViewModel: Hashable 2 | { 3 | nonisolated func hash(into hasher: inout Hasher) 4 | { 5 | hasher.combine(id) 6 | } 7 | 8 | nonisolated static func == (lhs: ArtifactViewModel, 9 | rhs: ArtifactViewModel) -> Bool 10 | { 11 | lhs.id == rhs.id 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Codeface.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | * App: [codeface.io](https://codeface.io) 2 | 3 | * Open-source: The app infrastructure is truly open-source and hosted at [github.com/nohype-ai](https://github.com/nohype-ai). 4 | 5 | * Disclaimer: *This repo here is not yet intended for contributions from the public and may at some point shift focus to certain aspects of the project. If you have ideas on what parts should further be extracted into open-source Swift packages, let us know: hello@codeface.io* 6 | -------------------------------------------------------------------------------- /Code/XPC Experiments/XPCExecutable/Shared/XPCExecutableClientExportedInterface.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc protocol XPCExecutableClientExportedInterface 4 | { 5 | func executableDidSend(stdOut: Data, 6 | confirmCall: @escaping () -> Void) 7 | 8 | func executableDidSend(stdErr: Data, 9 | confirmCall: @escaping () -> Void) 10 | 11 | func executableDidTerminate(confirmCall: @escaping () -> Void) 12 | } 13 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/codefaceAccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "215", 9 | "green" : "96", 10 | "red" : "47" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/DocumentLink.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct DocumentLink: View 4 | { 5 | init(_ text: String, url: URL) 6 | { 7 | self.text = text 8 | self.url = url 9 | } 10 | 11 | var body: some View 12 | { 13 | Link(destination: url) 14 | { 15 | Label(text, systemImage: "doc.text") 16 | } 17 | } 18 | 19 | private let text: String 20 | private let url: URL 21 | } 22 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/CodeFolderArtifact+CalculateMetrics.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | @BackgroundActor 4 | extension CodeFolderArtifact 5 | { 6 | func calculateMetrics() 7 | { 8 | // the order here matters as some steps build upon metrics of preceding ones 9 | calculateSizeMetricsRecursively() 10 | calculateDependencyMetricsRecursively() 11 | calculateCycleMetricsRecursively() 12 | calculateSortMetricsRecursively() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CI/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Specify inputs 4 | 5 | PROJECT="XcodeProject/Codeface.xcodeproj" 6 | SCHEME="Codeface" 7 | 8 | # Declare and run test function 9 | 10 | function testTheProject { 11 | echo "🤖 Testing $PROJECT ..." 12 | xcodebuild clean build test \ 13 | -project $PROJECT \ 14 | -scheme $SCHEME \ 15 | -sdk macosx \ 16 | -destination 'platform=macOS,arch=arm64' \ 17 | -configuration Debug \ 18 | > /dev/null 19 | } 20 | 21 | testTheProject -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/Center.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct Center: View 4 | { 5 | var body: some View 6 | { 7 | HStack 8 | { 9 | Spacer() 10 | 11 | VStack 12 | { 13 | Spacer() 14 | content() 15 | Spacer() 16 | } 17 | 18 | Spacer() 19 | } 20 | } 21 | 22 | @ViewBuilder let content: () -> Content 23 | } 24 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/CodeFolderArtifact+LOCInCycles.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | @BackgroundActor 4 | extension CodeFolderArtifact 5 | { 6 | func calculateCycleMetricsRecursively() 7 | { 8 | traverseDepthFirst { $0.calculateCycleMetrics() } 9 | } 10 | } 11 | 12 | @BackgroundActor 13 | private extension CodeArtifact 14 | { 15 | func calculateCycleMetrics() 16 | { 17 | metrics.linesOfCodeOfPartsInCycles = parts.sum { $0.metrics.linesOfCodeInCycles } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Code/App/Codebase Processor/Codebase Processor View/ProcessingFailureView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ProcessingFailureView: View 4 | { 5 | var body: some View 6 | { 7 | VStack(alignment: .leading) 8 | { 9 | Text("An error occured while loading the codebase:") 10 | .foregroundColor(Color(NSColor.systemRed)) 11 | .padding(.bottom) 12 | 13 | Text(errorMessage) 14 | } 15 | } 16 | 17 | let errorMessage: String 18 | } 19 | -------------------------------------------------------------------------------- /XCodeProject/CodefaceHelper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundlePackageType 6 | XPC 7 | CFBundleIdentifier 8 | com.flowtoolz.codeface.CodefaceHelper 9 | XPCService 10 | 11 | ServiceType 12 | Application 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/kotlin.imageset/Kotlin_Icon_2021.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Architecture View/TreeMap.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TreeMap: View 4 | { 5 | var body: some View 6 | { 7 | RootArtifactContentView(artifactVM: analysis.selectedArtifact, 8 | analysis: analysis) 9 | .padding(ArtifactViewModel.padding) 10 | .background(Color(white: colorScheme == .dark ? 0 : 0.6)) 11 | } 12 | 13 | @ObservedObject var analysis: CodebaseAnalysis 14 | @Environment(\.colorScheme) var colorScheme 15 | } 16 | -------------------------------------------------------------------------------- /Code/App/Codebase/CodeFile.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import FoundationToolz 3 | 4 | /** 5 | ⛔️ Do not change! This is part of the ".codebase" file format. 6 | */ 7 | final class CodeFile: Codable, Sendable 8 | { 9 | init(name: String, 10 | code: String, 11 | symbols: [CodeSymbol]? = nil) 12 | { 13 | self.name = name 14 | self.code = code 15 | self.symbols = symbols 16 | } 17 | 18 | let name: String 19 | 20 | var lines: [String] { code.lines } 21 | let code: String 22 | 23 | let symbols: [CodeSymbol]? 24 | } 25 | -------------------------------------------------------------------------------- /Code/XPC Experiments/XPCExecutable/Service/Service+main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftyToolz 3 | 4 | @main 5 | extension XPCExecutable.Service 6 | { 7 | static func main() 8 | { 9 | log("✅ Launched Service via XPCExecutable (POC)") 10 | 11 | let service = XPCExecutable.Service() // acts as the listener delegate for this service 12 | 13 | let listener = NSXPCListener.service() 14 | listener.delegate = service // handles incoming connections 15 | listener.resume() // starts this service; does not return 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/LoggingText.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | /** 5 | Log something as text **on every view update** (for debugging) 6 | 7 | For example, it allows to log the geometry proxy size inside `GeometryReader` to find out what a view's size actually is from the beginning, not just how it changes. 8 | */ 9 | struct LoggingText: View 10 | { 11 | init(_ text: String) 12 | { 13 | log(text) 14 | self.text = text 15 | } 16 | 17 | var body: some View 18 | { 19 | Text("Last Log: " + text) 20 | } 21 | 22 | private let text: String 23 | } 24 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/CodebaseAnalysisContentPanel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TopBar: View 4 | { 5 | var body: some View 6 | { 7 | VStack(spacing: 0) 8 | { 9 | PathBarView(overviewBar: analysis.pathBar) 10 | 11 | SearchBarView(analysis: analysis, 12 | artifactName: analysis.selectedArtifact.codeArtifact.name) 13 | 14 | Divider() 15 | } 16 | .background(Color(NSColor.controlBackgroundColor)) 17 | } 18 | 19 | @ObservedObject var analysis: CodebaseAnalysis 20 | } 21 | -------------------------------------------------------------------------------- /Code/App/URL+Codeface.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension URL 4 | { 5 | static let featureVote = URL(string: "https://flowtoolz.typeform.com/to/afRj6wz4")! 6 | 7 | static let lspService = URL(string: "https://codeface.io/lspservice/index.html")! 8 | 9 | static let wiki = URL(string: "https://github.com/codeface-io/Codeface/wiki")! 10 | 11 | static let privacyPolicy = URL(string: "https://codeface.io/privacy-policy")! 12 | 13 | static let licenseAgreement = URL(string: "https://www.apple.com/legal/macapps/stdeula")! 14 | // alternatively: https://www.apple.com/legal/internet-services/itunes/dev/stdeula/ 15 | } 16 | -------------------------------------------------------------------------------- /CI/ReleaseBot/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ReleaseBot", 8 | platforms: [ 9 | .macOS(.v13) // Minimum deployment version 10 | ], 11 | targets: [ 12 | // Targets are the basic building blocks of a package, defining a module or a test suite. 13 | // Targets can depend on other targets in this package and products from dependencies. 14 | .executableTarget( 15 | name: "ReleaseBot", 16 | path: "Sources"), 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /Code/App/Codebase Processor/Codebase Processor View/LoadingProgressView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LoadingProgressView: View 4 | { 5 | var body: some View 6 | { 7 | VStack 8 | { 9 | ProgressView() 10 | .progressViewStyle(.circular) 11 | .padding(.bottom) 12 | 13 | Text(primaryText) 14 | .padding(.bottom, 1) 15 | 16 | Text(secondaryText) 17 | .foregroundColor(.secondary) 18 | } 19 | .multilineTextAlignment(.center) 20 | } 21 | 22 | let primaryText: String 23 | let secondaryText: String 24 | } 25 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/CodeArtifactMetricsCache.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | @BackgroundActor 4 | class CodeArtifactMetricsCache 5 | { 6 | static let shared = CodeArtifactMetricsCache() 7 | 8 | func clear() 9 | { 10 | metricsByArtifactID.removeAll() 11 | } 12 | 13 | /// Since `Metrics` is a value type, the getter simply returns a new value when none is stored 14 | subscript(_ id: CodeArtifact.ID) -> Metrics 15 | { 16 | get { metricsByArtifactID[id] ?? Metrics() } 17 | set { metricsByArtifactID[id] = newValue } 18 | } 19 | 20 | private var metricsByArtifactID = [CodeArtifact.ID: Metrics]() 21 | } 22 | -------------------------------------------------------------------------------- /CI/ReleaseBot/Sources/FileSystem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func deleteItem(at relativePath: String) throws { 4 | let fileManager = FileManager.default 5 | let currentWorkingPath = fileManager.currentDirectoryPath 6 | let absolutePath = currentWorkingPath + "/" + relativePath 7 | 8 | if fileManager.fileExists(atPath: absolutePath) { 9 | try fileManager.removeItem(atPath: absolutePath) 10 | } 11 | } 12 | 13 | func changeDirectory(to directory: String) throws { 14 | guard FileManager.default.changeCurrentDirectoryPath(directory) else { 15 | throw "could not change directory to " + directory 16 | } 17 | } 18 | 19 | extension String: Error {} 20 | -------------------------------------------------------------------------------- /Code/XPC Experiments/XPCExecutable/Shared/XPCExecutableServiceExportedInterface.swift: -------------------------------------------------------------------------------- 1 | import FoundationToolz 2 | import Foundation 3 | 4 | /// The protocol that this service will vend as its API. This protocol will also need to be visible to the process hosting the service (the main app). 5 | @objc protocol XPCExecutableServiceExportedInterface 6 | { 7 | func getProcessID(handleCompletion: @escaping (Int) -> Void) 8 | 9 | func launchExecutable(withEncodedConfig: Data, 10 | handleCompletion: @escaping (Error?) -> Void) 11 | 12 | func writeExecutableStdIn(_: Data, 13 | handleCompletion: @escaping (Error?) -> Void) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/AppIcon.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import FoundationToolz 3 | import SwiftyToolz 4 | 5 | struct AppIcon: View 6 | { 7 | var body: some View 8 | { 9 | let iconName = Bundle.main.iconName ?? "AppIcon" 10 | 11 | if let nsImage = NSImage(named: iconName) 12 | { 13 | Image(nsImage: nsImage) 14 | .resizable() 15 | .aspectRatio(contentMode: .fit) 16 | } 17 | else 18 | { 19 | let errorMessage = "Found no image named '\(iconName)'" 20 | Text(errorMessage) 21 | .onAppear { log(error: errorMessage) } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/SwiftUINodes/AnimatableLine.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct Line: Shape 4 | { 5 | var animatableData: AnimatablePair 6 | { 7 | get 8 | { 9 | AnimatablePair(from.animatableData, to.animatableData) 10 | } 11 | 12 | set 13 | { 14 | from.animatableData = newValue.first 15 | to.animatableData = newValue.second 16 | } 17 | } 18 | 19 | func path(in rect: CGRect) -> Path 20 | { 21 | Path 22 | { 23 | p in 24 | 25 | p.move(to: from) 26 | p.addLine(to: to) 27 | } 28 | } 29 | 30 | var from, to: CGPoint 31 | } 32 | -------------------------------------------------------------------------------- /Code/App/Codebase/CodeFolder.swift: -------------------------------------------------------------------------------- 1 | /** 2 | ⛔️ Do not change! This is part of the ".codebase" file format. 3 | */ 4 | final class CodeFolder: Codable, Sendable 5 | { 6 | var looksLikeAPackage: Bool 7 | { 8 | if name.lowercased().contains("package") { return true } 9 | 10 | return files?.contains { $0.name.lowercased().contains("package") } ?? false 11 | } 12 | 13 | init(name: String, 14 | files: [CodeFile] = [], 15 | subfolders: [CodeFolder] = []) 16 | { 17 | self.name = name 18 | self.files = files.isEmpty ? nil : files 19 | self.subfolders = subfolders.isEmpty ? nil : subfolders 20 | } 21 | 22 | let name: String 23 | let files: [CodeFile]? 24 | let subfolders: [CodeFolder]? 25 | } 26 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Concrete Code Artifacts/CodeFileArtifact.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftLSP 3 | import SwiftNodes 4 | 5 | final class CodeFileArtifact: Identifiable, Sendable 6 | { 7 | init(name: String, 8 | codeLines: [String], 9 | symbolGraph: Graph) 10 | { 11 | self.name = name 12 | self.lines = codeLines 13 | self.symbolGraph = symbolGraph 14 | } 15 | 16 | // MARK: - Tree Structure 17 | 18 | let symbolGraph: Graph 19 | 20 | // MARK: - Basics 21 | 22 | let name: String 23 | var code: String? { lines.joined(separator: "\n") } 24 | let lines: [String] 25 | 26 | let id = UUID().uuidString 27 | } 28 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/CodeFolderArtifact+SizeMetrics.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | @BackgroundActor 4 | extension CodeFolderArtifact 5 | { 6 | func calculateSizeMetricsRecursively() 7 | { 8 | traverseDepthFirst { $0.calculateSizeMetrics() } 9 | } 10 | } 11 | 12 | @BackgroundActor 13 | private extension CodeArtifact 14 | { 15 | func calculateSizeMetrics() 16 | { 17 | let locOfParts = parts.sum { $0.linesOfCode } 18 | 19 | metrics.linesOfCodeOfParts = locOfParts 20 | 21 | parts.forEach 22 | { 23 | $0.metrics.sizeRelativeToAllPartsInScope = Double($0.linesOfCode) / Double(locOfParts) 24 | } 25 | 26 | metrics.linesOfCode = intrinsicSizeInLinesOfCode ?? locOfParts 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Code Artifact Conformance/CodeFolderArtifact+CodeArtifact.swift: -------------------------------------------------------------------------------- 1 | extension CodeFolderArtifact: SearchableCodeArtifact 2 | { 3 | func contains(fileLine: Int) -> Bool { false } 4 | } 5 | 6 | extension CodeFolderArtifact: CodeArtifact 7 | { 8 | var parts: [any CodeArtifact] 9 | { 10 | partGraph.nodesByID.values.map { $0.value } 11 | } 12 | 13 | var intrinsicSizeInLinesOfCode: Int? { nil } 14 | var kindName: String { "Folder" } 15 | var code: String? { nil } 16 | var lineNumber: Int? { nil } 17 | 18 | // MARK: - Hashability 19 | 20 | static func == (lhs: CodeFolderArtifact, 21 | rhs: CodeFolderArtifact) -> Bool { lhs === rhs } 22 | 23 | func hash(into hasher: inout Hasher) { hasher.combine(id) } 24 | } 25 | -------------------------------------------------------------------------------- /XCodeProject/CodefaceTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Code/App/Purchase/Purchase Panel/FeatureView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct FeatureView: View 4 | { 5 | init(_ title: String, subtitle: String) 6 | { 7 | self.title = title 8 | self.subtitle = subtitle 9 | } 10 | 11 | var body: some View 12 | { 13 | Label 14 | { 15 | VStack(alignment: .leading, spacing: 3) 16 | { 17 | Text(title) 18 | .fontWeight(.medium) 19 | 20 | Text(subtitle) 21 | .foregroundColor(.secondary) 22 | } 23 | } icon: { 24 | Image(systemName: "checkmark") 25 | .foregroundColor(Color(.systemGreen)) 26 | } 27 | } 28 | 29 | let title: String 30 | let subtitle: String 31 | } 32 | -------------------------------------------------------------------------------- /Code/App/Codebase Processor/Codebase Processor/CodebaseProcessorState.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | 3 | enum CodebaseProcessorState 4 | { 5 | var analysis: CodebaseAnalysis? 6 | { 7 | if case .analyzeArchitecture(let analysis) = self { return analysis } 8 | return nil 9 | } 10 | 11 | case empty, 12 | didLocateCodebase(LSP.CodebaseLocation), 13 | retrieveCodebase(String), 14 | didJustRetrieveCodebase(CodeFolder), 15 | processCodebase(CodeFolder, ProgressFeedback), 16 | processArchitecture(CodeFolder, CodeFolderArtifact, ProgressFeedback), 17 | analyzeArchitecture(CodebaseAnalysis), 18 | didFail(String) 19 | 20 | struct ProgressFeedback 21 | { 22 | let primaryText: String 23 | let secondaryText: String 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Code/App/GlobalSettings.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | class GlobalSettings: ObservableObject 4 | { 5 | static let shared = GlobalSettings() 6 | 7 | private init() {} 8 | 9 | #if DEBUG 10 | @Published var useCorrectAnimations = false 11 | #else 12 | /// DO NOT TOUCH THIS (so we can't accidentally fuck up a release) 13 | @Published var useCorrectAnimations = false 14 | #endif 15 | 16 | #if DEBUG 17 | var updateSearchTermGlobally = true 18 | #else 19 | /// DO NOT TOUCH THIS (so we can't accidentally fuck up a release) 20 | var updateSearchTermGlobally = true 21 | #endif 22 | 23 | #if DEBUG 24 | var showPurchasePanel = false 25 | #else 26 | /// DO NOT TOUCH THIS (so we can't accidentally fuck up a release) 27 | var showPurchasePanel = true 28 | #endif 29 | } 30 | -------------------------------------------------------------------------------- /Code/App/Codebase Processor/Codebase Processor View/EmptyProcesorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftLSP 3 | import LSPServiceKit 4 | 5 | struct EmptyProcesorView: View 6 | { 7 | var body: some View 8 | { 9 | VStack 10 | { 11 | HStack 12 | { 13 | Spacer() 14 | Text("This is an empty codebase file.\nImport a code folder into this file via the File menu.") 15 | .multilineTextAlignment(.center) 16 | .font(.title) 17 | .foregroundColor(.secondary) 18 | .padding() 19 | Spacer() 20 | } 21 | 22 | LSPServiceHint() 23 | } 24 | .padding(50) 25 | } 26 | 27 | @ObservedObject private var serverManager = LSP.ServerManager.shared 28 | } 29 | -------------------------------------------------------------------------------- /CI/Upload/ExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | generateAppStoreInformation 6 | 7 | installerSigningCertificate 8 | 3rd Party Mac Developer Installer: Sebastian Fichtner (8T3R57GCBV) 9 | manageAppVersionAndBuildNumber 10 | 11 | method 12 | app-store 13 | provisioningProfiles 14 | 15 | com.flowtoolz.codeface 16 | Codeface macOS Distribution Profile 17 | 18 | signingCertificate 19 | Apple Distribution 20 | signingStyle 21 | manual 22 | uploadSymbols 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Code/XPC Experiments/XPCExecutable/Client/ServiceExportedInterface+Conveniences.swift: -------------------------------------------------------------------------------- 1 | import FoundationToolz 2 | import Foundation 3 | 4 | extension XPCExecutableServiceExportedInterface 5 | { 6 | func launchExecutable(_ config: Executable.Configuration, 7 | handleCompletion: @escaping (Error?) -> Void) 8 | { 9 | // TODO: This should all use async/await, but we need to handle timeouts in case XPC fails, see "Pitfall": https://www.chimehq.com/blog/extensionkit-xpc 10 | 11 | do 12 | { 13 | let executableConfigData: Data = try config.encode(options: []) 14 | 15 | launchExecutable(withEncodedConfig: executableConfigData, 16 | handleCompletion: handleCompletion) 17 | } 18 | catch 19 | { 20 | handleCompletion(error) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/CodeArtifact.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | protocol SearchableCodeArtifact: CodeArtifact 4 | { 5 | func contains(fileLine: Int) -> Bool 6 | } 7 | 8 | extension CodeArtifact 9 | { 10 | func traverseDepthFirst(_ visit: (any CodeArtifact) -> Void) 11 | { 12 | parts.forEach { $0.traverseDepthFirst(visit) } 13 | visit(self) 14 | } 15 | } 16 | 17 | protocol CodeArtifact: AnyObject, Hashable, Sendable 18 | { 19 | // TODO: rather return the whole graph 20 | var parts: [any CodeArtifact] { get } 21 | // var test: Graph { get } 22 | 23 | // basic properties 24 | var intrinsicSizeInLinesOfCode: Int? { get } 25 | var name: String { get } 26 | var kindName: String { get } 27 | var code: String? { get } 28 | var lineNumber: Int? { get } 29 | 30 | // identity 31 | var id: ID { get } 32 | typealias ID = String 33 | } 34 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Artifact Icon/ArtifactIconView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ArtifactIconView: View 4 | { 5 | init(icon: ArtifactIcon, size: CGFloat? = nil) 6 | { 7 | self.icon = icon 8 | self.size = size 9 | } 10 | 11 | var body: some View 12 | { 13 | switch icon 14 | { 15 | case .imageName(let imageName): 16 | Image(imageName) 17 | .resizable() 18 | .aspectRatio(contentMode: .fit) 19 | .frame(height: size) 20 | 21 | case .systemImage(let name, let fillColor): 22 | Image(systemName: name) 23 | .resizable() 24 | .aspectRatio(contentMode: .fit) 25 | .foregroundColor(.init(fillColor)) 26 | .frame(height: size) 27 | } 28 | } 29 | 30 | let icon: ArtifactIcon 31 | let size: CGFloat? 32 | } 33 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/CodebaseAnalysisView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CodebaseAnalysisView: View 4 | { 5 | var body: some View 6 | { 7 | DoubleSidebarView(showLeftSidebar: $displayOptions.showsLeftSidebar, 8 | showRightSidebar: $displayOptions.showsRightSidebar) 9 | { 10 | CodebaseCentralView(analysis: analysis, 11 | displayOptions: displayOptions) 12 | } 13 | leftSidebar: 14 | { 15 | CodebaseNavigatorView(analysis: analysis, 16 | showsLinesOfCode: $displayOptions.showsLinesOfCode) 17 | } 18 | rightSidebar: 19 | { 20 | CodebaseInspectorView(selectedArtifact: analysis.selectedArtifact) 21 | } 22 | } 23 | 24 | @ObservedObject var analysis: CodebaseAnalysis 25 | @ObservedObject var displayOptions: WindowDisplayOptions 26 | } 27 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Code Artifact Conformance/CodeFileArtifact+CodeArtifact.swift: -------------------------------------------------------------------------------- 1 | import FoundationToolz 2 | 3 | extension CodeFileArtifact: SearchableCodeArtifact 4 | { 5 | func contains(fileLine: Int) -> Bool 6 | { 7 | lines.count > fileLine 8 | } 9 | } 10 | 11 | extension CodeFileArtifact: CodeArtifact 12 | { 13 | var parts: [any CodeArtifact] 14 | { 15 | symbolGraph.nodesByID.values.map { $0.value } 16 | } 17 | 18 | var intrinsicSizeInLinesOfCode: Int? { lines.count } 19 | 20 | var kindName: String 21 | { 22 | [name.fileExtension()?.capitalized, "File"] 23 | .compactMap({ $0 }) 24 | .joined(separator: " ") 25 | } 26 | 27 | var lineNumber: Int? { nil } 28 | 29 | // MARK: - Hashability 30 | 31 | static func == (lhs: CodeFileArtifact, 32 | rhs: CodeFileArtifact) -> Bool { lhs === rhs } 33 | 34 | func hash(into hasher: inout Hasher) { hasher.combine(id) } 35 | } 36 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/Metrics.swift: -------------------------------------------------------------------------------- 1 | struct Metrics: Sendable 2 | { 3 | // MARK: - Cycle Metrics 4 | 5 | var portionOfPartsInCycles: Double 6 | { 7 | guard let partLOCs = linesOfCodeOfParts, partLOCs > 0, 8 | let partLOCsInCycles = linesOfCodeOfPartsInCycles 9 | else { return 0 } 10 | 11 | return Double(partLOCsInCycles) / Double(partLOCs) 12 | } 13 | 14 | var linesOfCodeInCycles: Int 15 | { 16 | isInACycle ?? false ? linesOfCode ?? 0 : linesOfCodeOfPartsInCycles ?? 0 17 | } 18 | 19 | var isInACycle: Bool? 20 | var linesOfCodeOfPartsInCycles: Int? 21 | 22 | // MARK: - Sort Rank 23 | 24 | var sortRank: Int = 0 25 | 26 | // MARK: - Size 27 | 28 | var linesOfCode: Int? 29 | var linesOfCodeOfParts: Int? 30 | var sizeRelativeToAllPartsInScope: Double? 31 | 32 | // MARK: - Dependency "Ranking" 33 | 34 | var componentRank: Int? 35 | var sccIndexTopologicallySorted: Int? 36 | } 37 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Display Mode/DisplayModePicker.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct DisplayModePicker: View 4 | { 5 | var body: some View 6 | { 7 | Picker("Display Mode", selection: $displayMode) 8 | { 9 | ForEach(DisplayMode.allCases) { $0.label } 10 | } 11 | .pickerStyle(.segmented) 12 | .help("Switch between architecture and code (⌘→, ⌘←)") 13 | } 14 | 15 | @Binding var displayMode: DisplayMode 16 | } 17 | 18 | private extension DisplayMode 19 | { 20 | var label: some View 21 | { 22 | let content = labelText 23 | return Label(content.name, systemImage: content.systemImage) 24 | } 25 | 26 | private var labelText: (name: String, systemImage: String) 27 | { 28 | switch self 29 | { 30 | case .treeMap: 31 | return ("Tree Map", "rectangle.3.group") 32 | case .code: 33 | return ("Code", "chevron.left.forwardslash.chevron.right") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Code Artifact Conformance/CodeSymbolArtifact+CodeArtifact.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import SwiftyToolz 3 | 4 | extension CodeSymbolArtifact: SearchableCodeArtifact 5 | { 6 | func contains(fileLine: Int) -> Bool 7 | { 8 | fileLine >= range.start.line && fileLine <= range.end.line 9 | } 10 | } 11 | 12 | extension CodeSymbolArtifact: CodeArtifact 13 | { 14 | var parts: [any CodeArtifact] 15 | { 16 | subsymbolGraph.nodesByID.values.map { $0.value } 17 | } 18 | 19 | var intrinsicSizeInLinesOfCode: Int? { (range.end.line - range.start.line) + 1 } 20 | 21 | static var kindNames: [String] { LSPDocumentSymbol.SymbolKind.names } 22 | 23 | var kindName: String { kind?.name ?? "Unknown Kind of Symbol" } 24 | 25 | var lineNumber: Int? { selectionRange.start.line } 26 | 27 | // MARK: - Hashability 28 | 29 | static func == (lhs: CodeSymbolArtifact, 30 | rhs: CodeSymbolArtifact) -> Bool { lhs === rhs } 31 | 32 | func hash(into hasher: inout Hasher) { hasher.combine(id) } 33 | } 34 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Concrete Code Artifacts/CodeSymbolArtifact.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import SwiftNodes 3 | 4 | final class CodeSymbolArtifact: Identifiable, Hashable, Sendable 5 | { 6 | // MARK: - Initialization 7 | 8 | init(name: String, 9 | kind: LSPDocumentSymbol.SymbolKind?, 10 | range: LSPRange, 11 | selectionRange: LSPRange, 12 | code: String, 13 | subsymbolGraph: Graph) 14 | { 15 | self.name = name 16 | self.kind = kind 17 | self.range = range 18 | self.selectionRange = selectionRange 19 | self.code = code 20 | self.subsymbolGraph = subsymbolGraph 21 | } 22 | 23 | // MARK: - Graph Structure 24 | 25 | let subsymbolGraph: Graph 26 | 27 | // MARK: - Basics 28 | 29 | let id: CodeArtifact.ID = .randomID() 30 | let name: String 31 | let kind: LSPDocumentSymbol.SymbolKind? 32 | let range: LSPRange 33 | let selectionRange: LSPRange 34 | let code: String? 35 | } 36 | -------------------------------------------------------------------------------- /Code/XPC Experiments/ProcessServiceTest/ProcessServiceTestMain.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftyToolz 3 | import ProcessServiceServer 4 | import ProcessServiceShared 5 | 6 | @main 7 | enum ProcessServiceTestMain 8 | { 9 | static func main() 10 | { 11 | let delegate = ServiceDelegate() 12 | let listener = NSXPCListener.service() 13 | 14 | listener.delegate = delegate 15 | log("✅ Will resume listener on service side") 16 | listener.resume() 17 | } 18 | } 19 | 20 | final class ServiceDelegate: NSObject, NSXPCListenerDelegate 21 | { 22 | func listener(_ listener: NSXPCListener, shouldAcceptNewConnection 23 | newConnection: NSXPCConnection) -> Bool 24 | { 25 | do 26 | { 27 | _ = try newConnection.configureProcessServiceServer() 28 | } 29 | catch 30 | { 31 | log(error.readable) 32 | return false 33 | } 34 | 35 | newConnection.activate() 36 | 37 | log("✅ Did activate connection on service side") 38 | 39 | return true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/PathBar.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import OrderedCollections 3 | 4 | @MainActor 5 | public class PathBar: ObservableObject 6 | { 7 | public func select(_ artifactVM: ArtifactViewModel?) 8 | { 9 | artifactVMStack.elements = artifactVM?.getPath() ?? [] 10 | } 11 | 12 | public func add(_ artifactVM: ArtifactViewModel) 13 | { 14 | remove(artifactVM) 15 | 16 | artifactVMStack.append(artifactVM) 17 | } 18 | 19 | public func remove(_ artifactVM: ArtifactViewModel) 20 | { 21 | if let firstIndex = artifactVMStack.firstIndex(of: artifactVM) 22 | { 23 | let lastIndex = artifactVMStack.count - 1 24 | artifactVMStack.removeSubrange(firstIndex ... lastIndex) 25 | } 26 | } 27 | 28 | @Published public private(set) var artifactVMStack = OrderedSet() 29 | } 30 | 31 | private extension ArtifactViewModel 32 | { 33 | func getPath() -> [ArtifactViewModel] 34 | { 35 | (scope?.getPath() ?? []) + [self] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Code/App/Codebase/Load/CodebaseFileDocument.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import FoundationToolz 3 | import UniformTypeIdentifiers 4 | 5 | @available(macOS 11.0, *) 6 | struct CodebaseFileDocument: FileDocument, Codable 7 | { 8 | // load from file 9 | init(configuration: ReadConfiguration) throws 10 | { 11 | let selfData = try configuration.file.regularFileContents.unwrap() 12 | self = try CodebaseFileDocument(jsonData: selfData) 13 | } 14 | 15 | // write to file 16 | func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper 17 | { 18 | // avoid white space from pretty printing, avoid escaping slashes 19 | .init(regularFileWithContents: try encode(options: .withoutEscapingSlashes)) 20 | } 21 | 22 | // store optional codebase 23 | init(codebase: CodeFolder? = nil) 24 | { 25 | self.codebase = codebase 26 | } 27 | 28 | var codebase: CodeFolder? 29 | static var readableContentTypes: [UTType] = [.codebase] 30 | } 31 | 32 | extension UTType 33 | { 34 | static let codebase = UTType(exportedAs: "com.flowtoolz.codeface.codebase") 35 | } 36 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/CodebaseCentralView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CodebaseCentralView: View 4 | { 5 | var body: some View 6 | { 7 | VStack(spacing: 0) 8 | { 9 | TopBar(analysis: analysis) 10 | 11 | // AnalysisContentView has the selectedArtifact explicitly and can thereby directly observe the properties on analysis.selectedArtifact 12 | CodebaseContentView(analysis: analysis, 13 | selectedArtifact: analysis.selectedArtifact) 14 | 15 | if GlobalSettings.shared.showPurchasePanel 16 | { 17 | PurchasePanelView(isExpanded: $displayOptions.showsSubscriptionPanel, 18 | collapsedVisibility: appStoreClient.ownsProducts ? .hidden : .banner) 19 | } 20 | } 21 | .animation(.default, value: displayOptions.showsSubscriptionPanel) 22 | } 23 | 24 | @ObservedObject var analysis: CodebaseAnalysis 25 | @ObservedObject var appStoreClient = AppStoreClient.shared 26 | @ObservedObject var displayOptions: WindowDisplayOptions 27 | } 28 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Architecture View/ArtifactHeaderView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ArtifactHeaderView: View 4 | { 5 | var body: some View 6 | { 7 | let fontSize = artifactVM.fontSize 8 | 9 | HStack(alignment: .center, spacing: 0) 10 | { 11 | ArtifactIconView(icon: artifactVM.icon, 12 | size: fontSize) 13 | 14 | let collapseHorizontally = artifactVM.shouldCollapseHorizontally 15 | 16 | Text(collapseHorizontally ? "" : artifactVM.displayName) 17 | .frame(maxWidth: collapseHorizontally ? 0 : .infinity, 18 | alignment: .leading) 19 | .font(.system(size: fontSize, 20 | weight: .medium, 21 | design: artifactVM.fontDesign)) 22 | .foregroundColor(.primary) 23 | .drawingGroup() // so the opacity animation works and the text does not just plop in ... 24 | .opacity(artifactVM.shouldShowName ? 1 : 0) 25 | .padding(.leading, 26 | collapseHorizontally ? 0 : artifactVM.fontSize / 3) 27 | } 28 | } 29 | 30 | @ObservedObject var artifactVM: ArtifactViewModel 31 | } 32 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/PathBarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PathBarView: View 4 | { 5 | var body: some View 6 | { 7 | HStack(alignment: .firstTextBaseline, spacing: 0) 8 | { 9 | ForEach(overviewBar.artifactVMStack.indices, id: \.self) 10 | { 11 | let vm = overviewBar.artifactVMStack[$0] 12 | 13 | if $0 > 0 14 | { 15 | Image(systemName: "chevron.compact.right") 16 | .foregroundColor(.secondary) 17 | .imageScale(.large) 18 | .padding([.leading, .trailing], 3) 19 | } 20 | 21 | Image(systemName: vm.iconSystemImageName) 22 | .foregroundColor(.init(vm.iconFillColor)) 23 | .padding(.trailing, 3) 24 | 25 | Text(vm.codeArtifact.name) 26 | .font(.callout) 27 | .fixedSize(horizontal: false, vertical: false) 28 | .frame(maxHeight: .infinity) 29 | } 30 | 31 | Spacer() 32 | } 33 | .padding([.leading, .trailing]) 34 | .frame(height: 28) 35 | } 36 | 37 | @ObservedObject var overviewBar: PathBar 38 | } 39 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/CodeView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CodeView: View 4 | { 5 | var body: some View 6 | { 7 | if let code = artifact.codeArtifact.code 8 | { 9 | TextEditor(text: .constant("\n" + code)) 10 | .font(.system(size: CentralViewStyle.fontSize, design: .monospaced)) 11 | .scrollContentBackground(.hidden) // must be hidden to see background 12 | .padding(.leading) 13 | .background(colorScheme == .dark ? .black : .white) 14 | } 15 | else 16 | { 17 | VStack 18 | { 19 | Spacer () 20 | 21 | Label { 22 | Text(artifact.codeArtifact.name) 23 | } icon: { 24 | ArtifactIconView(icon: artifact.icon, size: 20) 25 | } 26 | .font(.title) 27 | 28 | Text("Select a contained file or symbol to show their code.") 29 | .font(.title3) 30 | .padding(.top) 31 | 32 | Spacer() 33 | } 34 | .foregroundColor(.secondary) 35 | .padding() 36 | } 37 | } 38 | 39 | let artifact: ArtifactViewModel 40 | 41 | @Environment(\.colorScheme) private var colorScheme 42 | } 43 | -------------------------------------------------------------------------------- /XCodeProject/App Store Testing.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "4EA5C95E", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | 8 | ], 9 | "settings" : { 10 | "_compatibilityTimeRate" : 6, 11 | "_timeRate" : 15 12 | }, 13 | "subscriptionGroups" : [ 14 | { 15 | "id" : "88E063D3", 16 | "localizations" : [ 17 | 18 | ], 19 | "name" : "Codeface Subscriptions", 20 | "subscriptions" : [ 21 | { 22 | "adHocOffers" : [ 23 | 24 | ], 25 | "codeOffers" : [ 26 | 27 | ], 28 | "displayPrice" : "1.99", 29 | "familyShareable" : false, 30 | "groupNumber" : 1, 31 | "internalID" : "6269D6E6", 32 | "introductoryOffer" : null, 33 | "localizations" : [ 34 | { 35 | "description" : "Support development and remove the banner", 36 | "displayName" : "Sponsor a Coffee", 37 | "locale" : "en_US" 38 | } 39 | ], 40 | "productID" : "io.codeface.subscription.level1", 41 | "recurringSubscriptionPeriod" : "P1M", 42 | "referenceName" : "Codeface Subscription Level 1", 43 | "subscriptionGroupID" : "88E063D3", 44 | "type" : "RecurringSubscription" 45 | } 46 | ] 47 | } 48 | ], 49 | "version" : { 50 | "major" : 2, 51 | "minor" : 0 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Code/App/Codebase/CodeSymbol.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import SwiftyToolz 3 | 4 | /** 5 | ⛔️ Do not change! This is part of the ".codebase" file format. 6 | */ 7 | final class CodeSymbol: Codable, Sendable 8 | { 9 | init(lspDocumentySymbol: LSPDocumentSymbol, 10 | referenceLocations: [ReferenceLocation], 11 | children: [CodeSymbol]) throws 12 | { 13 | guard let decodedKind = lspDocumentySymbol.decodedKind else 14 | { 15 | throw "Could not decode LSP document symbol kind of value \(lspDocumentySymbol.kind)" 16 | } 17 | 18 | name = lspDocumentySymbol.name 19 | kind = decodedKind 20 | range = lspDocumentySymbol.range 21 | selectionRange = lspDocumentySymbol.selectionRange 22 | references = referenceLocations.isEmpty ? nil : referenceLocations 23 | 24 | self.children = children.isEmpty ? nil : children 25 | } 26 | 27 | let name: String 28 | let kind: LSPDocumentSymbol.SymbolKind 29 | let range: LSPRange 30 | let selectionRange: LSPRange 31 | 32 | let references: [ReferenceLocation]? 33 | 34 | struct ReferenceLocation: Codable, Sendable 35 | { 36 | /// without root folder, like: `"SubfolderOfRoot/Deeper/Subfolders/myFile.swift"` 37 | let filePathRelativeToRoot: String 38 | 39 | let range: LSPRange 40 | } 41 | 42 | let children: [CodeSymbol]? 43 | } 44 | -------------------------------------------------------------------------------- /Code/App/Codebase Window/Codebase Window View/SecondaryToolbarButtons.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SecondaryToolbarButtons: View 4 | { 5 | var body: some View 6 | { 7 | if let analysis 8 | { 9 | ToolbarFilterIndicator(analysis: analysis) 10 | } 11 | } 12 | 13 | private var analysis: CodebaseAnalysis? 14 | { 15 | codebaseProcessor.state.analysis 16 | } 17 | 18 | @ObservedObject var codebaseProcessor: CodebaseProcessor 19 | } 20 | 21 | struct ToolbarFilterIndicator: View 22 | { 23 | var body: some View 24 | { 25 | if !analysis.search.term.isEmpty 26 | { 27 | Button 28 | { 29 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 30 | { 31 | analysis.set(searchTerm: "") 32 | } 33 | } 34 | label: 35 | { 36 | HStack 37 | { 38 | Text("Search Filter:") 39 | 40 | Text(analysis.search.term) 41 | .foregroundColor(.accentColor) 42 | 43 | Image(systemName: "xmark") 44 | .imageScale(.medium) 45 | } 46 | } 47 | .help("Clear the Search Term") 48 | } 49 | } 50 | 51 | @ObservedObject var analysis: CodebaseAnalysis 52 | } 53 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Architecture View Model/ArtifactViewModel+BorderColor.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | extension ArtifactViewModel 4 | { 5 | func borderColor(forBackgroundBrightness bgBrightness: Double) -> UXColor 6 | { 7 | if isInFocus { return .system(.accent) } 8 | 9 | let errorPortion = metrics.portionOfPartsInCycles 10 | 11 | let defaultBorderColor = lineColor(forBGBrightness: bgBrightness) 12 | 13 | return .dynamic(defaultBorderColor.mixed(with: errorPortion, of: .red)) 14 | } 15 | } 16 | 17 | func lineColor(forBGBrightness bgBrightness: Double) -> DynamicColor 18 | { 19 | .in(light: .gray(brightness: lineBrightness(forBGBrightness: bgBrightness, 20 | isDarkMode: false)), 21 | darkness: .gray(brightness: lineBrightness(forBGBrightness: bgBrightness, 22 | isDarkMode: true))) 23 | } 24 | 25 | func lineBrightness(forBGBrightness bgBrightness: Double, 26 | isDarkMode: Bool) -> Double 27 | { 28 | (bgBrightness + (isDarkMode ? 0.2 : -0.4)).clampedToFactor() 29 | } 30 | 31 | extension Double 32 | { 33 | func clampedToFactor() -> Double 34 | { 35 | clamped(to: 0 ... 1) 36 | } 37 | } 38 | 39 | extension Comparable 40 | { 41 | func clamped(to limits: ClosedRange) -> Self 42 | { 43 | return min(max(self, limits.lowerBound), limits.upperBound) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Architecture View/DependencyView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | struct DependencyView: View 5 | { 6 | var body: some View 7 | { 8 | Arrow(from: CGPoint(viewModel.sourcePoint), 9 | to: CGPoint(viewModel.targetPoint), 10 | size: size) 11 | .stroke(style: .init(lineWidth: size / 3, lineCap: .round)) 12 | .foregroundColor(Color(color)) 13 | } 14 | 15 | private var color: UXColor 16 | { 17 | if isHighlighted 18 | { 19 | return isPartOfCycle ? .system(.purple) : .system(.accent) 20 | } 21 | else 22 | { 23 | return isPartOfCycle ? .system(.red) : .rgba(.gray(brightness: defaultBrightness)) 24 | } 25 | } 26 | 27 | private var isHighlighted: Bool { source.isInFocus || target.isInFocus } 28 | 29 | private var isPartOfCycle: Bool 30 | { 31 | guard let sourceSCCIndex = source.metrics.sccIndexTopologicallySorted, 32 | let targetSCCIndex = target.metrics.sccIndexTopologicallySorted 33 | else { return false } 34 | 35 | return sourceSCCIndex == targetSCCIndex 36 | } 37 | 38 | @ObservedObject var source: ArtifactViewModel 39 | @ObservedObject var target: ArtifactViewModel 40 | @ObservedObject var viewModel: DependencyVM 41 | 42 | let defaultBrightness: Double 43 | let size: Double 44 | } 45 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/Path Bar/PathBarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PathBarView: View 4 | { 5 | var body: some View 6 | { 7 | HStack(alignment: .center, spacing: 0) 8 | { 9 | ForEach(overviewBar.artifactVMStack.indices, id: \.self) 10 | { 11 | let artifactVM = overviewBar.artifactVMStack[$0] 12 | 13 | if $0 > 0 14 | { 15 | Image(systemName: "chevron.compact.right") 16 | .foregroundColor(.secondary) 17 | .imageScale(.large) 18 | .padding([.leading, .trailing], 3) 19 | } 20 | 21 | ArtifactIconView(icon: artifactVM.icon, size: 14) 22 | .padding(.trailing, 3) 23 | 24 | Text(artifactVM.displayName) 25 | .font(.callout) 26 | .fixedSize(horizontal: false, vertical: false) 27 | .frame(maxHeight: .infinity) 28 | } 29 | 30 | Spacer() 31 | } 32 | .padding([.leading, .trailing]) 33 | .frame(height: 28) 34 | } 35 | 36 | @ObservedObject var overviewBar: PathBar 37 | } 38 | 39 | extension ArtifactViewModel 40 | { 41 | func getPath() -> [ArtifactViewModel] 42 | { 43 | (scope?.getPath() ?? []) + [self] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/NSApplication+NSWindow.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import SwiftyToolz 3 | 4 | extension NSApplication 5 | { 6 | func debugLogWindows() 7 | { 8 | guard !windows.isEmpty else 9 | { 10 | return log("🪟 There are no windows") 11 | } 12 | 13 | let output: String = windows.map 14 | { 15 | "🪟 Window:\n\tid = \($0.identifier?.rawValue ?? "nil")\n\tisVisible = \($0.isVisible)\n\tisKeyWindow = \($0.isKeyWindow)" 16 | } 17 | .joined(separator: "\n") 18 | 19 | log(output) 20 | } 21 | 22 | func closeWindows(where shouldClose: (NSWindow) -> Bool) 23 | { 24 | for window in windows 25 | { 26 | if shouldClose(window) 27 | { 28 | log(verbose: "🪟 gonna close window with id: \(window.identifier?.rawValue ?? "nil")") 29 | window.close() 30 | } 31 | } 32 | } 33 | 34 | func closeWindowIfOpen(id: String) 35 | { 36 | if let window = NSApp.window(withID: id) 37 | { 38 | log(verbose: "🪟 gonna close window with id: \(id)") 39 | window.close() 40 | } 41 | } 42 | 43 | func windowExists(withID id: String) -> Bool 44 | { 45 | window(withID: id) != nil 46 | } 47 | 48 | func window(withID id: String) -> NSWindow? 49 | { 50 | windows.first { $0.identifier?.rawValue == id } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Code/Proofs of Concept/ConcurrencyPOC.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ConcurrencyPOCView: View 4 | { 5 | var body: some View 6 | { 7 | VStack 8 | { 9 | Text("Number = \(viewModel.number)") 10 | 11 | Button("Start") 12 | { 13 | Task // just to have an async context 14 | { 15 | let bgCalcResult = await viewModel.calculateInBackground() 16 | 17 | result = "\(bgCalcResult)" 18 | } 19 | } 20 | 21 | Text("Result: \(result)") 22 | } 23 | } 24 | 25 | @StateObject private var viewModel = ViewModel() 26 | @State private var result = "" 27 | } 28 | 29 | @MainActor // vm is on main actor so view can observe it 30 | class ViewModel: ObservableObject 31 | { 32 | func calculateInBackground() async -> Int 33 | { 34 | // ❗️ rather remember the task if we have to cancel it 35 | await Task.detached // leave main actor to not block it 36 | { 37 | for _ in 1 ... 10000 // ❗️ also check Task.isCancelled if it might be cancelled 38 | { 39 | await MainActor.run // go back to main actor for progress update 40 | { 41 | self.number += 1 42 | } 43 | } 44 | 45 | return 1234567 46 | } 47 | .value 48 | } 49 | 50 | @Published var number = 0 51 | } 52 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "32-1.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "256-1.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "512-1.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Navigator View/SidebarLabel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUIToolzOLD 2 | import SwiftUI 3 | 4 | struct SidebarLabel: View 5 | { 6 | var body: some View 7 | { 8 | Label 9 | { 10 | Text(compactDisplayName) 11 | .font(.system(.title3, design: artifactVM.fontDesign)) 12 | 13 | if showsLinesOfCode, let linesOfCode = artifactVM.metrics.linesOfCode 14 | { 15 | Spacer() 16 | 17 | Text("\(linesOfCode)") 18 | .foregroundColor(.init(artifactVM.linesOfCodeColor)) 19 | .monospacedDigit() 20 | } 21 | } 22 | icon: 23 | { 24 | ArtifactIconView(icon: artifactVM.icon, size: 14) 25 | } 26 | } 27 | 28 | private var compactDisplayName: String 29 | { 30 | switch artifactVM.kind 31 | { 32 | case .folder(let folderVM): 33 | let components = folderVM.name.components(separatedBy: "/") 34 | 35 | if components.count > 1, let firstComponent = components.first 36 | { 37 | return firstComponent + " …" 38 | } 39 | else 40 | { 41 | return artifactVM.displayName 42 | } 43 | 44 | default: 45 | return artifactVM.displayName 46 | } 47 | } 48 | 49 | @ObservedObject var artifactVM: ArtifactViewModel 50 | @Binding var showsLinesOfCode: Bool 51 | } 52 | -------------------------------------------------------------------------------- /Code/App/Codebase/Load/CodeFolder+File System.swift: -------------------------------------------------------------------------------- 1 | import FoundationToolz 2 | import Foundation 3 | import SwiftyToolz 4 | 5 | extension CodeFolder 6 | { 7 | convenience init?(_ folderURL: URL, codeFileEndings: [String]) throws 8 | { 9 | let fileManager = FileManager.default 10 | 11 | let urls = fileManager.items(inDirectory: folderURL, recursive: false) 12 | 13 | var files = [CodeFile]() 14 | var subfolders = [CodeFolder]() 15 | 16 | for url in urls 17 | { 18 | if url.isDirectory 19 | { 20 | if let subfolder = try CodeFolder(url, codeFileEndings: codeFileEndings) 21 | { 22 | subfolders += subfolder 23 | } 24 | } 25 | else if codeFileEndings.contains(url.pathExtension) 26 | { 27 | files += try CodeFile(url) 28 | } 29 | } 30 | 31 | if files.count + subfolders.count == 0 { return nil } 32 | 33 | self.init(name: folderURL.lastPathComponent, 34 | files: files, 35 | subfolders: subfolders) 36 | } 37 | 38 | func printSize() 39 | { 40 | if let encoded = encode() 41 | { 42 | log(name + " size: \(Double(encoded.count) / 1000_000) MB") 43 | } 44 | } 45 | } 46 | 47 | private extension CodeFile 48 | { 49 | convenience init(_ file: URL) throws 50 | { 51 | self.init(name: file.lastPathComponent, 52 | code: try String(contentsOf: file, encoding: .utf8)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /XCodeProject/App Store Synced.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "E135B29C", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | 8 | ], 9 | "settings" : { 10 | "_applicationInternalID" : "1578175415", 11 | "_askToBuyEnabled" : false, 12 | "_compatibilityTimeRate" : 6, 13 | "_developerTeamID" : "8T3R57GCBV", 14 | "_lastSynchronizedDate" : 701531547.529796, 15 | "_renewalBillingIssuesEnabled" : false, 16 | "_timeRate" : 15 17 | }, 18 | "subscriptionGroups" : [ 19 | { 20 | "id" : "21268950", 21 | "localizations" : [ 22 | 23 | ], 24 | "name" : "Codeface Subscriptions", 25 | "subscriptions" : [ 26 | { 27 | "adHocOffers" : [ 28 | 29 | ], 30 | "codeOffers" : [ 31 | 32 | ], 33 | "displayPrice" : "3.99", 34 | "familyShareable" : false, 35 | "groupNumber" : 1, 36 | "internalID" : "6446200754", 37 | "introductoryOffer" : null, 38 | "localizations" : [ 39 | { 40 | "description" : "Introductory plan for early supporters", 41 | "displayName" : "Early Bird", 42 | "locale" : "en_US" 43 | } 44 | ], 45 | "productID" : "io.codeface.subscription.level1", 46 | "recurringSubscriptionPeriod" : "P1M", 47 | "referenceName" : "Codeface Subscription Level 1", 48 | "subscriptionGroupID" : "21268950", 49 | "type" : "RecurringSubscription" 50 | } 51 | ] 52 | } 53 | ], 54 | "version" : { 55 | "major" : 2, 56 | "minor" : 0 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Code/App/Codebase Processor/Codebase Processor/CodebaseProcessorSteps.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftLSP 3 | import SwiftyToolz 4 | 5 | /// Namespace to get the actual processing off the main actor 6 | @BackgroundActor 7 | enum CodebaseProcessorSteps 8 | { 9 | static func readFolder(from location: LSP.CodebaseLocation) throws -> CodeFolder? 10 | { 11 | try location.folder.mapSecurityScoped 12 | { 13 | guard let codeFolder = try CodeFolder($0, codeFileEndings: location.codeFileEndings) else 14 | { 15 | throw "Project folder contains no code files with the specified file endings\nFolder: \($0.absoluteString)\nFile endings: \(location.codeFileEndings)" 16 | } 17 | 18 | return codeFolder 19 | } 20 | } 21 | 22 | static func retrieveSymbolsAndReferences(for codebase: CodeFolder, 23 | from server: LSP.Server, 24 | codebaseRootFolder: URL) async throws -> CodeFolder 25 | { 26 | try await codebase.retrieveSymbolsAndReferences(from: server, 27 | codebaseRootFolder: codebaseRootFolder) 28 | } 29 | 30 | static func generateArchitecture(from folder: CodeFolder) -> CodeFolderArtifact 31 | { 32 | var extraReferences = [CodeSymbol.ReferenceLocation]() 33 | 34 | return CodeFolderArtifact(codeFolder: folder, 35 | pathInRootFolder: .root, 36 | additionalReferences: &extraReferences) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/Path Bar/PathBar.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | @MainActor 4 | class PathBar: ObservableObject 5 | { 6 | // MARK: - Initialize 7 | 8 | init(selectionPublisher: any Publisher) 9 | { 10 | self.selectionPublisher = selectionPublisher 11 | observeSelection() 12 | } 13 | 14 | // MARK: - Observe Root Selection 15 | 16 | private func observeSelection() 17 | { 18 | // TODO: does this really fire immediately since the unnderlying publisher is a CurrentValueSubject? Otherwise we wouldn't receive then initial selection ... 19 | observation = selectionPublisher.sink 20 | { 21 | [weak self] newSelection in self?.select(newSelection) 22 | } 23 | } 24 | 25 | private var observation: AnyCancellable? = nil 26 | 27 | private func select(_ artifactVM: ArtifactViewModel?) 28 | { 29 | artifactVMStack = artifactVM?.getPath() ?? [] 30 | } 31 | 32 | var selectionPublisher: any Publisher 33 | 34 | // MARK: - Manage Whole Stack 35 | 36 | func add(_ artifactVM: ArtifactViewModel) 37 | { 38 | remove(artifactVM) 39 | 40 | artifactVMStack.append(artifactVM) 41 | } 42 | 43 | func remove(_ artifactVM: ArtifactViewModel) 44 | { 45 | if let firstIndex = artifactVMStack.firstIndex(of: artifactVM) 46 | { 47 | let lastIndex = artifactVMStack.count - 1 48 | artifactVMStack.removeSubrange(firstIndex ... lastIndex) 49 | } 50 | } 51 | 52 | @Published private(set) var artifactVMStack = [ArtifactViewModel]() 53 | } 54 | 55 | -------------------------------------------------------------------------------- /CI/ReleaseBot/Sources/AppStoreCredentials.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct AppStoreCredentials { 4 | 5 | /// reads command line arguments first. if they're not provided, it looks for files temporarily provided during development. if there are no files, it asks the user to type name and password. 6 | static func retrieve() throws -> AppStoreCredentials { 7 | // 1. command line 8 | 9 | let arguments = CommandLine.arguments 10 | 11 | if arguments.count == 3 { 12 | return .init(username: arguments[1], password: arguments[2]) 13 | } 14 | 15 | print("🤖 Usage: ReleaseBot ") 16 | 17 | // 2. dev files 18 | 19 | let devCICredentialsFolder = "/Users/seb/Library/Mobile Documents/com~apple~CloudDocs/iCloud/SOFTWARE DEV/Codeface Private/Development/CI credentials" 20 | 21 | let usernameFile = devCICredentialsFolder + "/app_store_connect_user.txt" 22 | let passwordFile = devCICredentialsFolder + "/app_store_connect_password.txt" 23 | 24 | if let username = try? String(contentsOfFile: usernameFile), 25 | let password = try? String(contentsOfFile: passwordFile) { 26 | return .init(username: username, password: password) 27 | } 28 | 29 | // 3. user input 30 | 31 | print("Enter username:") 32 | let username = readLine(strippingNewline: true) ?? "" 33 | 34 | print("Enter password:") 35 | let password = readLine(strippingNewline: true) ?? "" 36 | 37 | return .init(username: username, password: password) 38 | } 39 | 40 | let username: String 41 | let password: String 42 | } 43 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/CodeFolderArtifact+SortMetric.swift: -------------------------------------------------------------------------------- 1 | import SwiftNodes 2 | import SwiftyToolz 3 | 4 | @BackgroundActor 5 | extension CodeFolderArtifact 6 | { 7 | func calculateSortMetricsRecursively() 8 | { 9 | // depth first! this is important 10 | for part in partGraph.values 11 | { 12 | switch part.kind 13 | { 14 | case .subfolder(let subfolder): 15 | subfolder.calculateSortMetricsRecursively() 16 | case .file(let file): 17 | file.calculateSortMetricsRecursively() 18 | } 19 | } 20 | 21 | partGraph.calculateSortMetrics() 22 | } 23 | } 24 | 25 | @BackgroundActor 26 | private extension CodeFileArtifact 27 | { 28 | func calculateSortMetricsRecursively() 29 | { 30 | symbolGraph.values.forEach { $0.calculateSortMetricsRecursively() } 31 | 32 | symbolGraph.calculateSortMetrics() 33 | } 34 | } 35 | 36 | @BackgroundActor 37 | private extension CodeSymbolArtifact 38 | { 39 | func calculateSortMetricsRecursively() 40 | { 41 | subsymbolGraph.values.forEach { $0.calculateSortMetricsRecursively() } 42 | 43 | subsymbolGraph.calculateSortMetrics() 44 | } 45 | } 46 | 47 | @BackgroundActor 48 | private extension Graph where NodeValue: CodeArtifact & Identifiable, NodeID == CodeArtifact.ID 49 | { 50 | func calculateSortMetrics() 51 | { 52 | nodes 53 | .sorted 54 | { 55 | $0.goesBefore($1) 56 | } 57 | .forEachIndex 58 | { 59 | node, nodeIndex in 60 | 61 | node.value.metrics.sortRank = nodeIndex 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Code/App/Main Menu/FindAndFilterMenuOptions.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | struct FindAndFilterMenuOptions: View 5 | { 6 | var body: some View 7 | { 8 | Button("Find and Filter") 9 | { 10 | withAnimation(.easeInOut(duration: Search.toggleAnimationDuration)) 11 | { 12 | analysis?.set(searchBarIsVisible: true) 13 | } 14 | 15 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 16 | { 17 | analysis?.set(fieldIsFocused: true) 18 | } 19 | } 20 | .disabled(analysis == nil) 21 | .keyboardShortcut("f") 22 | 23 | Button("Toggle the Search Filter") 24 | { 25 | guard let analysis else 26 | { 27 | log(warning: "When there's no analysis, this menu option shouldn't be displayed.") 28 | return 29 | } 30 | 31 | let searchBarWillBeVisible = !analysis.search.barIsShown 32 | 33 | withAnimation(.easeInOut(duration: Search.toggleAnimationDuration)) 34 | { 35 | analysis.set(searchBarIsVisible: searchBarWillBeVisible) 36 | } 37 | 38 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 39 | { 40 | analysis.set(fieldIsFocused: searchBarWillBeVisible) 41 | } 42 | } 43 | .disabled(analysis == nil) 44 | .keyboardShortcut("f", modifiers: [.shift, .command]) 45 | } 46 | 47 | private var analysis: CodebaseAnalysis? 48 | { 49 | codebaseProcessor.state.analysis 50 | } 51 | 52 | @ObservedObject var codebaseProcessor: CodebaseProcessor 53 | } 54 | -------------------------------------------------------------------------------- /Code/App/Codebase Window/Codebase Window View/PrimaryToolbarButtons.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PrimaryToolbarButtons: View 4 | { 5 | var body: some View 6 | { 7 | if let analysis 8 | { 9 | Button(systemImageName: "magnifyingglass") 10 | { 11 | let searchBarWillBeVisible = !analysis.search.barIsShown 12 | 13 | withAnimation(.easeInOut(duration: Search.toggleAnimationDuration)) 14 | { 15 | analysis.set(searchBarIsVisible: searchBarWillBeVisible) 16 | } 17 | 18 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 19 | { 20 | analysis.set(fieldIsFocused: searchBarWillBeVisible) 21 | } 22 | } 23 | .help("Toggle the Search Filter (⇧⌘F)") 24 | 25 | UpdatingDisplayModePicker(analysis: analysis) 26 | 27 | Button(systemImageName: "sidebar.right") 28 | { 29 | withAnimation 30 | { 31 | displayOptions.showsRightSidebar.toggle() 32 | } 33 | } 34 | .help("Toggle Inspector (⌥⌘0)") 35 | } 36 | } 37 | 38 | private var analysis: CodebaseAnalysis? 39 | { 40 | codebaseProcessor.state.analysis 41 | } 42 | 43 | @ObservedObject var codebaseProcessor: CodebaseProcessor 44 | @ObservedObject var displayOptions: WindowDisplayOptions 45 | } 46 | 47 | struct UpdatingDisplayModePicker: View 48 | { 49 | var body: some View 50 | { 51 | DisplayModePicker(displayMode: $analysis.displayMode) 52 | } 53 | 54 | @ObservedObject var analysis: CodebaseAnalysis 55 | } 56 | -------------------------------------------------------------------------------- /Code/App/Purchase/AppStoreClient+SwiftUI.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import StoreKit 3 | import SwiftyToolz 4 | 5 | extension AppStoreClient 6 | { 7 | /// The request being successful does **not** mean it has been approved 8 | func requestRefund(for productID: ProductID) async throws 9 | { 10 | guard let verificationResult = await StoreKit.Transaction.latest(for: productID.string) else 11 | { 12 | throw "Tried to request a refund for a product the user never bought: " + productID.string 13 | } 14 | 15 | let transaction = try verificationResult.payloadValue 16 | 17 | try await requestRefund(for: transaction) 18 | } 19 | 20 | /// The request being successful does **not** mean it has been approved 21 | func requestRefund(for transaction: StoreKit.Transaction) async throws 22 | { 23 | let presentRefundSheet = StoreKit.Transaction.beginRefundRequest(transaction) 24 | let presenter = try Self.retrievePresentingViewController() 25 | let requestStatus = try await presentRefundSheet(presenter) 26 | 27 | switch requestStatus 28 | { 29 | case .success: 30 | log("Did successfully request refund") 31 | case .userCancelled: 32 | log("User did cancel refund request") 33 | @unknown default: 34 | throw "Refund request returned unknown status: \(requestStatus)" 35 | } 36 | } 37 | 38 | private static func retrievePresentingViewController() throws -> NSViewController 39 | { 40 | guard let viewController = NSApp.keyWindow?.contentViewController else 41 | { 42 | throw "Could not retrieve view controller from key window" 43 | } 44 | 45 | return viewController 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/Search/SearchBarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SearchBarView: View 4 | { 5 | var body: some View 6 | { 7 | HStack // whole bar 8 | { 9 | HStack // field & button 10 | { 11 | SearchField(analysis: analysis, artifactName: artifactName) 12 | 13 | Button("Done") 14 | { 15 | withAnimation(.easeInOut(duration: Search.toggleAnimationDuration)) 16 | { 17 | analysis.set(searchBarIsVisible: false) 18 | } 19 | 20 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 21 | { 22 | analysis.set(fieldIsFocused: false) 23 | } 24 | } 25 | .focusable(false) 26 | .buttonStyle(.plain) 27 | .padding([.leading, .trailing]) 28 | .frame(maxHeight: .infinity) 29 | .overlay 30 | { 31 | RoundedRectangle(cornerRadius: 6) 32 | .stroke(.primary.opacity(0.2), lineWidth: 0.5) 33 | } 34 | .help("Hide the search filter (⇧⌘F)") 35 | } 36 | .font(.system(size: CentralViewStyle.fontSize)) 37 | .frame(height: 29) 38 | .padding(.top, 1) 39 | .padding(.bottom, 6) 40 | .padding([.leading, .trailing]) 41 | } 42 | .frame(height: analysis.search.barIsShown ? nil : 0) 43 | .clipShape(Rectangle()) 44 | } 45 | 46 | @ObservedObject var analysis: CodebaseAnalysis 47 | 48 | let artifactName: String 49 | } 50 | -------------------------------------------------------------------------------- /Code/App/Codebase Processor/Codebase Processor View/CodebaseProcessorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUIToolzOLD 2 | import SwiftUI 3 | 4 | struct CodebaseProcessorView: View 5 | { 6 | var body: some View 7 | { 8 | switch codebaseProcessor.state 9 | { 10 | case .empty: 11 | EmptyProcesorView() 12 | 13 | case .didLocateCodebase: 14 | LoadingProgressView(primaryText: "Project Located", 15 | secondaryText: "✅").padding() 16 | 17 | case .retrieveCodebase(let message): 18 | LoadingProgressView(primaryText: "Loading Codebase Data", 19 | secondaryText: message).padding() 20 | 21 | case .didJustRetrieveCodebase: 22 | LoadingProgressView(primaryText: "Codebase Loaded", 23 | secondaryText: "✅").padding() 24 | 25 | case .processCodebase(_, let progressFeedback): 26 | LoadingProgressView(primaryText: progressFeedback.primaryText, 27 | secondaryText: progressFeedback.secondaryText).padding() 28 | 29 | case .processArchitecture(_, _, let progressFeedback): 30 | LoadingProgressView(primaryText: progressFeedback.primaryText, 31 | secondaryText: progressFeedback.secondaryText).padding() 32 | 33 | case .analyzeArchitecture(let analysis): 34 | CodebaseAnalysisView(analysis: analysis, 35 | displayOptions: displayOptions) 36 | 37 | case .didFail(let errorMessage): 38 | ProcessingFailureView(errorMessage: errorMessage).padding() 39 | } 40 | } 41 | 42 | @ObservedObject var codebaseProcessor: CodebaseProcessor 43 | @ObservedObject var displayOptions: WindowDisplayOptions 44 | } 45 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Assets.xcassets/dart.imageset/dart-programming-language-icon.svg: -------------------------------------------------------------------------------- 1 | dart-programming-language -------------------------------------------------------------------------------- /Code/App/CodefaceAppDelegate.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | /// For Window Management On Launch. We have to use the app delegate, because onChange(of: scenePhase) does not work when no window is being opened on launch in the first place ... 🤮 5 | @MainActor class CodefaceAppDelegate: NSObject, NSApplicationDelegate 6 | { 7 | func applicationDidFinishLaunching(_ notification: Notification) 8 | { 9 | log(verbose: "Codeface did finish launching") 10 | } 11 | 12 | func applicationDidBecomeActive(_ notification: Notification) 13 | { 14 | log(verbose: "Codeface did become active") 15 | 16 | Task 17 | { 18 | try await Task.sleep(for: .milliseconds(50)) 19 | Self.openDocumentWindowIfNoneExist() 20 | } 21 | } 22 | 23 | func applicationWillUpdate(_ notification: Notification) 24 | { 25 | 26 | // log(verbose: "Codeface will update its windows") 27 | // log("number of windows: \(NSApp.windows.count)") 28 | } 29 | 30 | func applicationDidUpdate(_ notification: Notification) 31 | { 32 | // log(verbose: "Codeface did update its windows") 33 | // log("number of windows: \(NSApp.windows.count)") 34 | } 35 | 36 | func applicationDidResignActive(_ notification: Notification) 37 | { 38 | log(verbose: "Codeface did become inactive") 39 | } 40 | 41 | // MARK: - Window Management 42 | 43 | private static func openDocumentWindowIfNoneExist() 44 | { 45 | if !unidentifiedWindowsExist() 46 | { 47 | log(verbose: "🪟 gonna open document window because none exists") 48 | NSDocumentController.shared.newDocument(self) 49 | } 50 | } 51 | 52 | private static func unidentifiedWindowsExist() -> Bool 53 | { 54 | NSApp.windows.first { $0.identifier == nil } != nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Concrete Code Artifacts/CodeFolderArtifact.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftNodes 3 | 4 | final class CodeFolderArtifact: Identifiable, Sendable 5 | { 6 | init(name: String, 7 | partGraph: Graph) 8 | { 9 | self.name = name 10 | self.partGraph = partGraph 11 | } 12 | 13 | // MARK: - Graph Structure 14 | 15 | let partGraph: Graph 16 | 17 | final class Part: CodeArtifact, Identifiable, Hashable 18 | { 19 | // MARK: Hashability 20 | 21 | func hash(into hasher: inout Hasher) { hasher.combine(id) } 22 | static func == (lhs: Part, rhs: Part) -> Bool { lhs.id == rhs.id } 23 | 24 | // MARK: CodeArtifact Protocol 25 | 26 | var intrinsicSizeInLinesOfCode: Int? 27 | { 28 | codeArtifact.intrinsicSizeInLinesOfCode 29 | } 30 | 31 | var parts: [any CodeArtifact] { codeArtifact.parts } 32 | var name: String { codeArtifact.name } 33 | var kindName: String { codeArtifact.kindName } 34 | var code: String? { codeArtifact.code } 35 | var lineNumber: Int? { nil } 36 | var id: String { codeArtifact.id } 37 | 38 | // MARK: Actual Artifact 39 | 40 | var codeArtifact: any CodeArtifact 41 | { 42 | switch kind 43 | { 44 | case .file(let file): return file 45 | case .subfolder(let subfolder): return subfolder 46 | } 47 | } 48 | 49 | init(kind: Kind) { self.kind = kind } 50 | 51 | let kind: Kind 52 | 53 | enum Kind 54 | { 55 | case subfolder(CodeFolderArtifact), file(CodeFileArtifact) 56 | } 57 | } 58 | 59 | // MARK: - Basics 60 | 61 | let id = UUID().uuidString 62 | let name: String 63 | } 64 | -------------------------------------------------------------------------------- /Code/App/TestingDashboard.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | @MainActor 5 | struct TestingDashboard: Scene 6 | { 7 | var body: some Scene 8 | { 9 | Window("Testing Dashboard", id: Self.id) 10 | { 11 | NavigationSplitView 12 | { 13 | List 14 | { 15 | Section("Log Current State") 16 | { 17 | Button { 18 | AppStoreClient.shared.debugLogAllTransactions() 19 | } label: { 20 | Label("App Store Transactions", 21 | systemImage: "icloud") 22 | .lineLimit(1) 23 | } 24 | .buttonStyle(.link) 25 | 26 | Button { 27 | NSApp.debugLogWindows() 28 | } label: { 29 | Label("Windows", 30 | systemImage: "macwindow") 31 | .lineLimit(1) 32 | } 33 | .buttonStyle(.link) 34 | 35 | Button { 36 | Bundle.main.debugLogInfos() 37 | } label: { 38 | Label("Main Bundle", 39 | systemImage: "shippingbox") 40 | .lineLimit(1) 41 | } 42 | .buttonStyle(.link) 43 | } 44 | } 45 | .listStyle(.sidebar) 46 | } 47 | detail: 48 | { 49 | LogView() 50 | } 51 | } 52 | .windowStyle(.titleBar) 53 | .windowToolbarStyle(.unified(showsTitle: true)) 54 | } 55 | 56 | static let id = "testing-dashboard" 57 | } 58 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/LargeButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LargeButton: View 4 | { 5 | internal init(_ title: String, 6 | colorScheme: ColorScheme = .gray, 7 | action: @escaping () async -> Void) 8 | { 9 | self.title = title 10 | self.colorScheme = colorScheme 11 | self.action = action 12 | } 13 | 14 | var body: some View 15 | { 16 | ZStack(alignment: .center) 17 | { 18 | Text(title) 19 | .foregroundColor(.white) 20 | .font(.title3) 21 | .fontWeight(.semibold) 22 | .padding(14.5) 23 | .opacity(isWaitingForCompletion ? 0 : 1) 24 | 25 | ProgressView().progressViewStyle(.circular) 26 | .foregroundColor(.white) 27 | .opacity(isWaitingForCompletion ? 1 : 0) 28 | } 29 | .frame(maxWidth: .infinity) 30 | .background(RoundedRectangle(cornerRadius: Self.cornerRadius).fill(color)) 31 | .contentShape(RoundedRectangle(cornerRadius: Self.cornerRadius)) 32 | .onTapGesture 33 | { 34 | Task 35 | { 36 | isWaitingForCompletion = true 37 | await action() 38 | isWaitingForCompletion = false 39 | } 40 | } 41 | } 42 | 43 | private var color: SwiftUI.Color 44 | { 45 | switch colorScheme 46 | { 47 | case .accent: return .accentColor 48 | case .gray: return .init(white: 0.5).opacity(0.75) 49 | case .green: return Color(.systemGreen) 50 | } 51 | } 52 | 53 | let title: String 54 | 55 | let colorScheme: ColorScheme 56 | 57 | enum ColorScheme 58 | { 59 | case accent, gray, green 60 | } 61 | 62 | let action: () async -> Void 63 | 64 | @State private var isWaitingForCompletion = false 65 | 66 | private static let cornerRadius: CGFloat = 14 67 | } 68 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Architecture View Model/Layout/ArtifactViewModel+Layout.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftyToolz 3 | 4 | extension ArtifactViewModel 5 | { 6 | /** 7 | Recursively updates the layout of all part boxes and dependency arrows within this artifact. 8 | 9 | It writes many properties of the contained parts, but most importantly `frameInScopeContent`, `contentFrame` and `showsParts`. 10 | */ 11 | func updateLayout(forScopeSize scopeSize: Size? = nil, 12 | applySearchFilter: Bool) 13 | { 14 | guard let scopeSize = getScopeSize(forProvided: scopeSize) else 15 | { 16 | log(warning: "Tried to update layout but no proper scope size is available") 17 | return 18 | } 19 | 20 | // print("updating layout of \(codeArtifact.name)") 21 | 22 | // var stopWatch = StopWatch() 23 | layoutParts(in: scopeSize, 24 | applySearchFilter: applySearchFilter) 25 | // stopWatch.measure("Artifact Layout") 26 | 27 | // stopWatch.restart() 28 | layoutPartDependencies() 29 | // stopWatch.measure("Dependency Layout") 30 | } 31 | 32 | private func getScopeSize(forProvided scopeSize: Size?) -> Size? 33 | { 34 | guard let scopeSize else 35 | { 36 | return lastLayoutScopeSize 37 | } 38 | 39 | guard scopeSize.width > 75 && scopeSize.height > 75 else 40 | { 41 | log(warning: "Invalid (small) view size: \(scopeSize). Gonna abort layout.") 42 | // invalid / untrue view sizes are reported by GeometryReader all the time – not just in the very beginning ... we can never set `showsContent = nil` (and show the loading spinner) based on that noise from SwiftUI ... 43 | return lastLayoutScopeSize 44 | } 45 | 46 | lastLayoutScopeSize = scopeSize 47 | 48 | return scopeSize 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Navigator View/CodebaseNavigatorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | struct CodebaseNavigatorView: View 5 | { 6 | init(analysis: CodebaseAnalysis, 7 | showsLinesOfCode: Binding) 8 | { 9 | self.analysis = analysis 10 | _showsLinesOfCode = showsLinesOfCode 11 | _selectedArtifactID = State(wrappedValue: analysis.rootArtifact.id) 12 | } 13 | 14 | var body: some View 15 | { 16 | List([analysis.rootArtifact], 17 | children: \.children, 18 | selection: $selectedArtifactID) 19 | { 20 | artifact in 21 | 22 | NavigationLink(value: artifact.id) 23 | { 24 | SidebarLabel(artifactVM: artifact, 25 | showsLinesOfCode: $showsLinesOfCode) 26 | // .listRowBackground(nil) 27 | } 28 | .onChange(of: selectedArtifactID) 29 | { 30 | if $0 == artifact.id 31 | { 32 | analysis.selectedArtifact = artifact 33 | } 34 | } 35 | } 36 | } 37 | 38 | let analysis: CodebaseAnalysis 39 | 40 | // we hold this separately, so we don't have to hold analysis as an ObservedObject since that would fuck up the list UI 41 | @Binding var showsLinesOfCode: Bool 42 | 43 | // FIXME: as soon as we use anything other than the plain String ID as selection type, the list UI fucks up and rows cannot be selected anymore after a while ... we can't even wrap the id in a struct that only contains the id and is hashable by the id ... WTF apple ... this means every row has to observe the selected ID and set its view model as selected in the document when the ID matches ... 44 | @State private var selectedArtifactID: CodeArtifact.ID 45 | } 46 | 47 | private extension ArtifactViewModel 48 | { 49 | var children: [ArtifactViewModel]? 50 | { 51 | parts.isEmpty ? nil : parts 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/RelativeFilePath.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | struct RelativeFilePath 4 | { 5 | func appendingStringToLastComponent(_ string: String) -> RelativeFilePath 6 | { 7 | var newComponents = components 8 | let lastIndex = newComponents.count - 1 9 | newComponents[lastIndex] = newComponents[lastIndex] + string 10 | return RelativeFilePath(newComponents) 11 | } 12 | 13 | static func +=(path: inout RelativeFilePath, component: String) 14 | { 15 | path = path + component 16 | } 17 | 18 | static func +(path: RelativeFilePath, component: String) -> RelativeFilePath 19 | { 20 | path.appending(component) 21 | } 22 | 23 | func appending(_ component: String) -> RelativeFilePath 24 | { 25 | RelativeFilePath(components + Self.validComponents(from: [component])) 26 | } 27 | 28 | static var root: RelativeFilePath { .init() } 29 | 30 | init(string: String) 31 | { 32 | self.init([string]) 33 | } 34 | 35 | init(_ components: [String] = []) 36 | { 37 | self.components = Self.validComponents(from: components) 38 | } 39 | 40 | private static func validComponents(from components: [String]) -> [String] 41 | { 42 | components.reduce([]) 43 | { 44 | // check each provided component for remaining slashes and split it there 45 | $0 + $1.components(separatedBy: "/") 46 | } 47 | .compactMap 48 | { 49 | // throw out empty strings 50 | $0.isEmpty ? nil : $0 51 | } 52 | } 53 | 54 | func contains(_ otherPath: RelativeFilePath) -> Bool 55 | { 56 | // if self is the root folder, it contains any other file and folder. otherwise, the other path must have self as prefix 57 | isRoot ? true : otherPath.components.starts(with: components) 58 | } 59 | 60 | var isRoot: Bool { components.isEmpty } 61 | 62 | var string: String { components.joined(separator: "/") } 63 | 64 | let components: [String] 65 | } 66 | -------------------------------------------------------------------------------- /Code/App/Purchase/Purchase Panel/SubscriptionManagementView.swift: -------------------------------------------------------------------------------- 1 | import StoreKit 2 | import SwiftUI 3 | import SwiftyToolz 4 | 5 | struct SubscriptionManagementView: View 6 | { 7 | var body: some View 8 | { 9 | let userIsSubscribed = appStoreClient.owns(subscription) 10 | 11 | Text(subscription.displayName) 12 | .font(.title) 13 | .fontWeight(.bold) 14 | .padding(.bottom, 6) 15 | 16 | Text(subscription.description) 17 | .font(.title3) 18 | .foregroundColor(.secondary) 19 | .padding(.bottom) 20 | 21 | let green = Color(.systemGreen) 22 | 23 | if userIsSubscribed 24 | { 25 | HStack 26 | { 27 | Image(systemName: "checkmark.seal.fill") 28 | .foregroundColor(green) 29 | 30 | Text("Subscribed") 31 | } 32 | .font(.title3) 33 | .fontWeight(.medium) 34 | } 35 | else 36 | { 37 | Text(subscription.displayPrice + " / month") 38 | .font(.title3) 39 | .fontWeight(.medium) 40 | .foregroundColor(green) 41 | } 42 | 43 | Spacer() 44 | 45 | if userIsSubscribed 46 | { 47 | LargeButton("Vote on New Features", colorScheme: .green) 48 | { 49 | openURL(.featureVote) 50 | } 51 | } 52 | else 53 | { 54 | LargeButton("Subscribe", colorScheme: .accent) 55 | { 56 | do 57 | { 58 | try await appStoreClient.purchase(subscription) 59 | } 60 | catch 61 | { 62 | log(error: error.localizedDescription) 63 | } 64 | } 65 | } 66 | } 67 | 68 | let subscription: Product 69 | @ObservedObject private var appStoreClient = AppStoreClient.shared 70 | @Environment(\.openURL) var openURL 71 | } 72 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Architecture View/ArtifactContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUIToolzOLD 2 | import SwiftUI 3 | 4 | struct ArtifactContentView: View 5 | { 6 | var body: some View 7 | { 8 | GeometryReader 9 | { 10 | contentGeometry in 11 | 12 | ZStack 13 | { 14 | ForEach(artifactVM.partDependencies) 15 | { 16 | dependencyVM in 17 | 18 | DependencyView(source: dependencyVM.sourcePart, 19 | target: dependencyVM.targetPart, 20 | viewModel: dependencyVM, 21 | defaultBrightness: lineBrightness(forBGBrightness: partBGBrightness, 22 | isDarkMode: colorScheme == .dark), 23 | size: (artifactVM.gapBetweenParts ?? 0) / 2.5) 24 | .opacity(dependencyVM.sourcePart.passesSearchFilter && dependencyVM.targetPart.passesSearchFilter ? 1 : 0) 25 | } 26 | 27 | ForEach(artifactVM.parts) 28 | { 29 | partVM in 30 | 31 | ArtifactView(bgBrightness: partBGBrightness, 32 | artifactVM: partVM, 33 | pathBar: pathBar, 34 | ignoreSearchFilter: ignoreSearchFilter) 35 | .opacity(partVM.passesSearchFilter ? 1 : 0) 36 | } 37 | } 38 | .frame(width: contentGeometry.size.width, 39 | height: contentGeometry.size.height) 40 | } 41 | } 42 | 43 | private var partBGBrightness: Double 44 | { 45 | (bgBrightness + 0.1).clampedToFactor() 46 | } 47 | 48 | @ObservedObject var artifactVM: ArtifactViewModel 49 | let pathBar: PathBar 50 | let ignoreSearchFilter: Bool 51 | let bgBrightness: Double 52 | @Environment(\.colorScheme) var colorScheme 53 | } 54 | -------------------------------------------------------------------------------- /Code/App/Main Menu/ViewMenuOptions.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ViewButtons: View 4 | { 5 | var body: some View 6 | { 7 | Button("\(displayOptions.showsLinesOfCode ? "Hide" : "Show") Lines of Code in Navigator") 8 | { 9 | displayOptions.showsLinesOfCode.toggle() 10 | } 11 | .keyboardShortcut("l", modifiers: .command) 12 | .disabled(analysis == nil) 13 | 14 | Button("\(displayOptions.showsLeftSidebar ? "Hide" : "Show") the Navigator") 15 | { 16 | withAnimation 17 | { 18 | displayOptions.showsLeftSidebar.toggle() 19 | } 20 | } 21 | .keyboardShortcut("0", modifiers: .command) 22 | .disabled(analysis == nil) 23 | 24 | Button("\(displayOptions.showsRightSidebar ? "Hide" : "Show") the Inspector") 25 | { 26 | withAnimation 27 | { 28 | displayOptions.showsRightSidebar.toggle() 29 | } 30 | } 31 | .keyboardShortcut("0", modifiers: [.option, .command]) 32 | .disabled(analysis == nil) 33 | 34 | Button("\(displayOptions.showsSubscriptionPanel ? "Hide" : "Show") the Subscription Panel") 35 | { 36 | displayOptions.showsSubscriptionPanel.toggle() 37 | } 38 | .keyboardShortcut("s", modifiers: [.control, .command]) 39 | .disabled(analysis == nil) 40 | 41 | Divider() 42 | 43 | Button("Switch to Next Display Mode") 44 | { 45 | analysis?.switchDisplayMode() 46 | } 47 | .keyboardShortcut(.rightArrow, modifiers: .command) 48 | .disabled(analysis == nil) 49 | 50 | Button("Switch to Previous Display Mode") 51 | { 52 | analysis?.switchDisplayMode() 53 | } 54 | .keyboardShortcut(.leftArrow, modifiers: .command) 55 | .disabled(analysis == nil) 56 | } 57 | 58 | private var analysis: CodebaseAnalysis? 59 | { 60 | codebaseProcessor.state.analysis 61 | } 62 | 63 | @ObservedObject var codebaseProcessor: CodebaseProcessor 64 | @ObservedObject var displayOptions: WindowDisplayOptions 65 | } 66 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/LSPServiceHint.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Foundation 3 | import LSPServiceKit 4 | import SwiftLSP 5 | 6 | struct LSPServiceHint: View 7 | { 8 | var body: some View 9 | { 10 | List 11 | { 12 | Label 13 | { 14 | HStack(alignment: .firstTextBaseline) 15 | { 16 | Text("LSPService is \(lspServiceIsRunning ? "running" : "not running yet")") 17 | 18 | Button 19 | { 20 | Task 21 | { 22 | await checkLSPService() 23 | } 24 | } 25 | label: 26 | { 27 | Label("Check Again", systemImage: "arrow.clockwise") 28 | } 29 | } 30 | } 31 | icon: 32 | { 33 | Image(systemName: lspServiceIsRunning ? "checkmark.diamond.fill" : "exclamationmark.triangle.fill") 34 | .foregroundColor(Color(lspServiceIsRunning ? NSColor.systemGreen : NSColor.systemYellow)) 35 | } 36 | 37 | Label 38 | { 39 | Text("To see symbols and dependencies, you must (setup and) launch LSPService before importing code.") 40 | } 41 | icon: 42 | { 43 | Image(systemName: "info.circle") 44 | } 45 | 46 | Label 47 | { 48 | Text("If you want to import a Swift codebase, be aware that Apple's LSP server (SourceKit-LSP) does NOT support Xcode projects – only Swift packages.") 49 | } 50 | icon: 51 | { 52 | Image(systemName: "info.circle") 53 | } 54 | 55 | DocumentLink.lspService 56 | 57 | DocumentLink.wiki 58 | } 59 | .task { await checkLSPService() } 60 | } 61 | 62 | private func checkLSPService() async 63 | { 64 | lspServiceIsRunning = await LSPService.isRunning() 65 | } 66 | 67 | @State private var lspServiceIsRunning = false 68 | } 69 | -------------------------------------------------------------------------------- /Code/App/Purchase/PurchaseMenu.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | struct PurchaseMenu: View 5 | { 6 | var body: some View 7 | { 8 | Menu("Subscription") 9 | { 10 | Button("\(displayOptions.showsSubscriptionPanel ? "Hide" : "Show") the Subscription Panel") 11 | { 12 | displayOptions.showsSubscriptionPanel.toggle() 13 | } 14 | 15 | Divider() 16 | 17 | Button("Subscribe ...") 18 | { 19 | Task 20 | { 21 | do 22 | { 23 | try await appStoreClient.purchase(.subscriptionLevel1) 24 | } 25 | catch 26 | { 27 | log(error: error.localizedDescription) 28 | } 29 | } 30 | } 31 | .disabled(appStoreClient.ownsProducts) 32 | 33 | Button("Restore a Subscription ...") 34 | { 35 | Task 36 | { 37 | await appStoreClient.forceRestoreOwnedProducts() 38 | } 39 | } 40 | .disabled(appStoreClient.ownsProducts) 41 | 42 | Divider() 43 | 44 | Button("Vote On New Features (Subscribers Only) ...") 45 | { 46 | openURL(.featureVote) 47 | } 48 | .disabled(!appStoreClient.ownsProducts) 49 | 50 | Button("Refund a Subscription ...") 51 | { 52 | Task 53 | { 54 | do 55 | { 56 | try await appStoreClient.requestRefund(for: .subscriptionLevel1) 57 | } 58 | catch 59 | { 60 | log(error: error.localizedDescription) 61 | } 62 | } 63 | } 64 | .disabled(!appStoreClient.ownsProducts) 65 | } 66 | } 67 | 68 | @ObservedObject var displayOptions: WindowDisplayOptions 69 | @ObservedObject var appStoreClient = AppStoreClient.shared 70 | @Environment(\.openURL) var openURL 71 | } 72 | -------------------------------------------------------------------------------- /Code/App/Codebase Window/CodebaseLocationPersister.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import FoundationToolz 3 | import Foundation 4 | 5 | enum CodebaseLocationPersister 6 | { 7 | static var hasPersistedLastCodebaseLocation: Bool { persistedCodebaseLocationData != nil } 8 | 9 | static func persist(_ location: LSP.CodebaseLocation) throws 10 | { 11 | let bookmarkData = try location.folder.bookmarkData(options: .withSecurityScope, 12 | includingResourceValuesForKeys: nil, 13 | relativeTo: nil) 14 | 15 | let persistedLocation = PersistedCodebaseLocation(folderBookmarkData: bookmarkData, 16 | codebaseLocation: location) 17 | 18 | persistedCodebaseLocationData = try persistedLocation.encode() as Data 19 | } 20 | 21 | static func loadCodebaseLocation() throws -> LSP.CodebaseLocation 22 | { 23 | guard let locationData = persistedCodebaseLocationData else 24 | { 25 | throw "Found no persisted codebase location" 26 | } 27 | 28 | var persistedLocation = try PersistedCodebaseLocation(jsonData: locationData) 29 | 30 | var bookMarkIsStale = false 31 | 32 | let folder = try URL(resolvingBookmarkData: persistedLocation.folderBookmarkData, 33 | options: .withSecurityScope, 34 | relativeTo: nil, 35 | bookmarkDataIsStale: &bookMarkIsStale) 36 | 37 | persistedLocation.codebaseLocation.folder = folder 38 | 39 | if bookMarkIsStale 40 | { 41 | persistedLocation.folderBookmarkData = try folder.bookmarkData() 42 | 43 | persistedCodebaseLocationData = try persistedLocation.encode() as Data 44 | } 45 | 46 | return persistedLocation.codebaseLocation 47 | } 48 | 49 | @UserDefault(key: "persistedCodebaseLocationData", defaultValue: nil) 50 | private static var persistedCodebaseLocationData: Data? 51 | } 52 | 53 | private struct PersistedCodebaseLocation: Codable 54 | { 55 | var folderBookmarkData: Data 56 | var codebaseLocation: LSP.CodebaseLocation 57 | } 58 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Metrics/GraphNode+Sorting.swift: -------------------------------------------------------------------------------- 1 | import SwiftNodes 2 | import SwiftyToolz 3 | 4 | @BackgroundActor 5 | extension GraphNode where Value: CodeArtifact 6 | { 7 | func goesBefore(_ nextNode: Node) -> Bool 8 | { 9 | let nextArtifact = nextNode.value 10 | let thisArtifact = value 11 | 12 | // different components? 13 | if let componentNumA = thisArtifact.metrics.componentRank, 14 | let componentNumB = nextArtifact.metrics.componentRank, 15 | componentNumA != componentNumB 16 | { 17 | return componentNumA < componentNumB 18 | } 19 | 20 | // different topological rank? 21 | if let topoRankA = thisArtifact.metrics.sccIndexTopologicallySorted, 22 | let topoRankB = nextArtifact.metrics.sccIndexTopologicallySorted, 23 | topoRankA != topoRankB 24 | { 25 | return topoRankA < topoRankB 26 | } 27 | 28 | // different ratios of ingoing to outgoing dependencies? 29 | let inA = ancestorIDs.count 30 | let outA = descendantIDs.count 31 | 32 | let inB = nextNode.ancestorIDs.count 33 | let outB = nextNode.descendantIDs.count 34 | 35 | if inA + outA + inB + outB > 0 36 | { 37 | let ratioA = Double(inA + 1) / Double(outA + 1) 38 | let ratioB = Double(inB + 1) / Double(outB + 1) 39 | 40 | if ratioA != ratioB 41 | { 42 | return ratioA < ratioB 43 | } 44 | } 45 | 46 | // different positions in code? 47 | if let symbolA = thisArtifact as? CodeSymbolArtifact, 48 | let symbolB = nextArtifact as? CodeSymbolArtifact, 49 | symbolA.selectionRange.start.line != symbolB.selectionRange.start.line 50 | { 51 | 52 | return symbolA.selectionRange.start.line < symbolB.selectionRange.start.line 53 | } 54 | 55 | // different sizes? 56 | if thisArtifact.linesOfCode != nextArtifact.linesOfCode 57 | { 58 | return thisArtifact.linesOfCode > nextArtifact.linesOfCode 59 | } 60 | 61 | // ultima ratio: sort by name 62 | return thisArtifact.name < nextArtifact.name 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Code/XPC Experiments/XPCExecutable/Client/TestXPCExecutableForCodeface.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import Foundation 3 | import SwiftyToolz 4 | 5 | /** 6 | To work on this experiment, embedd XPC service target "CodefaceHelper" in app target "Codeface" via Targets -> Codeface -> Frameworks, Libraries, and Embedded Content 7 | */ 8 | 9 | extension XPCExecutable 10 | { 11 | static func testForCodeface() 12 | { 13 | do 14 | { 15 | let lastLoaction = try CodebaseLocationPersister.loadCodebaseLocation() 16 | try XPCExecutable.testForCodeface(with: lastLoaction) 17 | } 18 | catch 19 | { 20 | log(error.readable) 21 | } 22 | } 23 | 24 | static func testForCodeface(with location: LSP.CodebaseLocation) throws 25 | { 26 | let client = try XPCExecutable.Client(serviceBundleID: "com.flowtoolz.codeface.CodefaceHelper") 27 | 28 | let serviceProxy = client.serviceProxy 29 | 30 | log("✅ Created NSXPCConnection and retrieved service proxy") 31 | 32 | log("Gonna launch sourcekit-lsp via service proxy ...") 33 | 34 | serviceProxy.launchExecutable(.sourceKitLSP) 35 | { 36 | error in 37 | 38 | if let error 39 | { 40 | log(error: "🛑 service failed to launch executable: " + error.readable.message) 41 | return 42 | } 43 | 44 | serviceProxy.getProcessID 45 | { 46 | processID in 47 | 48 | let initializeRequest = LSP.Message.request(.initialize(folder: location.folder, 49 | clientProcessID: processID)) 50 | 51 | do 52 | { 53 | let packetData = try LSP.Packet(initializeRequest).data 54 | 55 | serviceProxy.writeExecutableStdIn(packetData) 56 | { 57 | error in 58 | 59 | log(error?.readable.message ?? "Sent initialize request to xourcekit-lsp ✅") 60 | } 61 | } 62 | catch 63 | { 64 | log(error.readable) 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Architecture View/ArtifactView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftLSP 3 | import SwiftyToolz 4 | import SwiftUIToolzOLD 5 | 6 | struct ArtifactView: View 7 | { 8 | var body: some View 9 | { 10 | ZStack 11 | { 12 | ArtifactHeaderView(artifactVM: artifactVM) 13 | .framePosition(artifactVM.headerFrame) 14 | 15 | if GlobalSettings.shared.useCorrectAnimations || artifactVM.showsParts == true 16 | { 17 | ArtifactContentView(artifactVM: artifactVM, 18 | pathBar: pathBar, 19 | ignoreSearchFilter: ignoreSearchFilter, 20 | bgBrightness: bgBrightness) 21 | .framePosition(artifactVM.contentFrame) 22 | .opacity(artifactVM.showsParts == true ? 1 : 0) 23 | } 24 | } 25 | .onHover 26 | { 27 | if $0 28 | { 29 | artifactVM.isInFocus = true 30 | pathBar.add(artifactVM) 31 | } 32 | else 33 | { 34 | withAnimation(.easeInOut) 35 | { 36 | artifactVM.isInFocus = false 37 | pathBar.remove(artifactVM) 38 | } 39 | } 40 | } 41 | .background( 42 | RoundedRectangle(cornerRadius: 5) 43 | .strokeBorder(borderColor) 44 | ) 45 | .background( 46 | RoundedRectangle(cornerRadius: 5) 47 | .fill(Color.accentColor) 48 | .opacity(artifactVM.containsSearchTermRegardlessOfParts ?? false ? colorScheme == .dark ? 1 : 0.2 : 0) 49 | .blendMode(colorScheme == .dark ? .multiply : .normal) 50 | ) 51 | .background( 52 | RoundedRectangle(cornerRadius: 5) 53 | .fill(Color(white: bgBrightness).opacity(0.9)) 54 | ) 55 | .framePosition(artifactVM.frameInScopeContent) 56 | } 57 | 58 | private var borderColor: SwiftUI.Color 59 | { 60 | .init(artifactVM.borderColor(forBackgroundBrightness: bgBrightness)) 61 | } 62 | 63 | let bgBrightness: Double 64 | 65 | @ObservedObject var artifactVM: ArtifactViewModel 66 | let pathBar: PathBar 67 | let ignoreSearchFilter: Bool 68 | 69 | @Environment(\.colorScheme) private var colorScheme 70 | } 71 | -------------------------------------------------------------------------------- /Code/Proofs of Concept/AnimationTestView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | /// tests animations by showing a text that changes its position when the user clicks on it 4 | struct AnimationTestView: View { 5 | var body: some View { 6 | DoubleSidebarView(showLeftSidebar: .constant(false), 7 | showRightSidebar: .constant(false)) { 8 | VStack { 9 | HStack { 10 | Spacer() 11 | 12 | Button("Animate") { animate() } 13 | .padding() 14 | } 15 | 16 | GeometryReader { geo in 17 | VStack { 18 | HStack { 19 | ArtifactIconView(icon: .forFile(named: "something.swift")) 20 | Text("Code Artifact Dummy") 21 | .font(.system(size: 16, 22 | weight: .medium, 23 | design: .default)) 24 | Spacer() 25 | } 26 | .padding() 27 | 28 | Spacer() 29 | } 30 | .frame(width: artifactWidth, height: artifactHeight) 31 | .background(Color.init(hue: 0, saturation: 0, brightness: 0.4)) 32 | .position(CGPoint(x: geo.size.width * relativeX, 33 | y: geo.size.height * relativeY)) 34 | .onTapGesture { 35 | animate() 36 | } 37 | .opacity(isVisible ? 1 : 0) 38 | .onChange(of: geo.size) { _ in animate() } 39 | } 40 | .clipped() 41 | } 42 | } leftSidebar: { 43 | 44 | } rightSidebar: { 45 | 46 | } 47 | } 48 | 49 | private func animate() { 50 | withAnimation(.easeInOut(duration: 1)) { 51 | relativeX = .random(in: 0 ... 1) 52 | relativeY = .random(in: 0 ... 1) 53 | 54 | artifactWidth = .random(in: 150 ... 600) 55 | artifactHeight = .random(in: 20 ... 200) 56 | 57 | isVisible.toggle() 58 | } 59 | } 60 | 61 | @State private var relativeX = 0.5 62 | @State private var relativeY = 0.5 63 | 64 | @State private var artifactWidth: Double = 300 65 | @State private var artifactHeight: Double = 100 66 | 67 | @State private var isVisible = true 68 | } 69 | -------------------------------------------------------------------------------- /Code/App/Codebase Window/Codebase Window View/CodebaseWindowView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftLSP 3 | import SwiftyToolz 4 | 5 | struct CodebaseWindowView: View 6 | { 7 | internal init(codebaseFile: Binding) 8 | { 9 | _codebaseFile = codebaseFile 10 | 11 | let codebase = codebaseFile.wrappedValue.codebase 12 | _documentWindow = StateObject(wrappedValue: CodebaseWindow(codebase: codebase)) 13 | } 14 | 15 | var body: some View 16 | { 17 | CodebaseProcessorView(codebaseProcessor: documentWindow.codebaseProcessor, 18 | displayOptions: documentWindow.displayOptions) 19 | .focusedSceneObject(documentWindow) 20 | .fileImporter(isPresented: $documentWindow.isPresentingFolderImporter, 21 | allowedContentTypes: [.directory], 22 | allowsMultipleSelection: false) 23 | { 24 | guard let folderURL = (try? $0.get())?.first else 25 | { 26 | return log(error: "Could not select code folder") 27 | } 28 | 29 | documentWindow.runProcessorWithSwiftPackageCodebase(at: folderURL) 30 | } 31 | .sheet(isPresented: $documentWindow.isPresentingCodebaseLocator) 32 | { 33 | CodebaseLocator(isBeingPresented: $documentWindow.isPresentingCodebaseLocator) 34 | { 35 | documentWindow.runProcessor(withCodebaseAtNewLocation: $0) 36 | } 37 | .padding() 38 | } 39 | .toolbar 40 | { 41 | ToolbarItemGroup(placement: .secondaryAction) 42 | { 43 | SecondaryToolbarButtons(codebaseProcessor: documentWindow.codebaseProcessor) 44 | } 45 | 46 | ToolbarItemGroup(placement: .primaryAction) 47 | { 48 | Spacer() 49 | 50 | PrimaryToolbarButtons(codebaseProcessor: documentWindow.codebaseProcessor, 51 | displayOptions: documentWindow.displayOptions) 52 | } 53 | } 54 | .onReceive(documentWindow.events) 55 | { 56 | switch $0 57 | { 58 | case .didRetrieveNewCodebase(let codebase): 59 | codebaseFile.codebase = codebase 60 | } 61 | } 62 | } 63 | 64 | @Binding var codebaseFile: CodebaseFileDocument 65 | @StateObject private var documentWindow: CodebaseWindow 66 | } 67 | -------------------------------------------------------------------------------- /XCodeProject/Codeface/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeIconSystemGenerated 11 | 1 12 | CFBundleTypeName 13 | Codebase 14 | CFBundleTypeRole 15 | Viewer 16 | LSHandlerRank 17 | Default 18 | LSItemContentTypes 19 | 20 | com.flowtoolz.codeface.codebase 21 | 22 | NSDocumentClass 23 | 24 | NSUbiquitousDocumentUserActivityType 25 | $(PRODUCT_BUNDLE_IDENTIFIER).codebase-document 26 | 27 | 28 | CFBundleExecutable 29 | $(EXECUTABLE_NAME) 30 | CFBundleIconFile 31 | 32 | CFBundleIdentifier 33 | $(PRODUCT_BUNDLE_IDENTIFIER) 34 | CFBundleInfoDictionaryVersion 35 | 6.0 36 | CFBundleName 37 | $(PRODUCT_NAME) 38 | CFBundlePackageType 39 | APPL 40 | CFBundleShortVersionString 41 | $(MARKETING_VERSION) 42 | CFBundleVersion 43 | $(CURRENT_PROJECT_VERSION) 44 | LSApplicationCategoryType 45 | public.app-category.developer-tools 46 | LSMinimumSystemVersion 47 | $(MACOSX_DEPLOYMENT_TARGET) 48 | NSHumanReadableCopyright 49 | Copyright © 2018–2023 Sebastian Fichtner. All rights reserved. 50 | NSPrincipalClass 51 | NSApplication 52 | UTExportedTypeDeclarations 53 | 54 | 55 | UTTypeConformsTo 56 | 57 | public.data 58 | public.content 59 | 60 | UTTypeDescription 61 | Codeface Codebase 62 | UTTypeIcons 63 | 64 | UTTypeIconBackgroundName 65 | CodebaseDocument 66 | UTTypeIconBadgeName 67 | 68 | UTTypeIconText 69 | 70 | 71 | UTTypeIdentifier 72 | com.flowtoolz.codeface.codebase 73 | UTTypeTagSpecification 74 | 75 | public.filename-extension 76 | 77 | codebase 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /Code/XPC Experiments/ProcessServiceTest/RunProcessServiceTest.swift: -------------------------------------------------------------------------------- 1 | import ProcessServiceClient 2 | import Combine 3 | import SwiftLSP 4 | import Foundation 5 | import SwiftyToolz 6 | 7 | /** 8 | To work on this experiment 9 | 1. Add ProcessService package to the project 10 | 2. Add this file to app target "Codeface" 11 | 3. Add "ProcessServiceTestMain.swift" to XPC service target "CodefaceHelper" 12 | 4. Embedd XPC service target "CodefaceHelper" in app target "Codeface" via Targets -> Codeface -> Frameworks, Libraries, and Embedded Content 13 | */ 14 | 15 | enum ProcessServiceTest 16 | { 17 | static func run() 18 | { 19 | Task 20 | { 21 | do 22 | { 23 | try await _run() 24 | } 25 | catch 26 | { 27 | log(error.readable) 28 | } 29 | } 30 | } 31 | 32 | private static func _run() async throws 33 | { 34 | // launch and observe an lsp-server via an XPC service 35 | 36 | try await hostedProcess.launch() 37 | 38 | observation = try await hostedProcess.processEventPublisher.sink 39 | { 40 | log("observation ended: \($0)") 41 | } 42 | receiveValue: 43 | { 44 | switch $0 45 | { 46 | case .stderr(let stdErr): 47 | log(error: "lsp-server sent stdErr: " + (stdErr.utf8String ?? "decoding error")) 48 | case .stdout(let stdOut): 49 | log("lsp-server sent stdOut: " + (stdOut.utf8String ?? "decoding error")) 50 | case .terminated(let terminationReason): 51 | log("lsp-server did terminate with reason code \(terminationReason.rawValue)") 52 | } 53 | } 54 | 55 | // send initialize request to lsp-server with codebase location and parent/client process id 56 | 57 | let location = try CodebaseLocationPersister.loadCodebaseLocation() 58 | let initializeRequest = LSP.Message.request(.initialize(folder: location.folder)) 59 | let packetData = try LSP.Packet(initializeRequest).data 60 | 61 | try await hostedProcess.write(packetData) 62 | 63 | // let launchOutput = try await hostedProcess.runAndReadStdout() 64 | // log("sourcekit-lsp output on launch: " + (launchOutput.utf8String ?? "nil")) 65 | 66 | log("✅ Did run test procedure on client side") 67 | } 68 | 69 | private static var observation: AnyCancellable? = nil 70 | private static let hostedProcess = HostedProcess(named: "com.flowtoolz.codeface.CodefaceHelper", 71 | parameters: .init(path: "/usr/bin/xcrun", 72 | arguments: ["sourcekit-lsp"])) 73 | } 74 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Create from Codebase/CodeFileArtifact+CodeFile.swift: -------------------------------------------------------------------------------- 1 | import SwiftNodes 2 | import SwiftyToolz 3 | 4 | @BackgroundActor 5 | extension CodeFileArtifact 6 | { 7 | convenience init(codeFile: CodeFile, 8 | pathInRootFolder: RelativeFilePath, 9 | additionalReferences: inout [CodeSymbol.ReferenceLocation]) 10 | { 11 | var graph = Graph() 12 | var referencesByChildID = [CodeArtifact.ID: [CodeSymbol.ReferenceLocation]]() 13 | 14 | // create child symbols recursively – DEPTH FIRST 15 | 16 | for childSymbol in (codeFile.symbols ?? []) 17 | { 18 | var extraReferences = [CodeSymbol.ReferenceLocation]() 19 | 20 | let child = CodeSymbolArtifact(symbol: childSymbol, 21 | linesOfEnclosingFile: codeFile.lines, 22 | pathInRootFolder: pathInRootFolder, 23 | additionalReferences: &extraReferences) 24 | 25 | let childReferences = (childSymbol.references ?? []) + extraReferences 26 | referencesByChildID[child.id] = childReferences 27 | 28 | graph.insert(child) 29 | } 30 | 31 | // base case: create this file artifact 32 | 33 | for (childID, childReferences) in referencesByChildID 34 | { 35 | for childReference in childReferences 36 | { 37 | if pathInRootFolder.string == childReference.filePathRelativeToRoot 38 | { 39 | // we found a reference within the scope of this file artifact that we initialize 40 | 41 | // search for a sibling that contains the reference location 42 | for sibling in graph.values 43 | { 44 | if sibling.id == childID { continue } // not a sibling but the same child 45 | 46 | if sibling.range.contains(childReference.range) 47 | { 48 | // the sibling references (depends on) the child -> add edge and leave for loop 49 | graph.add(1, toEdgeFrom: sibling.id, to: childID) 50 | break 51 | } 52 | } 53 | } 54 | else 55 | { 56 | // we found an out-of-scope reference that we pass on to the caller 57 | additionalReferences += childReference 58 | } 59 | } 60 | } 61 | 62 | graph.filterEssentialEdges() 63 | 64 | self.init(name: codeFile.name, 65 | codeLines: codeFile.lines, 66 | symbolGraph: graph) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/AboutPanel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Foundation 3 | import SwiftyToolz 4 | 5 | @MainActor 6 | struct AboutPanel: Scene 7 | { 8 | var body: some Scene 9 | { 10 | Window("About \(Bundle.main.name ?? "This App")", id: Self.id) 11 | { 12 | AboutView(privacyPolicyURL: privacyPolicyURL, 13 | licenseAgreementURL: licenseAgreementURL) 14 | } 15 | .windowResizability(.contentSize) 16 | .defaultPosition(.topLeading) 17 | .windowStyle(.hiddenTitleBar) 18 | } 19 | 20 | let privacyPolicyURL: URL 21 | let licenseAgreementURL: URL 22 | 23 | static let id = "about-panel" 24 | } 25 | 26 | struct AboutView: View 27 | { 28 | var body: some View 29 | { 30 | HStack(alignment: .top, spacing: 0) 31 | { 32 | Center 33 | { 34 | AppIcon() 35 | .frame(width: 120, height: 120) 36 | } 37 | .ignoresSafeArea() 38 | .frame(width: 200, height: 180) 39 | 40 | VStack(alignment: .leading, spacing: 0) 41 | { 42 | if let name = Bundle.main.name 43 | { 44 | Text(name) 45 | .font(.system(size: 38)) 46 | .fixedSize() 47 | } 48 | 49 | if let version = Bundle.main.version, 50 | let buildNumber = Bundle.main.buildNumber 51 | { 52 | Text("Version \(version) (\(buildNumber))") 53 | .foregroundColor(.secondary) 54 | .fontWeight(.light) 55 | .fixedSize() 56 | } 57 | 58 | Spacer() 59 | 60 | if let copyright = Bundle.main.copyright 61 | { 62 | Text(copyright.replacingOccurrences(of: ". ", 63 | with: ".\n")) 64 | .lineLimit(nil) 65 | .font(.footnote) 66 | .foregroundColor(.secondary) 67 | .fixedSize() 68 | } 69 | 70 | Spacer() 71 | 72 | HStack(spacing: 40) 73 | { 74 | DocumentLink("Privacy Policy", 75 | url: privacyPolicyURL) 76 | .fixedSize() 77 | 78 | DocumentLink("License Agreement", 79 | url: licenseAgreementURL) 80 | .fixedSize() 81 | } 82 | } 83 | .padding([.top, .trailing, .bottom]) 84 | .ignoresSafeArea() 85 | .frame(height: 180) 86 | } 87 | } 88 | 89 | let privacyPolicyURL: URL 90 | let licenseAgreementURL: URL 91 | } 92 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/CodebaseAnalysis.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | @MainActor 4 | class CodebaseAnalysis: ObservableObject 5 | { 6 | init(rootArtifact: ArtifactViewModel) 7 | { 8 | self.rootArtifact = rootArtifact 9 | self.selectedArtifact = rootArtifact 10 | } 11 | 12 | // MARK: - Search 13 | 14 | func set(searchBarIsVisible: Bool) 15 | { 16 | search.barIsShown = searchBarIsVisible 17 | } 18 | 19 | func set(fieldIsFocused: Bool) 20 | { 21 | guard search.fieldIsFocused != fieldIsFocused else { return } 22 | search.fieldIsFocused = fieldIsFocused 23 | if !fieldIsFocused { updateSearchFilter() } 24 | selectedArtifact.updateLayout(applySearchFilter: !fieldIsFocused) 25 | } 26 | 27 | func set(searchTerm: String) 28 | { 29 | guard search.term != searchTerm else { return } 30 | search.term = searchTerm // this fires since search is Published -> only for connecting to search text field UI ... 31 | updateSearchFilter() // update the filter synchronously, updates `passesSearchFilter` which is Published ... 32 | 33 | let didClearSearchTermViaButton = searchTerm.isEmpty && !search.fieldIsFocused 34 | 35 | if didClearSearchTermViaButton 36 | { 37 | selectedArtifact.updateLayout(applySearchFilter: false) 38 | } 39 | } 40 | 41 | private func updateSearchFilter() 42 | { 43 | if GlobalSettings.shared.updateSearchTermGlobally 44 | { 45 | // TODO: rather "clear search results" when term is empty 46 | rootArtifact.updateSearchResults(withSearchTerm: search.term) 47 | 48 | rootArtifact.updateSearchFilter(allPass: search.term.isEmpty) 49 | } 50 | else 51 | { 52 | // TODO: rather "clear search results" when term is empty 53 | selectedArtifact.updateSearchResults(withSearchTerm: search.term) 54 | 55 | selectedArtifact.updateSearchFilter(allPass: search.term.isEmpty) 56 | } 57 | } 58 | 59 | @Published private(set) var search = Search() 60 | 61 | // MARK: - Path Bar 62 | 63 | private(set) lazy var pathBar: PathBar = 64 | { 65 | PathBar(selectionPublisher: $selectedArtifact) 66 | }() 67 | 68 | // MARK: - Artifact View Models 69 | 70 | let rootArtifact: ArtifactViewModel 71 | 72 | // ⚠️ observers of CodebaseAnalysis will be notified when the selected artifact is replaced, but not when any of its properties change, even though ArtifactViewModel is itself an observable class 73 | @Published var selectedArtifact: ArtifactViewModel 74 | 75 | // MARK: - Display Mode 76 | 77 | func switchDisplayMode() 78 | { 79 | switch displayMode 80 | { 81 | case .code: displayMode = .treeMap 82 | case .treeMap: displayMode = .code 83 | } 84 | } 85 | 86 | @Published var displayMode: DisplayMode = .treeMap 87 | } 88 | -------------------------------------------------------------------------------- /XCodeProject/CodefaceTests/CodefaceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Codeface 3 | import SwiftLSP 4 | import SwiftyToolz 5 | 6 | class CodefaceTests: XCTestCase 7 | { 8 | @BackgroundActor func testHighlevelDependenciesAreDetectedInSpiteOfTrickyPathPrefix() throws 9 | { 10 | let range = LSPRange(start: .init(line: 0, character: 0), 11 | end: .init(line: 10, character: 0)) 12 | 13 | let classALSPSymbol = LSPDocumentSymbol(name: "ClassA", 14 | kind: 5, 15 | range: range, 16 | selectionRange: range) 17 | 18 | let referenceARange = LSPRange(start: .init(line: 1, character: 0), 19 | end: .init(line: 2, character: 0)) 20 | 21 | let referenceA = CodeSymbol.ReferenceLocation(filePathRelativeToRoot: "AB/AB.swift", 22 | range: referenceARange) 23 | 24 | let classA = try CodeSymbol(lspDocumentySymbol: classALSPSymbol, 25 | referenceLocations: [referenceA], 26 | children: []) 27 | 28 | let fileA = CodeFile(name: "A.swift", 29 | code: "", 30 | symbols: [classA]) 31 | 32 | let folderA = CodeFolder(name: "A", files: [fileA]) 33 | 34 | let classABLSPSymbol = LSPDocumentSymbol(name: "ClassAB", 35 | kind: 5, 36 | range: range, 37 | selectionRange: range) 38 | 39 | let classAB = try CodeSymbol(lspDocumentySymbol: classABLSPSymbol, 40 | referenceLocations: [], 41 | children: []) 42 | 43 | let fileAB = CodeFile(name: "AB.swift", 44 | code: "", 45 | symbols: [classAB]) 46 | 47 | let folderAB = CodeFolder(name: "AB", files: [fileAB]) 48 | 49 | let folder = CodeFolder(name: "Root", subfolders: [folderA, folderAB]) 50 | 51 | var extraReferences = [CodeSymbol.ReferenceLocation]() 52 | 53 | let folderArtifact = CodeFolderArtifact(codeFolder: folder, 54 | pathInRootFolder: .root, 55 | additionalReferences: &extraReferences) 56 | 57 | let nodes = folderArtifact.partGraph.nodes 58 | 59 | guard let idAB = nodes.first(where: { $0.value.name == "AB" })?.id, 60 | let idA = nodes.first(where: { $0.value.name == "A" })?.id 61 | else 62 | { 63 | throw "Could not find node IDs for subfolder artifacts" 64 | } 65 | 66 | XCTAssert(folderArtifact.partGraph.containsEdge(from: idAB, to: idA)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Architecture View Model/ArtifactViewModel+AddDependencies.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | extension ArtifactViewModel 4 | { 5 | func addDependencies() 6 | { 7 | // make view model hash map 8 | var viewModelHashMap = [CodeArtifact.ID : ArtifactViewModel]() 9 | 10 | applyRecursively 11 | { 12 | viewModelHashMap[$0.codeArtifact.id] = $0 13 | } 14 | 15 | // add view models for dependencies 16 | applyRecursively 17 | { 18 | artifactVM in 19 | 20 | // TODO: generalize this instead of repeating code for each kind 21 | 22 | switch artifactVM.kind 23 | { 24 | case .folder(let folder): 25 | for dependency in folder.partGraph.edges 26 | { 27 | guard let originVM = viewModelHashMap[dependency.originID], 28 | let destinationVM = viewModelHashMap[dependency.destinationID] 29 | else 30 | { 31 | log(error: "Could not find VMs for dependency from \(dependency.originID) to \(dependency.destinationID)") 32 | continue 33 | } 34 | 35 | artifactVM.partDependencies += .init(sourcePart: originVM, 36 | targetPart: destinationVM, 37 | weight: dependency.weight) 38 | } 39 | 40 | case .file(let file): 41 | for dependency in file.symbolGraph.edges 42 | { 43 | guard let originVM = viewModelHashMap[dependency.originID], 44 | let destinationVM = viewModelHashMap[dependency.destinationID] 45 | else { continue } 46 | 47 | artifactVM.partDependencies += .init(sourcePart: originVM, 48 | targetPart: destinationVM, 49 | weight: dependency.weight) 50 | } 51 | 52 | case .symbol(let symbol): 53 | for dependency in symbol.subsymbolGraph.edges 54 | { 55 | guard let originVM = viewModelHashMap[dependency.originID], 56 | let destinationVM = viewModelHashMap[dependency.destinationID] 57 | else { continue } 58 | 59 | artifactVM.partDependencies += .init(sourcePart: originVM, 60 | targetPart: destinationVM, 61 | weight: dependency.weight) 62 | } 63 | } 64 | } 65 | } 66 | 67 | func applyRecursively(action: (ArtifactViewModel) -> Void) 68 | { 69 | parts.forEach { $0.applyRecursively(action: action) } 70 | action(self) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CI/ReleaseBot/Sources/Upload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func uploadBuild(of scheme: XcodeSchemeLocation, 4 | withCredentials appStoreCredentials: AppStoreCredentials) throws { 5 | 6 | // MARK: Derived Inputs 7 | 8 | let project = "\(scheme.projectFolderPath)/\(scheme.projectName).xcodeproj" 9 | let uploadDirectory = "CI/Upload" 10 | let exportOptionsPlist = "\(uploadDirectory)/ExportOptions.plist" 11 | let byproductsDirectory = "\(uploadDirectory)/Byproducts" 12 | let archive = "\(byproductsDirectory)/\(scheme.projectName).xcarchive" 13 | let package = "\(byproductsDirectory)/\(scheme.projectName).pkg" 14 | 15 | // MARK: Run everything 16 | 17 | try deleteItem(at: byproductsDirectory) // Deleting all byproducts is optional 18 | 19 | try build(project: project, 20 | withScheme: scheme.name, 21 | asArchive: archive) 22 | 23 | try export(archive: archive, 24 | asPackage: package, 25 | exportOptionsPLIST: exportOptionsPlist) 26 | 27 | try upload(package: package, 28 | using: appStoreCredentials) 29 | 30 | print("🤖 Did upload \(package) to App Store Connect ✅") 31 | } 32 | 33 | struct XcodeSchemeLocation { 34 | let projectFolderPath: String 35 | let projectName: String 36 | let name: String 37 | } 38 | 39 | func build(project: String, 40 | withScheme scheme: String, 41 | asArchive archive: String) throws { 42 | print("🤖 Archiving \(project) to \(archive) ...") 43 | try deleteItem(at: archive) 44 | 45 | try run(command: 46 | """ 47 | xcodebuild archive \ 48 | -project \(project) \ 49 | -archivePath \(archive) \ 50 | -scheme \(scheme) \ 51 | -sdk macosx \ 52 | -destination 'platform=macOS,arch=arm64' \ 53 | -destination 'platform=macOS,arch=x86_64' \ 54 | -configuration Release \ 55 | > /dev/null 56 | """ 57 | ) 58 | } 59 | 60 | func export(archive: String, 61 | asPackage package: String, 62 | exportOptionsPLIST: String) throws { 63 | print("🤖 Exporting \(archive) as \(package) ...") 64 | try deleteItem(at: package) 65 | let exportPath = URL(filePath: package).deletingLastPathComponent().relativePath 66 | 67 | try run(command: 68 | """ 69 | xcodebuild -exportArchive \ 70 | -archivePath \(archive) \ 71 | -exportPath "\(exportPath)" \ 72 | -exportOptionsPlist \(exportOptionsPLIST) \ 73 | > /dev/null 74 | """ 75 | ) 76 | } 77 | 78 | func upload(package: String, 79 | using credentials: AppStoreCredentials) throws { 80 | print("🤖 Uploading \(package) to App Store Connect ...") 81 | 82 | try run(command: 83 | """ 84 | xcrun altool --upload-app \ 85 | --type macos \ 86 | --file \(package) \ 87 | --username \(credentials.username) \ 88 | --password \(credentials.password) \ 89 | > /dev/null 90 | """ 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Inspector View/CodebaseInspectorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | struct CodebaseInspectorView: View 5 | { 6 | var body: some View 7 | { 8 | List 9 | { 10 | Label 11 | { 12 | Text("Identity") 13 | } 14 | icon: 15 | { 16 | Image(systemName: "info.circle") 17 | } 18 | .font(.title3) 19 | .foregroundColor(.secondary) 20 | 21 | LabeledContent("Name") 22 | { 23 | Text(selectedArtifact.codeArtifact.name) 24 | } 25 | 26 | LabeledContent("Type") 27 | { 28 | Label 29 | { 30 | Text(selectedArtifact.codeArtifact.kindName) 31 | } 32 | icon: 33 | { 34 | ArtifactIconView(icon: selectedArtifact.icon, size: 16) 35 | } 36 | } 37 | 38 | Divider() 39 | 40 | Label 41 | { 42 | Text("Size") 43 | } 44 | icon: 45 | { 46 | Image(systemName: "arrow.up.and.down.text.horizontal") 47 | } 48 | .font(.title3) 49 | .foregroundColor(.secondary) 50 | 51 | LabeledContent("Lines of Code") 52 | { 53 | Text("\(selectedArtifact.metrics.linesOfCode ?? 0)") 54 | .foregroundColor(.init(selectedArtifact.linesOfCodeColor)) 55 | } 56 | 57 | Divider() 58 | 59 | Label 60 | { 61 | Text("Cycles") 62 | } 63 | icon: 64 | { 65 | Image(systemName: "arrow.3.trianglepath") 66 | } 67 | .font(.title3) 68 | .foregroundColor(.secondary) 69 | 70 | LabeledContent("Is Itself in Cycles") 71 | { 72 | let isInCycle = selectedArtifact.metrics.isInACycle ?? false 73 | 74 | let cycleColor: SwiftyToolz.Color = isInCycle ? .rgb(1, 0, 0) : .rgb(0, 1, 0) 75 | 76 | Text("\(isInCycle ? "Yes" : "No")") 77 | .foregroundColor(SwiftUI.Color(cycleColor)) 78 | } 79 | 80 | LabeledContent("Cyclic Code in Parts") 81 | { 82 | let cyclicPortion = selectedArtifact.metrics.portionOfPartsInCycles 83 | 84 | let cycleColor = Color.rgb(0, 1, 0) 85 | .mixed(with: cyclicPortion, of: .rgb(1, 0, 0)) 86 | 87 | Text("\(Int(cyclicPortion * 100))%") 88 | .foregroundColor(SwiftUI.Color(cycleColor)) 89 | } 90 | } 91 | .scrollContentBackground(.hidden) 92 | .background(Color(white: colorScheme == .dark ? 0.1568 : 0.9647)) 93 | } 94 | 95 | let selectedArtifact: ArtifactViewModel 96 | 97 | @Environment(\.colorScheme) private var colorScheme 98 | } 99 | -------------------------------------------------------------------------------- /Code/App/Codebase Window/Codebase Window View/CodebaseLocator.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftLSP 3 | import SwiftyToolz 4 | 5 | struct CodebaseLocator: View 6 | { 7 | var body: some View 8 | { 9 | VStack(alignment: .leading) 10 | { 11 | Text("Set codebase language:") 12 | 13 | Form 14 | { 15 | TextField("Language Name", text: $languageName) 16 | .lineLimit(1) 17 | 18 | TextField("Code File Endings", text: $fileEndingsInput) 19 | .lineLimit(1) 20 | } 21 | .frame(minWidth: 300) 22 | .padding([.bottom, .top]) 23 | 24 | HStack 25 | { 26 | Button("Cancel") { isBeingPresented = false } 27 | 28 | Spacer() 29 | 30 | Button("Next") 31 | { 32 | isPresentingFileImporter = true 33 | } 34 | .buttonStyle(.borderedProminent) 35 | .disabled(languageName.isEmpty || fileEndingsInput.isEmpty) 36 | .fileImporter(isPresented: $isPresentingFileImporter, 37 | allowedContentTypes: [.directory], 38 | allowsMultipleSelection: false, 39 | onCompletion: 40 | { 41 | result in 42 | 43 | isPresentingFileImporter = false 44 | isBeingPresented = false 45 | 46 | do 47 | { 48 | let urls = try result.get() 49 | 50 | guard let firstURL = urls.first else 51 | { 52 | throw "Empty array of URLs" 53 | } 54 | 55 | let fileEndings = fileEndings(fromInput: fileEndingsInput) 56 | 57 | log("Detected \(fileEndings.count) file endings in user input: \(fileEndings.joined(separator: ", "))") 58 | 59 | let config = LSP.CodebaseLocation(folder: firstURL, 60 | languageName: languageName, 61 | codeFileEndings: fileEndings) 62 | 63 | confirm(config) 64 | } 65 | catch { log(error.readable) } 66 | }) 67 | } 68 | } 69 | } 70 | 71 | @Binding var isBeingPresented: Bool 72 | let confirm: (LSP.CodebaseLocation) -> Void 73 | 74 | @State private var languageName: String = "" 75 | @State private var fileEndingsInput: String = "" 76 | @State private var isPresentingFileImporter = false 77 | } 78 | 79 | private func fileEndings(fromInput input: String) -> [String] 80 | { 81 | input 82 | .components(separatedBy: .whitespaces.union(.punctuationCharacters)) 83 | .filter { !$0.isEmpty } 84 | } 85 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Artifact Icon/ArtifactIcon.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import FoundationToolz 3 | import SwiftyToolz 4 | 5 | extension ArtifactIcon 6 | { 7 | static var package: ArtifactIcon 8 | { 9 | .systemImage(name: "shippingbox.fill", 10 | fillColor: .dynamic(.in(light: .bytes(167, 129, 79), 11 | darkness: .bytes(193, 156, 106)))) 12 | } 13 | 14 | static var folder: ArtifactIcon 15 | { 16 | .systemImage(name: "folder.fill", 17 | fillColor: .dynamic(.in(light: .bytes(19, 165, 235), 18 | darkness: .bytes(83, 168, 209)))) 19 | } 20 | 21 | static func forFile(named fileName: String) -> ArtifactIcon 22 | { 23 | guard let fileEnding = fileName.components(separatedBy: ".").last, 24 | !fileEnding.isEmpty 25 | else 26 | { 27 | return .file 28 | } 29 | 30 | switch fileEnding 31 | { 32 | case "swift": return .systemImage(name: "swift", 33 | fillColor: .rgba(.bytes(251, 139, 57))) 34 | case "dart": return .imageName("dart") 35 | case "kt": return .imageName("kotlin") 36 | default: return .file 37 | } 38 | } 39 | 40 | static var file: ArtifactIcon 41 | { 42 | .systemImage(name: "doc.fill", 43 | fillColor: .rgba(.white)) 44 | } 45 | 46 | static func `for`(symbolKind: LSPDocumentSymbol.SymbolKind?) -> ArtifactIcon 47 | { 48 | .systemImage(name: imageName(for: symbolKind), 49 | fillColor: fillColor(for: symbolKind)) 50 | } 51 | 52 | private static func imageName(for symbolKind: LSPDocumentSymbol.SymbolKind?) -> String 53 | { 54 | guard let symbolKind else { return "questionmark.square.fill" } 55 | 56 | switch symbolKind 57 | { 58 | case .File: 59 | return "doc.fill" 60 | case .Module, .Package: 61 | return "shippingbox.fill" 62 | case .Null: 63 | return "square.fill" 64 | default: 65 | if let firstCharacter = symbolKind.name.first?.lowercased() 66 | { 67 | return firstCharacter + ".square.fill" 68 | } 69 | else 70 | { 71 | return "questionmark.square.fill" 72 | } 73 | } 74 | } 75 | 76 | private static func fillColor(for symbolKind: LSPDocumentSymbol.SymbolKind?) -> UXColor 77 | { 78 | guard let symbolKind else { return .system(.secondaryLabel) } 79 | 80 | switch symbolKind 81 | { 82 | case .File, .Module, .Package: 83 | return .rgba(.white) 84 | case .Class, .Interface, .Struct: 85 | return .system(.purple) 86 | case .Namespace, .Enum: 87 | return .system(.orange) 88 | case .Method, .Constructor: 89 | return .system(.blue) 90 | case .Property, .Field, .EnumMember: 91 | return .system(.teal) 92 | case .Variable, .Constant, .Function, .Operator: 93 | return .system(.green) 94 | case .Number, .Boolean, .Array, .Object, .Key, .Null, .Event, .TypeParameter, .String: 95 | return .system(.secondaryLabel) 96 | } 97 | } 98 | } 99 | 100 | enum ArtifactIcon 101 | { 102 | case systemImage(name: String, fillColor: UXColor) 103 | case imageName(String) 104 | } 105 | -------------------------------------------------------------------------------- /Code/Proofs of Concept/NavigationAndFocusPOCView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NavigationAndFocusPOCView: View 4 | { 5 | var body: some View 6 | { 7 | // NavigationSplitView 8 | // { 9 | // List(["1", "2", "3"], id: \.self, selection: $selection) { item in 10 | // NavigationLink(value: item) { 11 | // Text(item) 12 | // } 13 | // } 14 | // .focusable(false) 15 | // .listStyle(.sidebar) 16 | // .toolbar { 17 | // Button("Start Typing") { 18 | // Task { contentIsFocused = true } 19 | // } 20 | // } 21 | // } 22 | // detail: 23 | // { 24 | // 25 | // TextField("Text Field Content", text: $textContent) 26 | // .focused($contentIsFocused) 27 | // .onSubmit { 28 | // contentIsFocused = false 29 | // } 30 | // List(["1", "2", "3"], id: \.self, selection: $selection) { item in 31 | // NavigationLink(value: item) { 32 | // Text(item) 33 | // } 34 | // } 35 | // .listStyle(.sidebar) 36 | // } 37 | 38 | 39 | 40 | // HSplitView 41 | // { 42 | // NavigationStack { 43 | // List(["1", "2", "3"], id: \.self) { item in 44 | // NavigationLink(value: item) { 45 | // Text(item) 46 | // } 47 | // } 48 | // .listStyle(.sidebar) 49 | // } 50 | // 51 | // List(["1", "2", "3"], id: \.self) { 52 | // Text($0) 53 | // } 54 | // .listStyle(.sidebar) 55 | // 56 | // List(["1", "2", "3"], id: \.self) { 57 | // Text($0) 58 | // } 59 | // .listStyle(.sidebar) 60 | // } 61 | 62 | 63 | 64 | DoubleSidebarView(showLeftSidebar: $showLeftSidebar, 65 | showRightSidebar: $showRightSidebar) 66 | { 67 | VStack 68 | { 69 | HStack { 70 | Text("HUHU") 71 | Spacer() 72 | } 73 | 74 | TextField("Text Field Content", text: $textContent) 75 | .focused($contentIsFocused) 76 | .onSubmit { 77 | contentIsFocused = false 78 | } 79 | Spacer() 80 | } 81 | .frame(maxHeight: .infinity) 82 | .ignoresSafeArea(.all, edges: [.top]) 83 | .background(.black) 84 | } 85 | leftSidebar: 86 | { 87 | VStack 88 | { 89 | TextField("Text Field Left", text: $textLeft) 90 | .focused($leftIsFocused) 91 | .onSubmit { 92 | leftIsFocused = false 93 | } 94 | .toolbar { 95 | Button("Start Typing") { 96 | Task { contentIsFocused = true } 97 | } 98 | } 99 | } 100 | } 101 | rightSidebar: 102 | { 103 | Text("right") 104 | } 105 | } 106 | 107 | @State var selection: String? = nil 108 | 109 | @State private var showLeftSidebar: Bool = true 110 | @State private var showRightSidebar: Bool = false 111 | 112 | @State var textLeft = "" 113 | @FocusState var leftIsFocused: Bool 114 | 115 | @State var textContent = "" 116 | @FocusState var contentIsFocused: Bool 117 | } 118 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/CodebaseContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftLSP 3 | 4 | struct CodebaseContentView: View 5 | { 6 | var body: some View { 7 | 8 | if selectedArtifact.partsPassingFilter.isEmpty 9 | { 10 | let contentIsFilteredOut = !selectedArtifact.passesSearchFilter || !selectedArtifact.parts.isEmpty 11 | 12 | if contentIsFilteredOut 13 | { 14 | VStack 15 | { 16 | Spacer() 17 | 18 | Label("No Search Results", systemImage: "xmark.rectangle") 19 | .foregroundColor(.secondary) 20 | .font(.title) 21 | .padding(.bottom) 22 | 23 | Text(selectedArtifact.codeArtifact.name + " does not contain \"\(analysis.search.term)\"") 24 | .foregroundColor(.secondary) 25 | .padding(.bottom) 26 | .font(.title3) 27 | 28 | Button("Clear Search Filter", role: .destructive) 29 | { 30 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 31 | { 32 | analysis.set(searchTerm: "") 33 | } 34 | } 35 | .focusable(false) 36 | .font(.title3) 37 | 38 | Spacer() 39 | } 40 | .padding() 41 | } 42 | else if case .symbol = selectedArtifact.kind 43 | { 44 | CodeView(artifact: selectedArtifact) 45 | } 46 | else // no filters, just a leaf artifact that is not a symbol 47 | { 48 | switch analysis.displayMode 49 | { 50 | case .treeMap: 51 | VStack 52 | { 53 | Spacer() 54 | 55 | Label("Empty " + selectedArtifact.codeArtifact.kindName, 56 | systemImage: "xmark.rectangle") 57 | .foregroundColor(.secondary) 58 | .font(.system(.title)) 59 | .padding(.bottom) 60 | 61 | if serverManager.serverIsWorking 62 | { 63 | Text(selectedArtifact.codeArtifact.name + " contains no further symbols.") 64 | .foregroundColor(.secondary) 65 | } 66 | else 67 | { 68 | LSPServiceHint() 69 | } 70 | 71 | Spacer() 72 | } 73 | .padding(50) 74 | 75 | case .code: 76 | CodeView(artifact: selectedArtifact) 77 | } 78 | } 79 | } 80 | else 81 | { 82 | switch analysis.displayMode 83 | { 84 | case .treeMap: 85 | TreeMap(analysis: analysis) 86 | case .code: 87 | CodeView(artifact: selectedArtifact) 88 | } 89 | } 90 | } 91 | 92 | @ObservedObject var analysis: CodebaseAnalysis 93 | 94 | // we need the selectedArtifact explicitly (even though it is accessible via analysis) so we can directly observe the properties on the selectedArtifact 95 | @ObservedObject var selectedArtifact: ArtifactViewModel 96 | 97 | @ObservedObject private var serverManager = LSP.ServerManager.shared 98 | } 99 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/User Interface/LogView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | 4 | struct LogView: View 5 | { 6 | var body: some View 7 | { 8 | VStack(alignment: .leading, spacing: 0) 9 | { 10 | List 11 | { 12 | Text("Log Messages") 13 | .font(.title2) 14 | 15 | ForEach(logViewModel.filteredLogEntries) 16 | { 17 | entry in 18 | 19 | Label 20 | { 21 | VStack(alignment: .leading) 22 | { 23 | Text(entry.message) 24 | 25 | Text(entry.context) 26 | .foregroundColor(.secondary) 27 | } 28 | } icon: { 29 | LogIcon(logLevel: entry.level) 30 | } 31 | } 32 | } 33 | .textSelection(.enabled) 34 | .animation(.default, value: logViewModel.minimumLogLevel) 35 | } 36 | .toolbar 37 | { 38 | ToolbarItemGroup(placement: .primaryAction) 39 | { 40 | Picker("Minimum Log Level", selection: $logViewModel.minimumLogLevel) 41 | { 42 | ForEach(Log.Level.allCases) 43 | { 44 | Text($0.displayName).tag($0) 45 | } 46 | } 47 | .lineLimit(1) 48 | .frame(minWidth: 100) 49 | .help("Minimum Log Level") 50 | 51 | Button { 52 | logViewModel.clear() 53 | } label: { 54 | Label("Clear Logs", systemImage: "trash") 55 | } 56 | .help("Clear Logs") 57 | } 58 | } 59 | } 60 | 61 | @ObservedObject private var logViewModel = LogViewModel.shared 62 | } 63 | 64 | struct LogIcon: View 65 | { 66 | init(logLevel: Log.Level) 67 | { 68 | switch logLevel 69 | { 70 | case .error: 71 | imageName = "xmark.octagon.fill" 72 | color = .red 73 | case .warning: 74 | imageName = "exclamationmark.triangle.fill" 75 | color = .yellow 76 | case .info: 77 | imageName = "info.circle.fill" 78 | color = .green 79 | case .verbose: 80 | imageName = "info.circle" 81 | color = .secondary 82 | } 83 | } 84 | 85 | var body: some View 86 | { 87 | Image(systemName: imageName).foregroundColor(color) 88 | } 89 | 90 | private let imageName: String 91 | private let color: SwiftUI.Color 92 | } 93 | 94 | @MainActor 95 | class LogViewModel: ObservableObject 96 | { 97 | static let shared = LogViewModel() 98 | 99 | /// just a way to create the instance that allows starting the log observation on app launch 100 | func startObservingLog() {} 101 | 102 | private init() 103 | { 104 | Log.shared.add(observer: self) 105 | { 106 | [weak self] entry in 107 | 108 | Task 109 | { 110 | @MainActor in // ensure view updates are triggered from main actor 111 | 112 | self?.logEntries.insertSorted(entry) 113 | } 114 | } 115 | } 116 | 117 | func clear() 118 | { 119 | logEntries.removeAll() 120 | } 121 | 122 | var filteredLogEntries: [Log.Entry] 123 | { 124 | logEntries.filter { $0.level >= minimumLogLevel } 125 | } 126 | 127 | #if DEBUG 128 | @Published var minimumLogLevel = Log.Level.verbose 129 | #else 130 | @Published var minimumLogLevel = Log.Level.info 131 | #endif 132 | 133 | @Published var logEntries = [Log.Entry]() 134 | } 135 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Create from Codebase/CodeSymbolArtifact+CodeSymbol.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import SwiftNodes 3 | import SwiftyToolz 4 | 5 | @BackgroundActor 6 | extension CodeSymbolArtifact 7 | { 8 | convenience init(symbol: CodeSymbol, 9 | linesOfEnclosingFile: [String], 10 | pathInRootFolder: RelativeFilePath, 11 | additionalReferences: inout [CodeSymbol.ReferenceLocation]) 12 | { 13 | var graph = Graph() 14 | var referencesByChildID = [CodeArtifact.ID: [CodeSymbol.ReferenceLocation]]() 15 | 16 | // create subsymbols recursively – RECURSION FIRST 17 | 18 | for childSymbol in (symbol.children ?? []) 19 | { 20 | var extraChildReferences = [CodeSymbol.ReferenceLocation]() 21 | 22 | let child = CodeSymbolArtifact(symbol: childSymbol, 23 | linesOfEnclosingFile: linesOfEnclosingFile, 24 | pathInRootFolder: pathInRootFolder, 25 | additionalReferences: &extraChildReferences) 26 | 27 | let childReferences = (childSymbol.references ?? []) + extraChildReferences 28 | 29 | referencesByChildID[child.id] = childReferences 30 | 31 | graph.insert(child) 32 | } 33 | 34 | // base case: create this symbol artifact 35 | 36 | // let fileName = pathInRootFolder.components.last ?? "" 37 | // 38 | // if (fileName.contains("cookie_method_channel") || fileName.contains("cookie_method_call_handler")) { 39 | // for reference in (symbol.references ?? []) { 40 | // if reference.filePathRelativeToRoot.contains("app_method_channel.dart") { 41 | // print("💥 found faulty reference:\nsource: \(reference.filePathRelativeToRoot) line \(reference.range.start.line + 1)\ntarget: \(pathInRootFolder.components.last ?? "nil") line \(symbol.range.start.line + 1) symbol \(symbol.name)") 42 | // } 43 | // } 44 | // } 45 | 46 | for (childID, childReferences) in referencesByChildID 47 | { 48 | for childReference in childReferences 49 | { 50 | if pathInRootFolder.string == childReference.filePathRelativeToRoot, 51 | symbol.range.contains(childReference.range) 52 | { 53 | // we found a reference within the scope of this symbol artifact that we initialize 54 | 55 | // search for a sibling that contains the reference location 56 | for sibling in graph.values 57 | { 58 | if sibling.id == childID { continue } // not a sibling but the same child 59 | 60 | if sibling.range.contains(childReference.range) 61 | { 62 | // the sibling references (depends on) the child -> add edge and leave for loop 63 | graph.add(1, toEdgeFrom: sibling.id, to: childID) 64 | break 65 | } 66 | } 67 | } 68 | else 69 | { 70 | // we found an out-of-scope reference that we pass on to the caller 71 | additionalReferences += childReference 72 | } 73 | } 74 | } 75 | 76 | graph.filterEssentialEdges() 77 | 78 | let code = getCode(of: symbol.range, 79 | inFileLines: linesOfEnclosingFile) 80 | 81 | self.init(name: symbol.name, 82 | kind: symbol.kind, 83 | range: symbol.range, 84 | selectionRange: symbol.selectionRange, 85 | code: code ?? "", 86 | subsymbolGraph: graph) 87 | } 88 | } 89 | 90 | func getCode(of range: LSPRange, inFileLines fileLines: [String]) -> String? 91 | { 92 | guard fileLines.isValid(index: range.start.line), 93 | fileLines.isValid(index: range.end.line) else { return nil } 94 | 95 | return fileLines[range.start.line ... range.end.line].joined(separator: "\n") 96 | } 97 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Codebase Analysis View/Central View/Top Panel/Search/SearchField.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SearchField: View 4 | { 5 | @MainActor 6 | init(analysis: CodebaseAnalysis, artifactName: String) 7 | { 8 | self.analysis = analysis 9 | _searchTerm = State(wrappedValue: analysis.search.term) 10 | self.artifactName = artifactName 11 | } 12 | 13 | var body: some View 14 | { 15 | HStack(alignment: .firstTextBaseline) 16 | { 17 | Image(systemName: "magnifyingglass") 18 | .foregroundColor(.secondary) 19 | 20 | TextField("Search Field", 21 | text: $searchTerm, 22 | prompt: Text("Find in \(artifactName)")) 23 | .textFieldStyle(.plain) 24 | .focused($isFocused) 25 | .onChange(of: isFocused) 26 | { 27 | // ❗️ we have to write the view model async (later) to not screw up focus management 28 | newFocus in Task 29 | { 30 | withAnimation(.easeInOut(duration: 1)) 31 | { 32 | analysis.set(fieldIsFocused: newFocus) 33 | } 34 | } 35 | } 36 | .onReceive(analysis.$search.dropFirst().map({ $0.fieldIsFocused }).removeDuplicates()) 37 | { 38 | isFocused = $0 39 | } 40 | .onChange(of: searchTerm) 41 | { 42 | // ❗️ we have to write the view model async (later) to not screw up focus management 43 | newTerm in 44 | 45 | Task 46 | { 47 | withAnimation(.easeInOut(duration: Search.filterUpdateAnimationDuration)) 48 | { 49 | analysis.set(searchTerm: newTerm) 50 | } 51 | } 52 | } 53 | .onReceive(analysis.$search.dropFirst().map({ $0.term }).removeDuplicates()) 54 | { 55 | searchTerm = $0 56 | } 57 | .onSubmit 58 | { 59 | // we don't wait for the view model here in order to avoid a certain visual hickup 60 | isFocused = false 61 | 62 | // ❗️ we have to write the view model async (later) to not screw up focus management 63 | Task 64 | { 65 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 66 | { 67 | analysis.set(fieldIsFocused: false) 68 | } 69 | } 70 | } 71 | 72 | if !analysis.search.term.isEmpty 73 | { 74 | Button(systemImageName: "xmark.circle.fill") 75 | { 76 | withAnimation(.easeInOut(duration: Search.layoutAnimationDuration)) 77 | { 78 | analysis.set(searchTerm: "") 79 | } 80 | } 81 | .foregroundColor(.secondary) 82 | .buttonStyle(.plain) 83 | .focusable(false) 84 | } 85 | } 86 | .padding([.leading, .trailing], 6) 87 | .frame(minWidth: 200, maxHeight: .infinity) 88 | .background 89 | { 90 | RoundedRectangle(cornerRadius: 6) 91 | .fill(.primary.opacity(isFocused ? 0.02 : 0.06)) 92 | } 93 | .overlay 94 | { 95 | RoundedRectangle(cornerRadius: 6) 96 | .stroke(.primary.opacity(0.2), lineWidth: 0.5) 97 | } 98 | } 99 | 100 | /// ❗️ we can **not** make analysis an `@ObservedObject` and simply use `onChange(of:)` for observing `Search` since that would also screw up focus management ... 101 | let analysis: CodebaseAnalysis 102 | 103 | @FocusState 104 | private var isFocused: Bool 105 | 106 | @State 107 | private var searchTerm: String 108 | 109 | let artifactName: String 110 | } 111 | 112 | extension Button where Label == Image 113 | { 114 | init(systemImageName: String, action: @escaping () -> Void) 115 | { 116 | self = Button(action: action) 117 | { 118 | Image(systemName: systemImageName) 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /XCodeProject/Codeface.xcodeproj/xcshareddata/xcschemes/Codeface.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 64 | 70 | 71 | 72 | 73 | 77 | 78 | 79 | 81 | 82 | 83 | 89 | 91 | 97 | 98 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Code/Framework-Candidates/SwiftUINodes/Arrow.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftyToolz 3 | import simd 4 | 5 | /* produces concurrency warnings for reasons i could not quickly discern 6 | struct ArrowPreview: PreviewProvider { 7 | static var previews: some View { 8 | ZStack { 9 | GeometryReader { geo in 10 | Arrow(from: CGPoint(x: geo.size.width, 11 | y: 0), 12 | to: CGPoint(x: geo.size.width / 2 + 20, 13 | y: geo.size.height / 2), 14 | size: 10) 15 | .stroke(style: .init(lineWidth: 3, 16 | lineCap: .round)) 17 | .foregroundColor(.red.opacity(0.4)) 18 | } 19 | } 20 | } 21 | } 22 | */ 23 | 24 | struct Arrow: Shape 25 | { 26 | init(from: CGPoint, to: CGPoint, size: Double) 27 | { 28 | self.size = size 29 | 30 | self.from = from 31 | self.to = to 32 | 33 | (self.a, self.b) = Self.pathPointsAAndB(forArrowFrom: from, 34 | to: to, 35 | size: size) 36 | 37 | self.c = Self.arrowHeadPoints(forArrowFrom: self.a, 38 | to: self.b, 39 | size: size) 40 | } 41 | 42 | private static func pathPointsAAndB(forArrowFrom from: CGPoint, 43 | to: CGPoint, 44 | size: Double) -> (CGPoint, CGPoint) 45 | { 46 | let fromV = from.vector 47 | let toV = to.vector 48 | 49 | let padding = 4.0 50 | 51 | let normal = simd_normalize(toV - fromV) 52 | let reverseNormal = simd_normalize(fromV - toV) 53 | 54 | return (CGPoint(fromV + normal * size), 55 | CGPoint(toV + reverseNormal * padding)) 56 | } 57 | 58 | var animatableData: AnimatablePair 59 | { 60 | get 61 | { 62 | AnimatablePair(from.animatableData, to.animatableData) 63 | } 64 | 65 | set 66 | { 67 | from.animatableData = newValue.first 68 | to.animatableData = newValue.second 69 | 70 | (a, b) = Self.pathPointsAAndB(forArrowFrom: from, to: to, size: size) 71 | 72 | c = Self.arrowHeadPoints(forArrowFrom: a, to: b, size: size) 73 | } 74 | } 75 | 76 | func path(in rect: CGRect) -> Path 77 | { 78 | Path 79 | { 80 | p in 81 | 82 | p.move(to: b) 83 | p.addLine(to: c.0) 84 | p.move(to: b) 85 | p.addLine(to: c.1) 86 | p.move(to: b) 87 | p.addLine(to: a) 88 | 89 | p.addEllipse(in: .init(x: from.x - size, 90 | y: from.y - size, 91 | width: 2 * size, 92 | height: 2 * size)) 93 | } 94 | } 95 | 96 | let size: Double 97 | private var from, to: CGPoint 98 | private var a, b: CGPoint 99 | private var c: (CGPoint, CGPoint) 100 | 101 | private static func arrowHeadPoints(forArrowFrom pointA: CGPoint, 102 | to pointB: CGPoint, 103 | size: Double) -> (CGPoint, CGPoint) 104 | { 105 | let a = pointA.vector 106 | let b = pointB.vector 107 | 108 | let f = simd_normalize(a - b) // normalized vector pointing from b to a 109 | 110 | let length = 2.5 * size // length of the arrow head 111 | 112 | let d = b + (f * length) 113 | 114 | let f_orth_1 = Vector2D(-f.y, f.x) // 1st vector orthogonal to f 115 | let f_orth_2 = Vector2D(f.y, -f.x) // 2nd vector orthogonal to f 116 | 117 | let width = size // half width of the arrow head 118 | 119 | let c_1 = d + (width * f_orth_1) 120 | let c_2 = d + (width * f_orth_2) 121 | 122 | return (CGPoint(c_1), CGPoint(c_2)) 123 | } 124 | } 125 | 126 | extension CGPoint 127 | { 128 | init(_ vector: Vector2D) 129 | { 130 | self.init(x: vector.x, y: vector.y) 131 | } 132 | 133 | var vector: Vector2D { .init(x: x, y: y) } 134 | } 135 | -------------------------------------------------------------------------------- /Code/App/Codebase Window/CodebaseWindow.swift: -------------------------------------------------------------------------------- 1 | import SwiftLSP 2 | import Foundation 3 | import Combine 4 | import SwiftyToolz 5 | 6 | @MainActor 7 | class CodebaseWindow: ObservableObject 8 | { 9 | // MARK: - Initialize 10 | 11 | init(codebase: CodeFolder?) 12 | { 13 | _lastLocation = Published(initialValue: try? CodebaseLocationPersister.loadCodebaseLocation()) 14 | 15 | if let codebase { runProcessor(with: codebase) } 16 | 17 | sendEventWhenProcessorDidRetrieveNewCodebase() 18 | } 19 | 20 | private func sendEventWhenProcessorDidRetrieveNewCodebase() 21 | { 22 | processorObservation = codebaseProcessor.$state.sink 23 | { 24 | if case .didJustRetrieveCodebase(let codebase) = $0 25 | { 26 | self.send(.didRetrieveNewCodebase(codebase)) 27 | } 28 | } 29 | } 30 | 31 | private var processorObservation: AnyCancellable? 32 | 33 | // MARK: - Run Processor with Codebase at Location 34 | 35 | func runProcessorWithSwiftPackageCodebase(at folderURL: URL) 36 | { 37 | runProcessor(withCodebaseAtNewLocation: .init(folder: folderURL, 38 | languageName: "Swift", 39 | codeFileEndings: ["swift"])) 40 | } 41 | 42 | func runProcessorWithLastCodebaseIfNoneIsLoaded() 43 | { 44 | if CodebaseLocationPersister.hasPersistedLastCodebaseLocation 45 | { 46 | runProcessorWithLastCodebase() 47 | } 48 | } 49 | 50 | func runProcessorWithLastCodebase() 51 | { 52 | do 53 | { 54 | try runProcessor(withCodebaseAt: CodebaseLocationPersister.loadCodebaseLocation()) 55 | } 56 | catch { log(error.readable) } 57 | } 58 | 59 | func runProcessor(withCodebaseAtNewLocation location: LSP.CodebaseLocation) 60 | { 61 | do 62 | { 63 | try runProcessor(withCodebaseAt: location) 64 | try CodebaseLocationPersister.persist(location) 65 | } 66 | catch { log(error.readable) } 67 | } 68 | 69 | private func runProcessor(withCodebaseAt location: LSP.CodebaseLocation) throws 70 | { 71 | guard FileManager.default.itemExists(location.folder) else 72 | { 73 | throw "Project folder does not exist: " + location.folder.absoluteString 74 | } 75 | 76 | runProcessor(from: .didLocateCodebase(location)) 77 | lastLocation = location 78 | } 79 | 80 | @Published var lastLocation: LSP.CodebaseLocation? 81 | 82 | // MARK: - Load Processor for Codebase from File 83 | 84 | func runProcessor(withCodebaseAt fileURL: URL) 85 | { 86 | guard let fileData = Data(from: fileURL) else 87 | { 88 | log(error: "Couldn't read codebase file") 89 | return 90 | } 91 | 92 | guard let codebase = CodeFolder(fileData) else 93 | { 94 | log(error: "Couldn't decode codebase") 95 | return 96 | } 97 | 98 | runProcessor(with: codebase) 99 | } 100 | 101 | func runProcessor(with codebase: CodeFolder) 102 | { 103 | runProcessor(from: .processCodebase(codebase, 104 | .init(primaryText: "Did Load Codebase Data", 105 | secondaryText: ""))) 106 | } 107 | 108 | // MARK: - Load Processor 109 | 110 | private func runProcessor(from state: CodebaseProcessorState) 111 | { 112 | codebaseProcessor.state = state 113 | codebaseProcessor.run() 114 | } 115 | 116 | // MARK: - Observable Events 117 | 118 | private func send(_ event: Event) 119 | { 120 | events.send(event) 121 | } 122 | 123 | let events = CombineMessenger() 124 | 125 | enum Event 126 | { 127 | case didRetrieveNewCodebase(CodeFolder) 128 | } 129 | 130 | typealias CombineMessenger = PassthroughSubject 131 | 132 | // MARK: - Codebase Processor 133 | 134 | let codebaseProcessor = CodebaseProcessor() 135 | 136 | // MARK: - Import Views 137 | 138 | @Published var isPresentingCodebaseLocator = false 139 | @Published var isPresentingFolderImporter = false 140 | 141 | // MARK: - Display Options 142 | 143 | let displayOptions = WindowDisplayOptions() 144 | } 145 | -------------------------------------------------------------------------------- /Code/App/Codebase Architecture/Create from Codebase/CodeFolderArtifact+CodeFolder.swift: -------------------------------------------------------------------------------- 1 | import SwiftNodes 2 | import SwiftyToolz 3 | 4 | @BackgroundActor 5 | extension CodeFolderArtifact 6 | { 7 | convenience init(codeFolder: CodeFolder, 8 | pathInRootFolder: RelativeFilePath, 9 | additionalReferences: inout [CodeSymbol.ReferenceLocation]) 10 | { 11 | // use the first (sub-)folder that contains more than one thing 12 | 13 | var ultimateCodeFolder = codeFolder 14 | var ultmatePathInRootFolder = pathInRootFolder 15 | 16 | while let onlySubfolder = ultimateCodeFolder.containsExactlyOneSubfolder 17 | { 18 | ultimateCodeFolder = CodeFolder(name: ultimateCodeFolder.name + "/" + onlySubfolder.name, 19 | files: onlySubfolder.files ?? [], 20 | subfolders: onlySubfolder.subfolders ?? []) 21 | 22 | ultmatePathInRootFolder += onlySubfolder.name 23 | } 24 | 25 | // create child parts recursively – DEPTH FIRST 26 | 27 | var referencesByChildID = [CodeArtifact.ID: [CodeSymbol.ReferenceLocation]]() 28 | var graph = Graph() 29 | 30 | for subfolder in (ultimateCodeFolder.subfolders ?? []) 31 | { 32 | var extraReferences = [CodeSymbol.ReferenceLocation]() 33 | 34 | let child = Part(kind: .subfolder(.init(codeFolder: subfolder, 35 | pathInRootFolder: ultmatePathInRootFolder + subfolder.name, 36 | additionalReferences: &extraReferences))) 37 | 38 | referencesByChildID[child.id] = extraReferences 39 | 40 | graph.insert(child) 41 | } 42 | 43 | for file in (ultimateCodeFolder.files ?? []) 44 | { 45 | var extraReferences = [CodeSymbol.ReferenceLocation]() 46 | 47 | let child = Part(kind: .file(.init(codeFile: file, 48 | pathInRootFolder: ultmatePathInRootFolder + file.name, 49 | additionalReferences: &extraReferences))) 50 | 51 | referencesByChildID[child.id] = extraReferences 52 | 53 | graph.insert(child) 54 | } 55 | 56 | // base case: create this folder artifact 57 | 58 | for (childID, childReferences) in referencesByChildID 59 | { 60 | for childReference in childReferences 61 | { 62 | let childReferencePath = RelativeFilePath(string: childReference.filePathRelativeToRoot) 63 | 64 | if ultmatePathInRootFolder.contains(childReferencePath) 65 | { 66 | // we found a reference within the scope of this folder artifact that we initialize 67 | 68 | // search for a sibling that contains the reference location 69 | for sibling in graph.values 70 | { 71 | if sibling.id == childID { continue } // not a sibling but the same child 72 | 73 | let siblingFilePath = ultmatePathInRootFolder + sibling.name 74 | 75 | if siblingFilePath.contains(childReferencePath) 76 | { 77 | // the sibling references (depends on) the child -> add edge and leave the for-loop 78 | graph.add(1, toEdgeFrom: sibling.id, to: childID) 79 | break 80 | } 81 | } 82 | } 83 | else 84 | { 85 | // we found an out-of-scope reference that we pass on to the caller 86 | additionalReferences += childReference 87 | } 88 | } 89 | } 90 | 91 | graph.filterEssentialEdges() 92 | 93 | self.init(name: ultimateCodeFolder.name, partGraph: graph) 94 | } 95 | } 96 | 97 | extension CodeFolder 98 | { 99 | var containsExactlyOneSubfolder: CodeFolder? 100 | { 101 | if !(files?.isEmpty ?? true) { return nil } 102 | guard let subfolders, subfolders.count == 1 else { return nil } 103 | return subfolders.first 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Code/App/Codebase Analysis/Architecture View Model/ArtifactViewModel+Search.swift: -------------------------------------------------------------------------------- 1 | import SwiftyToolz 2 | 3 | extension ArtifactViewModel 4 | { 5 | // MARK: - Filter 6 | 7 | var filteredPartDependencies: [DependencyVM] 8 | { 9 | partDependencies.filter 10 | { 11 | $0.targetPart.passesSearchFilter && $0.sourcePart.passesSearchFilter 12 | } 13 | } 14 | 15 | var partsNotPassingFilter: [ArtifactViewModel] 16 | { 17 | parts.filter { !$0.passesSearchFilter } 18 | } 19 | 20 | var partsPassingFilter: [ArtifactViewModel] 21 | { 22 | parts.filter { $0.passesSearchFilter } 23 | } 24 | 25 | func updateSearchFilter(allPass: Bool) 26 | { 27 | guard let containsSearchTermRegardlessOfParts, let partsContainSearchTerm else 28 | { 29 | passesSearchFilter = true 30 | 31 | for part in parts 32 | { 33 | part.updateSearchFilter(allPass: allPass) 34 | } 35 | 36 | return 37 | } 38 | 39 | if allPass || containsSearchTermRegardlessOfParts 40 | { 41 | passesSearchFilter = true 42 | 43 | for part in parts 44 | { 45 | part.updateSearchFilter(allPass: true) 46 | } 47 | 48 | return 49 | } 50 | 51 | passesSearchFilter = partsContainSearchTerm 52 | 53 | for part in parts 54 | { 55 | part.updateSearchFilter(allPass: false) 56 | } 57 | } 58 | 59 | // MARK: - Results 60 | 61 | @discardableResult 62 | func updateSearchResults(withSearchTerm searchTerm: String) -> Bool 63 | { 64 | if searchTerm == "" 65 | { 66 | containsSearchTermRegardlessOfParts = nil 67 | partsContainSearchTerm = nil 68 | 69 | for part in parts 70 | { 71 | part.updateSearchResults(withSearchTerm: searchTerm) 72 | } 73 | 74 | return true 75 | } 76 | 77 | containsSearchTermRegardlessOfParts = false 78 | partsContainSearchTerm = false 79 | 80 | for part in parts 81 | { 82 | if part.updateSearchResults(withSearchTerm: searchTerm) 83 | { 84 | partsContainSearchTerm = true 85 | } 86 | } 87 | 88 | if codeArtifact.name.find(searchTerm) 89 | { 90 | containsSearchTermRegardlessOfParts = true 91 | } 92 | 93 | if codeArtifact.kindName.find(searchTerm) 94 | { 95 | containsSearchTermRegardlessOfParts = true 96 | } 97 | 98 | switch kind 99 | { 100 | case .folder, .symbol: break 101 | 102 | case .file(let fileArtifact): 103 | // search in code, then assign these matches recursively to parts 104 | var allMatches = [Int]() 105 | 106 | for lineIndex in 0 ..< fileArtifact.lines.count 107 | { 108 | if fileArtifact.lines[lineIndex].find(searchTerm) 109 | { 110 | allMatches += lineIndex 111 | } 112 | } 113 | 114 | assign(searchMatches: allMatches) 115 | } 116 | 117 | return partsContainSearchTerm ?? false || containsSearchTermRegardlessOfParts ?? false 118 | } 119 | 120 | @discardableResult 121 | private func assign(searchMatches: [Int]) -> [Int] 122 | { 123 | switch kind 124 | { 125 | case .file, .symbol: 126 | var matchesWithoutParts = searchMatches 127 | 128 | for part in parts 129 | { 130 | matchesWithoutParts = part.assign(searchMatches: matchesWithoutParts) 131 | } 132 | 133 | if matchesWithoutParts.count < searchMatches.count 134 | { 135 | partsContainSearchTerm = true 136 | } 137 | 138 | let matchesNotInSelf = matchesWithoutParts.filter 139 | { 140 | !codeArtifact.contains(fileLine: $0) 141 | } 142 | 143 | if matchesNotInSelf.count < matchesWithoutParts.count 144 | { 145 | containsSearchTermRegardlessOfParts = true 146 | } 147 | 148 | return matchesNotInSelf 149 | 150 | case .folder: return [] 151 | } 152 | } 153 | } 154 | 155 | extension String 156 | { 157 | func find(_ searchTerm: String) -> Bool 158 | { 159 | range(of: searchTerm, options: .caseInsensitive) != nil 160 | } 161 | } 162 | --------------------------------------------------------------------------------