├── .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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------