From a13d6f002496ca7b3fbb2b8c14c71652fa7bf30d Mon Sep 17 00:00:00 2001 From: Abe M Date: Sat, 23 Mar 2024 13:53:31 -0700 Subject: [PATCH] Foundation --- CodeEdit.xcodeproj/project.pbxproj | 3397 ++--------------- .../xcshareddata/swiftpm/Package.resolved | 203 - CodeEdit/AppDelegate.swift | 236 -- CodeEdit/CodeEditApp.swift | 35 +- .../Components/EffectView.swift | 122 + .../CodeEditShared/Models/RecentProject.swift | 17 + .../ServiceContainer.swift | 44 + .../DependencyInjection/ServiceTypes.swift | 15 + .../DependencyInjection/ServiceWrapper.swift | 26 + .../DocumentService/DocumentService.swift | 14 + .../NSPasteboardProvider.swift | 35 + .../PasteboardService/PasteboardService.swift | 49 + .../UIPasteboardProvider.swift | 35 + .../Extensions/Bundle/Bundle+Info.swift | 83 + .../NSApplication/NSApp+openWindow.swift | 6 +- .../Utilities/ProjectManager.swift | 20 + .../Utilities}/SceneID.swift | 4 +- .../Components}/RecentProjectItem.swift | 8 +- .../Components}/RecentProjectsListView.swift | 12 +- .../Components}/WelcomeActionView.swift | 9 +- .../Welcome/Components/WelcomeScene.swift} | 31 +- .../Welcome/Components}/WelcomeView.swift | 110 +- .../Components}/WelcomeWindowView.swift | 33 +- .../About/Views/AboutDefaultView.swift | 112 - .../About/Views/AboutDetailView.swift | 152 - CodeEdit/Features/About/Views/AboutView.swift | 64 - .../Features/About/Views/AboutWindow.swift | 19 - .../About/Views/BlurButtonStyle.swift | 55 - .../AcknowledgementsViewModel.swift | 52 - .../Views/AcknowledgementRowView.swift | 44 - .../Views/AcknowledgementsView.swift | 32 - .../AcknowledgementsWindowController.swift | 56 - .../Views/ParsePackagesResolved.swift | 39 - .../Models/CEWorkspaceFile+Recursion.swift | 93 - .../CEWorkspace/Models/CEWorkspaceFile.swift | 267 -- .../Models/CEWorkspaceFileIcon.swift | 210 - ...EWorkspaceFileManager+FileManagement.swift | 197 - .../Models/CEWorkspaceFileManager.swift | 392 -- .../Models/DirectoryEventStream.swift | 178 - .../CodeEditUI/Views/AreaTabBar.swift | 302 -- .../Views/CEContentUnavailableView.swift | 62 - .../CodeEditUI/Views/CEOutlineGroup.swift | 77 - .../Features/CodeEditUI/Views/Divided.swift | 37 - .../CodeEditUI/Views/EffectView.swift | 72 - .../CodeEditUI/Views/HelpButton.swift | 36 - .../CodeEditUI/Views/IconButtonStyle.swift | 120 - .../CodeEditUI/Views/IconToggleStyle.swift | 59 - .../CodeEditUI/Views/OverlayPanel.swift | 34 - .../CodeEditUI/Views/OverlayView.swift | 206 - .../CodeEditUI/Views/PaneTextField.swift | 131 - .../CodeEditUI/Views/PanelDivider.swift | 22 - .../Views/PressActionsModifier.swift | 39 - .../CodeEditUI/Views/SegmentedControl.swift | 130 - .../Views/SegmentedControlImproved.swift | 145 - .../CodeEditUI/Views/SettingsTextEditor.swift | 80 - .../Views/ToolbarBranchPicker.swift | 221 -- .../Views/TrackableScrollView.swift | 112 - .../Features/CodeFile/CodeFileDocument.swift | 157 - CodeEdit/Features/CodeFile/CodeFileView.swift | 197 - .../CodeFile/Image/ImageFileView.swift | 34 - .../CodeFile/Other/OtherFileView.swift | 46 - .../CodeFile/WindowCodeFileView.swift | 32 - .../ViewModels/CommandPaletteViewModel.swift | 39 - .../Commands/Views/CommandPaletteView.swift | 108 - .../Contributors/ContributorRowView.swift | 104 - .../Contributors/ContributorsView.swift | 45 - .../Contributors/Model/Contributor.swift | 54 - .../CodeEditDocumentController.swift | 152 - .../CodeEditSplitViewController.swift | 173 - .../CodeEditWindowController.swift | 297 -- .../CodeEditWindowControllerExtensions.swift | 121 - .../Documents/Indexer/AsyncFileIterator.swift | 48 - .../Documents/Indexer/FileHelper.swift | 22 - .../Documents/Indexer/SearchIndexer+Add.swift | 145 - .../SearchIndexer+AsyncController.swift | 200 - .../Indexer/SearchIndexer+File.swift | 92 - .../SearchIndexer+InternalMethods.swift | 117 - .../Indexer/SearchIndexer+Memory.swift | 85 - .../SearchIndexer+ProgressiveSearch.swift | 111 - .../Indexer/SearchIndexer+Search.swift | 43 - .../Indexer/SearchIndexer+Terms.swift | 90 - .../Documents/Indexer/SearchIndexer.swift | 135 - .../Features/Documents/LazyStringLoader.swift | 42 - .../String+AppearancesOfSubstring.swift | 39 - .../Features/Documents/String+Character.swift | 21 - .../Views/WorkspaceCodeFileView.swift | 102 - .../Documents/WorkspaceDocument+Find.swift | 333 -- .../WorkspaceDocument+FindAndReplace.swift | 172 - .../Documents/WorkspaceDocument+Index.swift | 80 - .../WorkspaceDocument+Listeners.swift | 19 - .../WorkspaceDocument+SearchState.swift | 76 - .../Documents/WorkspaceDocument.swift | 230 -- .../Documents/WorkspaceStateKey.swift | 17 - CodeEdit/Features/Editor/Models/Editor.swift | 312 -- .../Editor/Models/EditorInstance.swift | 92 - .../EditorLayout+StateRestoration.swift | 230 -- .../Features/Editor/Models/EditorLayout.swift | 133 - .../Editor/Models/EditorManager.swift | 157 - .../Models/Environment+ActiveEditor.swift | 19 - .../Views/EditorPathBarComponent.swift | 167 - .../PathBar/Views/EditorPathBarMenu.swift | 99 - .../PathBar/Views/EditorPathBarView.swift | 85 - .../Tabs/Tab/EditorFileTabCloseButton.swift | 44 - .../TabBar/Tabs/Tab/EditorTabBackground.swift | 68 - .../Tabs/Tab/EditorTabButtonStyle.swift | 26 - .../Tabs/Tab/EditorTabCloseButton.swift | 112 - .../TabBar/Tabs/Tab/EditorTabView.swift | 299 -- .../TabBar/Tabs/Tab/Models/EditorItemID.swift | 26 - .../Tab/Models/EditorTabRepresentable.swift | 20 - .../Editor/TabBar/Tabs/Views/EditorTabs.swift | 475 --- .../Tabs/Views/EditorTabsOverflowShadow.swift | 46 - .../TabBar/Views/EditorTabBarAccessory.swift | 83 - .../Views/EditorTabBarContextMenu.swift | 171 - .../TabBar/Views/EditorTabBarDivider.swift | 77 - .../EditorTabBarLeadingAccessories.swift | 135 - .../TabBar/Views/EditorTabBarNative.swift | 68 - .../EditorTabBarTrailingAccessories.swift | 86 - .../TabBar/Views/EditorTabBarView.swift | 39 - .../Editor/Views/EditorLayoutView.swift | 97 - .../Features/Editor/Views/EditorView.swift | 99 - .../Extensions/Commands+ForEach.swift | 104 - .../Extensions/ExtensionActivatorView.swift | 29 - .../Extensions/ExtensionDetailView.swift | 42 - .../Extensions/ExtensionDiscovery.swift | 139 - .../Features/Extensions/ExtensionInfo.swift | 117 - .../Extensions/ExtensionManagerWindow.swift | 33 - .../Extensions/ExtensionSceneView.swift | 112 - .../Extensions/ExtensionsListView.swift | 53 - .../Extensions/ExtensionsManager.swift | 24 - .../codeedit.extension.appextensionpoint | 11 - .../FeedbackWindowController.swift | 52 - CodeEdit/Features/Feedback/FeedbackView.swift | 216 -- .../Feedback/HelperView/FeedbackToolbar.swift | 29 - .../Feedback/Model/FeedbackIssueArea.swift | 13 - .../Feedback/Model/FeedbackModel.swift | 171 - .../Feedback/Model/FeedbackType.swift | 13 - .../Bitbucket/BitBucketAccount+Token.swift | 51 - .../Accounts/Bitbucket/BitBucketAccount.swift | 24 - .../BitBucketOAuthConfiguration.swift | 111 - .../BitBucketTokenConfiguration.swift | 38 - .../Model/BitBucketRepositories.swift | 93 - .../Bitbucket/Model/BitBucketUser.swift | 83 - .../Routers/BitBucketOAuthRouter.swift | 69 - .../Routers/BitBucketRepositoryRouter.swift | 55 - .../Routers/BitBucketTokenRouter.swift | 54 - .../Routers/BitBucketUserRouter.swift | 41 - .../Git/Accounts/GitHub/GitHubAccount.swift | 18 - .../Accounts/GitHub/GitHubConfiguration.swift | 194 - .../Git/Accounts/GitHub/GitHubOpenness.swift | 14 - .../Accounts/GitHub/GitHubPreviewHeader.swift | 25 - .../Model/GitHubAccount+deleteReference.swift | 35 - .../Accounts/GitHub/Model/GitHubComment.swift | 25 - .../Accounts/GitHub/Model/GitHubFiles.swift | 29 - .../Accounts/GitHub/Model/GitHubGist.swift | 233 -- .../Accounts/GitHub/Model/GitHubIssue.swift | 368 -- .../GitHub/Model/GitHubPullRequest.swift | 170 - .../GitHub/Model/GitHubRepositories.swift | 115 - .../Accounts/GitHub/Model/GitHubReview.swift | 71 - .../Accounts/GitHub/Model/GitHubUser.swift | 125 - .../Git/Accounts/GitHub/PublicKey.swift | 75 - .../GitHub/Routers/GitHubGistRouter.swift | 89 - .../GitHub/Routers/GitHubIssueRouter.swift | 115 - .../Routers/GitHubPullRequestRouter.swift | 69 - .../Routers/GitHubRepositoryRouter.swift | 52 - .../GitHub/Routers/GitHubReviewsRouter.swift | 47 - .../GitHub/Routers/GitHubRouter.swift | 46 - .../GitHub/Routers/GitHubUserRouter.swift | 41 - .../Git/Accounts/GitLab/GitLabAccount.swift | 18 - .../Accounts/GitLab/GitLabConfiguration.swift | 36 - .../GitLab/GitLabOAuthConfiguration.swift | 83 - .../GitLab/Model/GitLabAccountModel.swift | 345 -- .../GitLab/Model/GitLabAvatarURL.swift | 18 - .../Accounts/GitLab/Model/GitLabCommit.swift | 294 -- .../Accounts/GitLab/Model/GitLabEvent.swift | 38 - .../GitLab/Model/GitLabEventData.swift | 44 - .../GitLab/Model/GitLabEventNote.swift | 34 - .../GitLab/Model/GitLabGroupAccess.swift | 18 - .../GitLab/Model/GitLabNamespace.swift | 42 - .../GitLab/Model/GitLabPermissions.swift | 18 - .../Accounts/GitLab/Model/GitLabProject.swift | 92 - .../GitLab/Model/GitLabProjectAccess.swift | 18 - .../GitLab/Model/GitLabProjectHook.swift | 108 - .../Accounts/GitLab/Model/GitLabUser.swift | 86 - .../GitLab/Routers/GitLabCommitRouter.swift | 72 - .../GitLab/Routers/GitLabOAuthRouter.swift | 77 - .../GitLab/Routers/GitLabProjectRouter.swift | 249 -- .../GitLab/Routers/GitLabUserRouter.swift | 37 - .../Networking/GitJSONPostRouter.swift | 233 -- .../Git/Accounts/Networking/GitRouter.swift | 382 -- .../Accounts/Networking/GitURLSession.swift | 67 - .../Features/Git/Accounts/Parameters.swift | 20 - .../Features/Git/Accounts/Utils/GitTime.swift | 36 - .../Utils/String+PercentEncoding.swift | 22 - .../Utils/String+QueryParameters.swift | 23 - .../Accounts/Utils/URL+URLParameters.swift | 34 - .../Git/Client/GitClient+Branches.swift | 150 - .../Features/Git/Client/GitClient+Clone.swift | 102 - .../Git/Client/GitClient+Commit.swift | 100 - .../Git/Client/GitClient+CommitHistory.swift | 62 - .../Features/Git/Client/GitClient+Fetch.swift | 17 - .../Git/Client/GitClient+Initiate.swift | 15 - .../Features/Git/Client/GitClient+Pull.swift | 17 - .../Features/Git/Client/GitClient+Push.swift | 24 - .../Git/Client/GitClient+Remote.swift | 69 - .../Features/Git/Client/GitClient+Stash.swift | 74 - .../Git/Client/GitClient+Status.swift | 71 - .../Git/Client/GitClient+Validate.swift | 24 - CodeEdit/Features/Git/Client/GitClient.swift | 85 - .../Git/Client/Models/GitBranch.swift | 26 - .../Git/Client/Models/GitBranchesGroup.swift | 16 - .../Git/Client/Models/GitChangedFile.swift | 17 - .../Git/Client/Models/GitCommit.swift | 65 - .../Git/Client/Models/GitRemote.swift | 14 - .../Git/Client/Models/GitStashEntry.swift | 14 - .../Features/Git/Client/Models/GitType.swift | 32 - .../Git/Clone/GitCheckoutBranchView.swift | 80 - .../Features/Git/Clone/GitCloneView.swift | 116 - .../GitCheckoutBranchViewModel.swift | 39 - .../Clone/ViewModels/GitCloneViewModel.swift | 182 - .../Features/Git/SourceControlManager.swift | 345 -- .../FileInspector/FileInspectorView.swift | 242 -- .../HistoryInspectorItemView.swift | 33 - .../HistoryInspectorModel.swift | 54 - .../HistoryInspectorView.swift | 64 - .../HistoryInspector/HistoryPopoverView.swift | 91 - .../InspectorArea/Models/InspectorTab.swift | 56 - .../ViewModels/InspectorAreaViewModel.swift | 18 - .../Views/InspectorAreaView.swift | 83 - .../InspectorArea/Views/InspectorField.swift | 41 - .../Views/InspectorSection.swift | 40 - .../Views/NoSelectionInspectorView.swift | 14 - .../Features/Keybindings/CommandManager.swift | 85 - .../Keybindings/KeybindingManager.swift | 116 - .../Keybindings/ModifierKeysObserver.swift | 113 - .../Keybindings/default_keybindings.json | 9 - .../FindNavigator/FindModePicker.swift | 210 - .../FindNavigator/FindNavigatorForm.swift | 279 -- .../FindNavigator/FindNavigatorIndexBar.swift | 69 - .../FindNavigatorListViewController.swift | 240 -- .../FindNavigatorMatchListCell.swift | 92 - .../FindNavigatorResultList.swift | 65 - .../FindNavigatorToolbarBottom.swift | 42 - .../FindNavigator/FindNavigatorView.swift | 100 - .../NavigatorArea/Models/NavigatorTab.swift | 63 - .../OutlineView/FileSystemTableViewCell.swift | 113 - .../OutlineView/StandardTableViewCell.swift | 216 -- .../OutlineView/TextTableViewCell.swift | 85 - .../OutlineView/ProjectNavigatorMenu.swift | 273 -- .../ProjectNavigatorOutlineView.swift | 84 - .../ProjectNavigatorTableViewCell.swift | 64 - ...vigatorViewController+NSMenuDelegate.swift | 33 - ...troller+OutlineTableViewCellDelegate.swift | 26 - .../ProjectNavigatorViewController.swift | 396 -- .../ProjectNavigatorToolbarBottom.swift | 157 - .../ProjectNavigatorView.swift | 24 - ...ourceControlNavigatorChangedFileView.swift | 70 - ...rceControlNavigatorChangesCommitView.swift | 136 - .../SourceControlNavigatorChangesList.swift | 80 - .../SourceControlNavigatorChangesView.swift | 62 - .../SourceControlNavigatorNoRemotesView.swift | 36 - .../SourceControlNavigatorSyncView.swift | 91 - .../Views/CommitChangedFileListItemView.swift | 48 - .../Views/CommitDetailsHeaderView.swift | 105 - .../History/Views/CommitDetailsView.swift | 114 - .../History/Views/CommitListItemView.swift | 94 - .../SourceControlNavigatorHistoryView.swift | 101 - .../Models/RepoOutlineGroupItem.swift | 21 - ...SourceControlNavigatorRepositoryItem.swift | 65 - ...lNavigatorRepositoryView+contextMenu.swift | 84 - ...gatorRepositoryView+outlineGroupData.swift | 69 - ...SourceControlNavigatorRepositoryView.swift | 192 - .../Views/SourceControlAddRemoteView.swift | 74 - .../SourceControlNavigatorNewBranchView.swift | 71 - ...urceControlNavigatorRenameBranchView.swift | 68 - .../SourceControlNavigatorToolbarBottom.swift | 103 - .../Views/SourceControlNavigatorView.swift | 84 - .../Views/SourceControlStashChangesView.swift | 64 - .../NavigatorSidebarViewModel.swift | 18 - .../Views/NavigatorAreaView.swift | 65 - .../ViewModels/QuickOpenViewModel.swift | 68 - .../ViewModels/URL+FuzzySearchable.swift | 14 - .../QuickOpen/Views/NSTableViewWrapper.swift | 142 - .../QuickOpen/Views/QuickOpenItem.swift | 45 - .../Views/QuickOpenPreviewView.swift | 58 - .../QuickOpen/Views/QuickOpenView.swift | 64 - .../Search/Extensions/String+SafeOffset.swift | 69 - .../FuzzySearch/Collection+FuzzySearch.swift | 29 - .../FuzzySearch/FuzzySearchModels.swift | 26 - .../Search/FuzzySearch/FuzzySearchable.swift | 84 - .../String+LengthOfMatchingPrefix.swift | 31 - .../Search/FuzzySearch/String+Normalise.swift | 27 - .../Search/Model/SearchModeModel.swift | 84 - .../Search/Model/SearchResultMatchModel.swift | 94 - .../Search/Model/SearchResultModel.swift | 40 - .../Settings/Models/AppSettings.swift | 51 - .../Settings/Models/PageAndSettings.swift | 19 - .../Features/Settings/Models/Settings.swift | 95 - .../Settings/Models/SettingsData.swift | 108 - .../Settings/Models/SettingsInjector.swift | 20 - .../Settings/Models/SettingsPage.swift | 62 - .../Models/SettingsSearchResult.swift | 25 - .../Settings/Models/SettingsSidebarFix.swift | 27 - .../AccountSelectionView.swift | 56 - .../AccountsSettingsAccountLink.swift | 36 - .../AccountsSettingsDetailsView.swift | 170 - .../AccountsSettingsProviderRow.swift | 47 - .../AccountsSettingsSigninView.swift | 250 -- .../AccountsSettingsView.swift | 64 - .../AccountsSettings/CreateSSHKeyView.swift | 68 - .../Models/AccountsSettings.swift | 54 - .../Models/SourceControlAccount.swift | 161 - .../GeneralSettings/GeneralSettingsView.swift | 384 -- .../Models/GeneralSettings.swift | 333 -- .../GeneralSettings/View+actionBar.swift | 29 - .../Models/KeybindingsSettings.swift | 53 - .../LocationsSettingsView.swift | 60 - .../Models/LocationsSettings.swift | 24 - .../Models/NavigationSettings.swift | 42 - .../NavigationSettingsView.swift | 32 - .../IgnorePatternListItemView.swift | 65 - .../Models/SearchSettings.swift | 38 - .../Models/SearchSettingsModel.swift | 67 - ...rchSettingsIgnoreGlobPatternItemView.swift | 16 - .../SearchSettings/SearchSettingsView.swift | 132 - .../Models/IgnoredFiles.swift | 13 - .../Models/SourceControlSettings.swift | 164 - .../SourceControlGeneralView.swift | 118 - .../SourceControlGitView.swift | 68 - .../SourceControlSettingsView.swift | 36 - .../Models/TerminalSettings.swift | 117 - .../TerminalSettingsView.swift | 84 - .../Models/TextEditingSettings.swift | 188 - .../TextEditingSettingsView.swift | 162 - .../Pages/ThemeSettings/Models/Theme.swift | 442 --- .../ThemeSettings/Models/ThemeModel.swift | 328 -- .../ThemeSettings/Models/ThemeSettings.swift | 114 - .../ThemeSettings/ThemeSettingThemeRow.swift | 51 - .../ThemeSettingsColorPreview.swift | 94 - .../ThemeSettingsThemeDetails.swift | 123 - .../ThemeSettings/ThemeSettingsView.swift | 136 - CodeEdit/Features/Settings/SettingsView.swift | 201 - .../Features/Settings/SettingsWindow.swift | 26 - .../Features/Settings/SoftwareUpdater.swift | 108 - .../Settings/Views/ExternalLink.swift | 107 - .../Settings/Views/MonospacedFontPicker.swift | 150 - .../Settings/Views/SettingsColorPicker.swift | 36 - .../Settings/Views/SettingsForm.swift | 84 - .../Settings/Views/SettingsPageView.swift | 57 - .../Views/View+ConstrainHeightToWindow.swift | 38 - .../Views/View+HideSidebarToggle.swift | 28 - .../View+NavigationBarBackButtonVisible.swift | 38 - .../Model/Environment+ContentInsets.swift | 25 - .../Model/Environment+SplitEditor.swift | 19 - .../SplitView/Model/SplitViewData.swift | 97 - .../SplitView/Model/SplitViewItem.swift | 55 - .../Features/SplitView/Views/SplitView.swift | 29 - .../Views/SplitViewControllerView.swift | 101 - .../SplitView/Views/SplitViewModifiers.swift | 46 - .../SplitView/Views/SplitViewReader.swift | 57 - .../Features/SplitView/Views/Variadic.swift | 25 - .../StatusBar/Models/CursorLocation.swift | 18 - .../StatusBar/Views/StatusBarIcon.swift | 51 - .../StatusBarBreakpointButton.swift | 28 - .../StatusBarCursorLocationLabel.swift | 112 - .../StatusBarEncodingSelector.swift | 21 - .../StatusBarIndentSelector.swift | 38 - .../StatusBarLineEndSelector.swift | 21 - .../StatusBarItems/StatusBarMenuStyle.swift | 31 - .../StatusBarToggleUtilityAreaButton.swift | 45 - .../StatusBar/Views/StatusBarView.swift | 86 - .../TerminalEmulatorView+Coordinator.swift | 41 - .../TerminalEmulatorView.swift | 328 -- .../DebugUtility/UtilityAreaDebugView.swift | 44 - .../UtilityArea/Models/UtilityAreaTab.swift | 49 - .../OutputUtility/UtilityAreaOutputView.swift | 90 - .../UtilityAreaTerminalTab.swift | 59 - .../UtilityAreaTerminalView.swift | 279 -- .../UtilityArea/Toolbar/FilterTextField.swift | 54 - .../Toolbar/StatusBarClearButton.swift | 22 - .../Toolbar/StatusBarMaximizeButton.swift | 22 - .../StatusBarSplitTerminalButton.swift | 22 - .../ViewModels/UtilityAreaTabViewModel.swift | 18 - .../ViewModels/UtilityAreaViewModel.swift | 74 - .../UtilityArea/Views/OSLogType+Color.swift | 26 - .../UtilityArea/Views/PaneToolbar.swift | 55 - .../Views/UtilityAreaTabView.swift | 178 - .../UtilityArea/Views/UtilityAreaView.swift | 66 - .../UtilityArea/Views/View+paneToolbar.swift | 20 - .../WindowCommands/CodeEditCommands.swift | 21 - .../WindowCommands/ExtensionCommands.swift | 24 - .../WindowCommands/FileCommands.swift | 88 - .../WindowCommands/FindCommands.swift | 62 - .../WindowCommands/HelpCommands.swift | 27 - .../WindowCommands/MainCommands.swift | 33 - .../WindowCommands/NavigateCommands.swift | 69 - .../WindowCommands/Utils/CommandsFixes.swift | 46 - .../Utils/FirstResponderPropertyWrapper.swift | 28 - .../WindowCommands/ViewCommands.swift | 138 - .../WindowCommands/WindowCommands.swift | 32 - .../Utils/Environment/Env+IsFullscreen.swift | 19 - CodeEdit/Utils/Environment/Env+Window.swift | 19 - .../Extensions/Array/Array+SortURLs.swift | 48 - .../Utils/Extensions/Bundle/Bundle+Info.swift | 29 - .../Utils/Extensions/Color/Color+HEX.swift | 99 - .../Extensions/Date/Date+Formatted.swift | 39 - .../NSTableView/NSTableView+Background.swift | 18 - .../String/String+HighlightOccurrences.swift | 56 - .../Extensions/String/String+Lines.swift | 54 - .../Utils/Extensions/String/String+MD5.swift | 35 - .../Extensions/String/String+Ranges.swift | 27 - .../String/String+RemoveOccurrences.swift | 23 - .../Extensions/String/String+SHA256.swift | 35 - .../Color/SwiftTerm+Color+Init.swift | 47 - .../Utils/Extensions/Text/Font+Caption3.swift | 12 - .../Utils/Extensions/URL/URL+isImage.swift | 38 - .../Extensions/View/View+focusedValue.swift | 18 - .../Extensions/View/View+isHovering.swift | 25 - CodeEdit/Utils/FocusedValues.swift | 28 - .../Utils/KeyChain/CodeEditKeychain.swift | 293 -- .../KeyChain/CodeEditKeychainConstants.swift | 53 - .../KeyChain/KeychainSwiftAccessOptions.swift | 94 - CodeEdit/Utils/Protocols/Loopable.swift | 53 - .../Protocols/SearchableSettingsPage.swift | 12 - .../ShellClient/Models/ShellClient.swift | 121 - CodeEdit/WindowObserver.swift | 50 - CodeEdit/WorkspaceView.swift | 92 - CodeEdit/World.swift | 6 - OpenWithCodeEdit/FinderSync.swift | 2 + 428 files changed, 868 insertions(+), 39143 deletions(-) delete mode 100644 CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 CodeEdit/AppDelegate.swift create mode 100644 CodeEdit/CodeEditShared/Components/EffectView.swift create mode 100644 CodeEdit/CodeEditShared/Models/RecentProject.swift create mode 100644 CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceContainer.swift create mode 100644 CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceTypes.swift create mode 100644 CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceWrapper.swift create mode 100644 CodeEdit/CodeEditShared/Services/DocumentService/DocumentService.swift create mode 100644 CodeEdit/CodeEditShared/Services/PasteboardService/NSPasteboardProvider.swift create mode 100644 CodeEdit/CodeEditShared/Services/PasteboardService/PasteboardService.swift create mode 100644 CodeEdit/CodeEditShared/Services/PasteboardService/UIPasteboardProvider.swift create mode 100644 CodeEdit/CodeEditShared/Utilities/Extensions/Bundle/Bundle+Info.swift rename CodeEdit/{Utils => CodeEditShared/Utilities}/Extensions/NSApplication/NSApp+openWindow.swift (91%) create mode 100644 CodeEdit/CodeEditShared/Utilities/ProjectManager.swift rename CodeEdit/{ => CodeEditShared/Utilities}/SceneID.swift (73%) rename CodeEdit/{Features/Welcome/Views => CodeEditShared/Views/Welcome/Components}/RecentProjectItem.swift (82%) rename CodeEdit/{Features/Welcome/Views => CodeEditShared/Views/Welcome/Components}/RecentProjectsListView.swift (94%) rename CodeEdit/{Features/Welcome/Views => CodeEditShared/Views/Welcome/Components}/WelcomeActionView.swift (77%) rename CodeEdit/{Features/Welcome/Views/WelcomeWindow.swift => CodeEditShared/Views/Welcome/Components/WelcomeScene.swift} (60%) rename CodeEdit/{Features/Welcome/Views => CodeEditShared/Views/Welcome/Components}/WelcomeView.swift (76%) rename CodeEdit/{Features/Welcome/Views => CodeEditShared/Views/Welcome/Components}/WelcomeWindowView.swift (58%) delete mode 100644 CodeEdit/Features/About/Views/AboutDefaultView.swift delete mode 100644 CodeEdit/Features/About/Views/AboutDetailView.swift delete mode 100644 CodeEdit/Features/About/Views/AboutView.swift delete mode 100644 CodeEdit/Features/About/Views/AboutWindow.swift delete mode 100644 CodeEdit/Features/About/Views/BlurButtonStyle.swift delete mode 100644 CodeEdit/Features/Acknowledgements/ViewModels/AcknowledgementsViewModel.swift delete mode 100644 CodeEdit/Features/Acknowledgements/Views/AcknowledgementRowView.swift delete mode 100644 CodeEdit/Features/Acknowledgements/Views/AcknowledgementsView.swift delete mode 100644 CodeEdit/Features/Acknowledgements/Views/AcknowledgementsWindowController.swift delete mode 100644 CodeEdit/Features/Acknowledgements/Views/ParsePackagesResolved.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile+Recursion.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileIcon.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/AreaTabBar.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/CEContentUnavailableView.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/CEOutlineGroup.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/Divided.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/EffectView.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/HelpButton.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/IconButtonStyle.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/IconToggleStyle.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/OverlayPanel.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/OverlayView.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/PanelDivider.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/PressActionsModifier.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/SegmentedControlImproved.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/SettingsTextEditor.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/TrackableScrollView.swift delete mode 100644 CodeEdit/Features/CodeFile/CodeFileDocument.swift delete mode 100644 CodeEdit/Features/CodeFile/CodeFileView.swift delete mode 100644 CodeEdit/Features/CodeFile/Image/ImageFileView.swift delete mode 100644 CodeEdit/Features/CodeFile/Other/OtherFileView.swift delete mode 100644 CodeEdit/Features/CodeFile/WindowCodeFileView.swift delete mode 100644 CodeEdit/Features/Commands/ViewModels/CommandPaletteViewModel.swift delete mode 100644 CodeEdit/Features/Commands/Views/CommandPaletteView.swift delete mode 100644 CodeEdit/Features/Contributors/ContributorRowView.swift delete mode 100644 CodeEdit/Features/Contributors/ContributorsView.swift delete mode 100644 CodeEdit/Features/Contributors/Model/Contributor.swift delete mode 100644 CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift delete mode 100644 CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift delete mode 100644 CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift delete mode 100644 CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/AsyncFileIterator.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/FileHelper.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+AsyncController.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+File.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+Memory.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+Search.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift delete mode 100644 CodeEdit/Features/Documents/Indexer/SearchIndexer.swift delete mode 100644 CodeEdit/Features/Documents/LazyStringLoader.swift delete mode 100644 CodeEdit/Features/Documents/String+AppearancesOfSubstring.swift delete mode 100644 CodeEdit/Features/Documents/String+Character.swift delete mode 100644 CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument+Find.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument+FindAndReplace.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument+Index.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument+Listeners.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument+SearchState.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument.swift delete mode 100644 CodeEdit/Features/Documents/WorkspaceStateKey.swift delete mode 100644 CodeEdit/Features/Editor/Models/Editor.swift delete mode 100644 CodeEdit/Features/Editor/Models/EditorInstance.swift delete mode 100644 CodeEdit/Features/Editor/Models/EditorLayout+StateRestoration.swift delete mode 100644 CodeEdit/Features/Editor/Models/EditorLayout.swift delete mode 100644 CodeEdit/Features/Editor/Models/EditorManager.swift delete mode 100644 CodeEdit/Features/Editor/Models/Environment+ActiveEditor.swift delete mode 100644 CodeEdit/Features/Editor/PathBar/Views/EditorPathBarComponent.swift delete mode 100644 CodeEdit/Features/Editor/PathBar/Views/EditorPathBarMenu.swift delete mode 100644 CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabButtonStyle.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorItemID.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorTabRepresentable.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabsOverflowShadow.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarAccessory.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarDivider.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarNative.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift delete mode 100644 CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift delete mode 100644 CodeEdit/Features/Editor/Views/EditorLayoutView.swift delete mode 100644 CodeEdit/Features/Editor/Views/EditorView.swift delete mode 100644 CodeEdit/Features/Extensions/Commands+ForEach.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionActivatorView.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionDetailView.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionDiscovery.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionInfo.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionManagerWindow.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionSceneView.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionsListView.swift delete mode 100644 CodeEdit/Features/Extensions/ExtensionsManager.swift delete mode 100644 CodeEdit/Features/Extensions/codeedit.extension.appextensionpoint delete mode 100644 CodeEdit/Features/Feedback/Controllers/FeedbackWindowController.swift delete mode 100644 CodeEdit/Features/Feedback/FeedbackView.swift delete mode 100644 CodeEdit/Features/Feedback/HelperView/FeedbackToolbar.swift delete mode 100644 CodeEdit/Features/Feedback/Model/FeedbackIssueArea.swift delete mode 100644 CodeEdit/Features/Feedback/Model/FeedbackModel.swift delete mode 100644 CodeEdit/Features/Feedback/Model/FeedbackType.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount+Token.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketOAuthConfiguration.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketTokenConfiguration.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketRepositories.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketUser.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketOAuthRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketRepositoryRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketTokenRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketUserRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/GitHubAccount.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/GitHubConfiguration.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/GitHubOpenness.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/GitHubPreviewHeader.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubAccount+deleteReference.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubComment.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubFiles.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubGist.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubIssue.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubPullRequest.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubRepositories.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubReview.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubUser.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/PublicKey.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubGistRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubIssueRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubPullRequestRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRepositoryRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubReviewsRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubUserRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/GitLabAccount.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/GitLabConfiguration.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/GitLabOAuthConfiguration.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAccountModel.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAvatarURL.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabCommit.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEvent.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventData.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventNote.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabGroupAccess.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabNamespace.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabPermissions.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProject.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectAccess.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectHook.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabUser.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabCommitRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabOAuthRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabProjectRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabUserRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Networking/GitJSONPostRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Networking/GitURLSession.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Parameters.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Utils/GitTime.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Utils/String+PercentEncoding.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Utils/String+QueryParameters.swift delete mode 100644 CodeEdit/Features/Git/Accounts/Utils/URL+URLParameters.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Branches.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Clone.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Commit.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Fetch.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Initiate.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Pull.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Push.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Remote.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Stash.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Status.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient+Validate.swift delete mode 100644 CodeEdit/Features/Git/Client/GitClient.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitBranch.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitChangedFile.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitCommit.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitRemote.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitStashEntry.swift delete mode 100644 CodeEdit/Features/Git/Client/Models/GitType.swift delete mode 100644 CodeEdit/Features/Git/Clone/GitCheckoutBranchView.swift delete mode 100644 CodeEdit/Features/Git/Clone/GitCloneView.swift delete mode 100644 CodeEdit/Features/Git/Clone/ViewModels/GitCheckoutBranchViewModel.swift delete mode 100644 CodeEdit/Features/Git/Clone/ViewModels/GitCloneViewModel.swift delete mode 100644 CodeEdit/Features/Git/SourceControlManager.swift delete mode 100644 CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift delete mode 100644 CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorItemView.swift delete mode 100644 CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorModel.swift delete mode 100644 CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorView.swift delete mode 100644 CodeEdit/Features/InspectorArea/HistoryInspector/HistoryPopoverView.swift delete mode 100644 CodeEdit/Features/InspectorArea/Models/InspectorTab.swift delete mode 100644 CodeEdit/Features/InspectorArea/ViewModels/InspectorAreaViewModel.swift delete mode 100644 CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift delete mode 100644 CodeEdit/Features/InspectorArea/Views/InspectorField.swift delete mode 100644 CodeEdit/Features/InspectorArea/Views/InspectorSection.swift delete mode 100644 CodeEdit/Features/InspectorArea/Views/NoSelectionInspectorView.swift delete mode 100644 CodeEdit/Features/Keybindings/CommandManager.swift delete mode 100644 CodeEdit/Features/Keybindings/KeybindingManager.swift delete mode 100644 CodeEdit/Features/Keybindings/ModifierKeysObserver.swift delete mode 100644 CodeEdit/Features/Keybindings/default_keybindings.json delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindModePicker.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorForm.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorIndexBar.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorMatchListCell.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorResultList.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift delete mode 100644 CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift delete mode 100644 CodeEdit/Features/NavigatorArea/OutlineView/FileSystemTableViewCell.swift delete mode 100644 CodeEdit/Features/NavigatorArea/OutlineView/StandardTableViewCell.swift delete mode 100644 CodeEdit/Features/NavigatorArea/OutlineView/TextTableViewCell.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSMenuDelegate.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangedFileView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesCommitView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorNoRemotesView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorSyncView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitChangedFileListItemView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsHeaderView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitListItemView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/SourceControlNavigatorHistoryView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Models/RepoOutlineGroupItem.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+contextMenu.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+outlineGroupData.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlAddRemoteView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorNewBranchView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorRenameBranchView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlStashChangesView.swift delete mode 100644 CodeEdit/Features/NavigatorArea/ViewModels/NavigatorSidebarViewModel.swift delete mode 100644 CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift delete mode 100644 CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift delete mode 100644 CodeEdit/Features/QuickOpen/ViewModels/URL+FuzzySearchable.swift delete mode 100644 CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift delete mode 100644 CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift delete mode 100644 CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift delete mode 100644 CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift delete mode 100644 CodeEdit/Features/Search/Extensions/String+SafeOffset.swift delete mode 100644 CodeEdit/Features/Search/FuzzySearch/Collection+FuzzySearch.swift delete mode 100644 CodeEdit/Features/Search/FuzzySearch/FuzzySearchModels.swift delete mode 100644 CodeEdit/Features/Search/FuzzySearch/FuzzySearchable.swift delete mode 100644 CodeEdit/Features/Search/FuzzySearch/String+LengthOfMatchingPrefix.swift delete mode 100644 CodeEdit/Features/Search/FuzzySearch/String+Normalise.swift delete mode 100644 CodeEdit/Features/Search/Model/SearchModeModel.swift delete mode 100644 CodeEdit/Features/Search/Model/SearchResultMatchModel.swift delete mode 100644 CodeEdit/Features/Search/Model/SearchResultModel.swift delete mode 100644 CodeEdit/Features/Settings/Models/AppSettings.swift delete mode 100644 CodeEdit/Features/Settings/Models/PageAndSettings.swift delete mode 100644 CodeEdit/Features/Settings/Models/Settings.swift delete mode 100644 CodeEdit/Features/Settings/Models/SettingsData.swift delete mode 100644 CodeEdit/Features/Settings/Models/SettingsInjector.swift delete mode 100644 CodeEdit/Features/Settings/Models/SettingsPage.swift delete mode 100644 CodeEdit/Features/Settings/Models/SettingsSearchResult.swift delete mode 100644 CodeEdit/Features/Settings/Models/SettingsSidebarFix.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountSelectionView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift delete mode 100644 CodeEdit/Features/Settings/Pages/GeneralSettings/GeneralSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/GeneralSettings/View+actionBar.swift delete mode 100644 CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SearchSettings/IgnorePatternListItemView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettingsModel.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsIgnoreGlobPatternItemView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/IgnoredFiles.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGeneralView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGitView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/TerminalSettings/TerminalSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/Models/Theme.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeModel.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingThemeRow.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsThemeDetails.swift delete mode 100644 CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsView.swift delete mode 100644 CodeEdit/Features/Settings/SettingsView.swift delete mode 100644 CodeEdit/Features/Settings/SettingsWindow.swift delete mode 100644 CodeEdit/Features/Settings/SoftwareUpdater.swift delete mode 100644 CodeEdit/Features/Settings/Views/ExternalLink.swift delete mode 100644 CodeEdit/Features/Settings/Views/MonospacedFontPicker.swift delete mode 100644 CodeEdit/Features/Settings/Views/SettingsColorPicker.swift delete mode 100644 CodeEdit/Features/Settings/Views/SettingsForm.swift delete mode 100644 CodeEdit/Features/Settings/Views/SettingsPageView.swift delete mode 100644 CodeEdit/Features/Settings/Views/View+ConstrainHeightToWindow.swift delete mode 100644 CodeEdit/Features/Settings/Views/View+HideSidebarToggle.swift delete mode 100644 CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift delete mode 100644 CodeEdit/Features/SplitView/Model/Environment+ContentInsets.swift delete mode 100644 CodeEdit/Features/SplitView/Model/Environment+SplitEditor.swift delete mode 100644 CodeEdit/Features/SplitView/Model/SplitViewData.swift delete mode 100644 CodeEdit/Features/SplitView/Model/SplitViewItem.swift delete mode 100644 CodeEdit/Features/SplitView/Views/SplitView.swift delete mode 100644 CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift delete mode 100644 CodeEdit/Features/SplitView/Views/SplitViewModifiers.swift delete mode 100644 CodeEdit/Features/SplitView/Views/SplitViewReader.swift delete mode 100644 CodeEdit/Features/SplitView/Views/Variadic.swift delete mode 100644 CodeEdit/Features/StatusBar/Models/CursorLocation.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarIcon.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarBreakpointButton.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorLocationLabel.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarEncodingSelector.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarIndentSelector.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarLineEndSelector.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarMenuStyle.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift delete mode 100644 CodeEdit/Features/StatusBar/Views/StatusBarView.swift delete mode 100644 CodeEdit/Features/TerminalEmulator/TerminalEmulatorView+Coordinator.swift delete mode 100644 CodeEdit/Features/TerminalEmulator/TerminalEmulatorView.swift delete mode 100644 CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift delete mode 100644 CodeEdit/Features/UtilityArea/Models/UtilityAreaTab.swift delete mode 100644 CodeEdit/Features/UtilityArea/OutputUtility/UtilityAreaOutputView.swift delete mode 100644 CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalTab.swift delete mode 100644 CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift delete mode 100644 CodeEdit/Features/UtilityArea/Toolbar/FilterTextField.swift delete mode 100644 CodeEdit/Features/UtilityArea/Toolbar/StatusBarClearButton.swift delete mode 100644 CodeEdit/Features/UtilityArea/Toolbar/StatusBarMaximizeButton.swift delete mode 100644 CodeEdit/Features/UtilityArea/Toolbar/StatusBarSplitTerminalButton.swift delete mode 100644 CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaTabViewModel.swift delete mode 100644 CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift delete mode 100644 CodeEdit/Features/UtilityArea/Views/OSLogType+Color.swift delete mode 100644 CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift delete mode 100644 CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift delete mode 100644 CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift delete mode 100644 CodeEdit/Features/UtilityArea/Views/View+paneToolbar.swift delete mode 100644 CodeEdit/Features/WindowCommands/CodeEditCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/ExtensionCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/FileCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/FindCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/HelpCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/MainCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/NavigateCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/Utils/CommandsFixes.swift delete mode 100644 CodeEdit/Features/WindowCommands/Utils/FirstResponderPropertyWrapper.swift delete mode 100644 CodeEdit/Features/WindowCommands/ViewCommands.swift delete mode 100644 CodeEdit/Features/WindowCommands/WindowCommands.swift delete mode 100644 CodeEdit/Utils/Environment/Env+IsFullscreen.swift delete mode 100644 CodeEdit/Utils/Environment/Env+Window.swift delete mode 100644 CodeEdit/Utils/Extensions/Array/Array+SortURLs.swift delete mode 100644 CodeEdit/Utils/Extensions/Bundle/Bundle+Info.swift delete mode 100644 CodeEdit/Utils/Extensions/Color/Color+HEX.swift delete mode 100644 CodeEdit/Utils/Extensions/Date/Date+Formatted.swift delete mode 100644 CodeEdit/Utils/Extensions/NSTableView/NSTableView+Background.swift delete mode 100644 CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift delete mode 100644 CodeEdit/Utils/Extensions/String/String+Lines.swift delete mode 100644 CodeEdit/Utils/Extensions/String/String+MD5.swift delete mode 100644 CodeEdit/Utils/Extensions/String/String+Ranges.swift delete mode 100644 CodeEdit/Utils/Extensions/String/String+RemoveOccurrences.swift delete mode 100644 CodeEdit/Utils/Extensions/String/String+SHA256.swift delete mode 100644 CodeEdit/Utils/Extensions/SwiftTerm/Color/SwiftTerm+Color+Init.swift delete mode 100644 CodeEdit/Utils/Extensions/Text/Font+Caption3.swift delete mode 100644 CodeEdit/Utils/Extensions/URL/URL+isImage.swift delete mode 100644 CodeEdit/Utils/Extensions/View/View+focusedValue.swift delete mode 100644 CodeEdit/Utils/Extensions/View/View+isHovering.swift delete mode 100644 CodeEdit/Utils/FocusedValues.swift delete mode 100644 CodeEdit/Utils/KeyChain/CodeEditKeychain.swift delete mode 100644 CodeEdit/Utils/KeyChain/CodeEditKeychainConstants.swift delete mode 100644 CodeEdit/Utils/KeyChain/KeychainSwiftAccessOptions.swift delete mode 100644 CodeEdit/Utils/Protocols/Loopable.swift delete mode 100644 CodeEdit/Utils/Protocols/SearchableSettingsPage.swift delete mode 100644 CodeEdit/Utils/ShellClient/Models/ShellClient.swift delete mode 100644 CodeEdit/WindowObserver.swift delete mode 100644 CodeEdit/WorkspaceView.swift delete mode 100644 CodeEdit/World.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 3e9c94bc4f..100104a5f6 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -7,74 +7,33 @@ objects = { /* Begin PBXBuildFile section */ - 041FC6AD2AE437CE00C1F65A /* SourceControlNavigatorNewBranchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 041FC6AC2AE437CE00C1F65A /* SourceControlNavigatorNewBranchView.swift */; }; - 043BCF03281DA18A000AC47C /* WorkspaceDocument+SearchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043BCF02281DA18A000AC47C /* WorkspaceDocument+SearchState.swift */; }; - 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */; }; - 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321527E3201F006AE443 /* WorkspaceDocument.swift */; }; - 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */; }; - 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */; }; - 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */; }; - 04BA7C0B2AE2A2D100584E1C /* GitBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C0A2AE2A2D100584E1C /* GitBranch.swift */; }; - 04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C0D2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift */; }; - 04BA7C132AE2AA7300584E1C /* GitCheckoutBranchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C112AE2AA7300584E1C /* GitCheckoutBranchViewModel.swift */; }; - 04BA7C142AE2AA7300584E1C /* GitCloneViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C122AE2AA7300584E1C /* GitCloneViewModel.swift */; }; - 04BA7C192AE2D7C600584E1C /* GitClient+Branches.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C182AE2D7C600584E1C /* GitClient+Branches.swift */; }; - 04BA7C1C2AE2D84100584E1C /* GitClient+Commit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C1B2AE2D84100584E1C /* GitClient+Commit.swift */; }; - 04BA7C1E2AE2D8A000584E1C /* GitClient+Clone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C1D2AE2D8A000584E1C /* GitClient+Clone.swift */; }; - 04BA7C202AE2D92B00584E1C /* GitClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C1F2AE2D92B00584E1C /* GitClient+Status.swift */; }; - 04BA7C222AE2D95E00584E1C /* GitClient+CommitHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C212AE2D95E00584E1C /* GitClient+CommitHistory.swift */; }; - 04BA7C242AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C232AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift */; }; - 04BA7C272AE2E9F100584E1C /* GitClient+Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BA7C262AE2E9F100584E1C /* GitClient+Push.swift */; }; - 04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BC1CDD2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift */; }; - 04C3255B2801F86400C8DA2D /* ProjectNavigatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */; }; - 04C3255C2801F86900C8DA2D /* ProjectNavigatorMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */; }; - 201169D72837B2E300F92B46 /* SourceControlNavigatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201169D62837B2E300F92B46 /* SourceControlNavigatorView.swift */; }; - 201169DB2837B34000F92B46 /* SourceControlNavigatorChangedFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201169DA2837B34000F92B46 /* SourceControlNavigatorChangedFileView.swift */; }; - 201169DD2837B3AC00F92B46 /* SourceControlNavigatorToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201169DC2837B3AC00F92B46 /* SourceControlNavigatorToolbarBottom.swift */; }; - 201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201169E12837B3D800F92B46 /* SourceControlNavigatorChangesView.swift */; }; - 201169E52837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201169E42837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift */; }; - 201169E72837B5CA00F92B46 /* SourceControlManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201169E62837B5CA00F92B46 /* SourceControlManager.swift */; }; - 2072FA13280D74ED00C7F8D4 /* HistoryInspectorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2072FA12280D74ED00C7F8D4 /* HistoryInspectorModel.swift */; }; - 20D839AB280DEB2900B27357 /* NoSelectionInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D839AA280DEB2900B27357 /* NoSelectionInspectorView.swift */; }; - 20D839AE280E0CA700B27357 /* HistoryPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D839AD280E0CA700B27357 /* HistoryPopoverView.swift */; }; - 20EBB501280C325D00F3A5DA /* FileInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20EBB500280C325D00F3A5DA /* FileInspectorView.swift */; }; - 20EBB503280C327C00F3A5DA /* HistoryInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20EBB502280C327C00F3A5DA /* HistoryInspectorView.swift */; }; - 20EBB505280C329800F3A5DA /* CommitListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20EBB504280C329800F3A5DA /* CommitListItemView.swift */; }; - 2806E9022979588B000040F4 /* Contributor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2806E9012979588B000040F4 /* Contributor.swift */; }; - 2806E904297958B9000040F4 /* ContributorRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2806E903297958B9000040F4 /* ContributorRowView.swift */; }; - 2813F93827ECC4AA00E305E4 /* FindNavigatorResultList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B327E9989900CB86D0 /* FindNavigatorResultList.swift */; }; - 2813F93927ECC4C300E305E4 /* NavigatorAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E627E3413200D46668 /* NavigatorAreaView.swift */; }; - 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 2816F593280CF50500DD548B /* CodeEditSymbols */; }; - 283BDCBD2972EEBD002AFF81 /* Package.resolved in Resources */ = {isa = PBXBuildFile; fileRef = 283BDCBC2972EEBD002AFF81 /* Package.resolved */; }; 283BDCC52972F236002AFF81 /* AcknowledgementsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283BDCC42972F236002AFF81 /* AcknowledgementsTests.swift */; }; - 2847019E27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */; }; - 284DC84F2978B7B400BF2770 /* ContributorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284DC84E2978B7B400BF2770 /* ContributorsView.swift */; }; 284DC8512978BA2600BF2770 /* .all-contributorsrc in Resources */ = {isa = PBXBuildFile; fileRef = 284DC8502978BA2600BF2770 /* .all-contributorsrc */; }; - 285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */; }; - 286471AB27ED51FD0039369D /* ProjectNavigatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286471AA27ED51FD0039369D /* ProjectNavigatorView.swift */; }; - 287776E927E34BC700D46668 /* EditorTabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E827E34BC700D46668 /* EditorTabBarView.swift */; }; - 2897E1C72979A29200741E32 /* TrackableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2897E1C62979A29200741E32 /* TrackableScrollView.swift */; }; - 28B8F884280FFE4600596236 /* NSTableView+Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B8F883280FFE4600596236 /* NSTableView+Background.swift */; }; - 2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468438427DC76E200F8E88E /* AppDelegate.swift */; }; 2B7AC06B282452FB0082A5B8 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B7AC06A282452FB0082A5B8 /* Media.xcassets */; }; 2BE487EF28245162003F3F64 /* FinderSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE487EE28245162003F3F64 /* FinderSync.swift */; }; - 2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; }; - 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; }; + 30F67AD32BAF76A200AB0168 /* EffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AB22BAF76A200AB0168 /* EffectView.swift */; }; + 30F67AD42BAF76A200AB0168 /* RecentProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AB42BAF76A200AB0168 /* RecentProject.swift */; }; + 30F67AD52BAF76A200AB0168 /* ServiceContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AB62BAF76A200AB0168 /* ServiceContainer.swift */; }; + 30F67AD62BAF76A200AB0168 /* ServiceTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AB72BAF76A200AB0168 /* ServiceTypes.swift */; }; + 30F67AD72BAF76A200AB0168 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AB82BAF76A200AB0168 /* ServiceWrapper.swift */; }; + 30F67AD82BAF76A200AB0168 /* DocumentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ABA2BAF76A200AB0168 /* DocumentService.swift */; }; + 30F67AD92BAF76A200AB0168 /* NSPasteboardProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ABC2BAF76A200AB0168 /* NSPasteboardProvider.swift */; }; + 30F67ADA2BAF76A200AB0168 /* PasteboardService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ABD2BAF76A200AB0168 /* PasteboardService.swift */; }; + 30F67ADB2BAF76A200AB0168 /* UIPasteboardProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ABE2BAF76A200AB0168 /* UIPasteboardProvider.swift */; }; + 30F67ADC2BAF76A200AB0168 /* Bundle+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AC12BAF76A200AB0168 /* Bundle+Info.swift */; }; + 30F67ADD2BAF76A200AB0168 /* NSApp+openWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AC32BAF76A200AB0168 /* NSApp+openWindow.swift */; }; + 30F67ADE2BAF76A200AB0168 /* ProjectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AC62BAF76A200AB0168 /* ProjectManager.swift */; }; + 30F67ADF2BAF76A200AB0168 /* SceneID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AC72BAF76A200AB0168 /* SceneID.swift */; }; + 30F67AE02BAF76A200AB0168 /* RecentProjectItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67AC92BAF76A200AB0168 /* RecentProjectItem.swift */; }; + 30F67AE12BAF76A200AB0168 /* RecentProjectsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ACA2BAF76A200AB0168 /* RecentProjectsListView.swift */; }; + 30F67AE22BAF76A200AB0168 /* WelcomeActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ACB2BAF76A200AB0168 /* WelcomeActionView.swift */; }; + 30F67AE32BAF76A200AB0168 /* WelcomeScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ACC2BAF76A200AB0168 /* WelcomeScene.swift */; }; + 30F67AE42BAF76A200AB0168 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ACD2BAF76A200AB0168 /* WelcomeView.swift */; }; + 30F67AE52BAF76A200AB0168 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F67ACE2BAF76A200AB0168 /* WelcomeWindowView.swift */; }; 3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; }; 3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; }; - 4E7F066629602E7B00BB3C12 /* CodeEditSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */; }; 4EE96ECB2960565E00FFBEA8 /* DocumentsUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */; }; 4EE96ECE296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */; }; - 581550CF29FBD30400684881 /* StandardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581550CC29FBD30400684881 /* StandardTableViewCell.swift */; }; - 581550D029FBD30400684881 /* FileSystemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581550CD29FBD30400684881 /* FileSystemTableViewCell.swift */; }; - 581550D129FBD30400684881 /* TextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581550CE29FBD30400684881 /* TextTableViewCell.swift */; }; - 581550D429FBD37D00684881 /* ProjectNavigatorToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581550D329FBD37D00684881 /* ProjectNavigatorToolbarBottom.swift */; }; - 581BFB672926431000D251EC /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581BFB5A2926431000D251EC /* WelcomeWindowView.swift */; }; - 581BFB682926431000D251EC /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581BFB5B2926431000D251EC /* WelcomeView.swift */; }; - 581BFB692926431000D251EC /* WelcomeActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581BFB5C2926431000D251EC /* WelcomeActionView.swift */; }; - 581BFB6B2926431000D251EC /* RecentProjectItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581BFB5E2926431000D251EC /* RecentProjectItem.swift */; }; - 582213F0291834A500EFE361 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582213EF291834A500EFE361 /* AboutView.swift */; }; 583E528C29361B39001AB554 /* CodeEditUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583E527529361B39001AB554 /* CodeEditUITests.swift */; }; 583E528D29361B39001AB554 /* testHelpButtonDark.1.png in Resources */ = {isa = PBXBuildFile; fileRef = 583E527929361B39001AB554 /* testHelpButtonDark.1.png */; }; 583E528E29361B39001AB554 /* testEffectViewLight.1.png in Resources */ = {isa = PBXBuildFile; fileRef = 583E527A29361B39001AB554 /* testEffectViewLight.1.png */; }; @@ -88,410 +47,32 @@ 583E529629361B39001AB554 /* testSegmentedControlDark.1.png in Resources */ = {isa = PBXBuildFile; fileRef = 583E528229361B39001AB554 /* testSegmentedControlDark.1.png */; }; 583E529729361B39001AB554 /* testEffectViewDark.1.png in Resources */ = {isa = PBXBuildFile; fileRef = 583E528329361B39001AB554 /* testEffectViewDark.1.png */; }; 583E529829361B39001AB554 /* testBranchPickerLight.1.png in Resources */ = {isa = PBXBuildFile; fileRef = 583E528429361B39001AB554 /* testBranchPickerLight.1.png */; }; - 583E529C29361BAB001AB554 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 583E529B29361BAB001AB554 /* SnapshotTesting */; }; - 58710159298EB80000951BA4 /* CEWorkspaceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58710158298EB80000951BA4 /* CEWorkspaceFileManager.swift */; }; - 5878DA82291863F900DD95A3 /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DA81291863F900DD95A3 /* AcknowledgementsView.swift */; }; - 5878DA842918642000DD95A3 /* ParsePackagesResolved.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DA832918642000DD95A3 /* ParsePackagesResolved.swift */; }; - 5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DA862918642F00DD95A3 /* AcknowledgementsViewModel.swift */; }; - 5878DAA5291AE76700DD95A3 /* QuickOpenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA1291AE76700DD95A3 /* QuickOpenView.swift */; }; - 5878DAA6291AE76700DD95A3 /* QuickOpenPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA2291AE76700DD95A3 /* QuickOpenPreviewView.swift */; }; - 5878DAA7291AE76700DD95A3 /* QuickOpenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA3291AE76700DD95A3 /* QuickOpenViewModel.swift */; }; - 5878DAA8291AE76700DD95A3 /* QuickOpenItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA4291AE76700DD95A3 /* QuickOpenItem.swift */; }; - 5878DAB0291D627C00DD95A3 /* EditorPathBarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAAD291D627C00DD95A3 /* EditorPathBarMenu.swift */; }; - 5878DAB1291D627C00DD95A3 /* EditorPathBarComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAAE291D627C00DD95A3 /* EditorPathBarComponent.swift */; }; - 5878DAB2291D627C00DD95A3 /* EditorPathBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAAF291D627C00DD95A3 /* EditorPathBarView.swift */; }; - 58798218292D92370085B254 /* String+SafeOffset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798213292D92370085B254 /* String+SafeOffset.swift */; }; - 58798219292D92370085B254 /* SearchModeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798215292D92370085B254 /* SearchModeModel.swift */; }; - 5879821A292D92370085B254 /* SearchResultModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798216292D92370085B254 /* SearchResultModel.swift */; }; - 5879821B292D92370085B254 /* SearchResultMatchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798217292D92370085B254 /* SearchResultMatchModel.swift */; }; - 58798233292E30B90085B254 /* FeedbackToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5879822B292E30B90085B254 /* FeedbackToolbar.swift */; }; - 58798234292E30B90085B254 /* FeedbackIssueArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5879822D292E30B90085B254 /* FeedbackIssueArea.swift */; }; - 58798235292E30B90085B254 /* FeedbackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5879822E292E30B90085B254 /* FeedbackModel.swift */; }; - 58798236292E30B90085B254 /* FeedbackType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5879822F292E30B90085B254 /* FeedbackType.swift */; }; - 58798237292E30B90085B254 /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798230292E30B90085B254 /* FeedbackView.swift */; }; - 58798238292E30B90085B254 /* FeedbackWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798232292E30B90085B254 /* FeedbackWindowController.swift */; }; - 5879824F292E78D80085B254 /* CodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798248292E78D80085B254 /* CodeFileView.swift */; }; - 58798250292E78D80085B254 /* CodeFileDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798249292E78D80085B254 /* CodeFileDocument.swift */; }; - 58798251292E78D80085B254 /* OtherFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5879824B292E78D80085B254 /* OtherFileView.swift */; }; - 58798252292E78D80085B254 /* ImageFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5879824D292E78D80085B254 /* ImageFileView.swift */; }; - 58798284292ED0FB0085B254 /* TerminalEmulatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798280292ED0FB0085B254 /* TerminalEmulatorView.swift */; }; - 58798285292ED0FB0085B254 /* TerminalEmulatorView+Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798281292ED0FB0085B254 /* TerminalEmulatorView+Coordinator.swift */; }; - 58798286292ED0FB0085B254 /* SwiftTerm+Color+Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58798283292ED0FB0085B254 /* SwiftTerm+Color+Init.swift */; }; - 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 58798289292ED15F0085B254 /* SwiftTerm */; }; 587B60F82934124200D5CD8F /* CEWorkspaceFileManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B60F72934124100D5CD8F /* CEWorkspaceFileManagerTests.swift */; }; 587B61012934170A00D5CD8F /* UnitTests_Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B61002934170A00D5CD8F /* UnitTests_Extensions.swift */; }; 587B612E293419B700D5CD8F /* CodeFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B612D293419B700D5CD8F /* CodeFileTests.swift */; }; - 587B9D9F29300ABD00AC7927 /* SegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8829300ABD00AC7927 /* SegmentedControl.swift */; }; - 587B9DA029300ABD00AC7927 /* PanelDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8929300ABD00AC7927 /* PanelDivider.swift */; }; - 587B9DA229300ABD00AC7927 /* EffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8B29300ABD00AC7927 /* EffectView.swift */; }; - 587B9DA329300ABD00AC7927 /* SettingsTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8C29300ABD00AC7927 /* SettingsTextEditor.swift */; }; - 587B9DA429300ABD00AC7927 /* OverlayPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8D29300ABD00AC7927 /* OverlayPanel.swift */; }; - 587B9DA529300ABD00AC7927 /* PressActionsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8E29300ABD00AC7927 /* PressActionsModifier.swift */; }; - 587B9DA629300ABD00AC7927 /* ToolbarBranchPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D8F29300ABD00AC7927 /* ToolbarBranchPicker.swift */; }; - 587B9DA729300ABD00AC7927 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9D9029300ABD00AC7927 /* HelpButton.swift */; }; - 587B9E5A29301D8F00AC7927 /* GitCloneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E0729301D8F00AC7927 /* GitCloneView.swift */; }; - 587B9E5B29301D8F00AC7927 /* GitCheckoutBranchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E0829301D8F00AC7927 /* GitCheckoutBranchView.swift */; }; - 587B9E5C29301D8F00AC7927 /* Parameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E0A29301D8F00AC7927 /* Parameters.swift */; }; - 587B9E5D29301D8F00AC7927 /* GitLabUserRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E0D29301D8F00AC7927 /* GitLabUserRouter.swift */; }; - 587B9E5E29301D8F00AC7927 /* GitLabCommitRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E0E29301D8F00AC7927 /* GitLabCommitRouter.swift */; }; - 587B9E5F29301D8F00AC7927 /* GitLabProjectRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E0F29301D8F00AC7927 /* GitLabProjectRouter.swift */; }; - 587B9E6029301D8F00AC7927 /* GitLabOAuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1029301D8F00AC7927 /* GitLabOAuthRouter.swift */; }; - 587B9E6129301D8F00AC7927 /* GitLabOAuthConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1129301D8F00AC7927 /* GitLabOAuthConfiguration.swift */; }; - 587B9E6229301D8F00AC7927 /* GitLabConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1229301D8F00AC7927 /* GitLabConfiguration.swift */; }; - 587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1329301D8F00AC7927 /* GitLabAccount.swift */; }; - 587B9E6429301D8F00AC7927 /* GitLabCommit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1529301D8F00AC7927 /* GitLabCommit.swift */; }; - 587B9E6529301D8F00AC7927 /* GitLabGroupAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1629301D8F00AC7927 /* GitLabGroupAccess.swift */; }; - 587B9E6629301D8F00AC7927 /* GitLabProjectHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1729301D8F00AC7927 /* GitLabProjectHook.swift */; }; - 587B9E6729301D8F00AC7927 /* GitLabEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1829301D8F00AC7927 /* GitLabEventData.swift */; }; - 587B9E6829301D8F00AC7927 /* GitLabAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1929301D8F00AC7927 /* GitLabAccountModel.swift */; }; - 587B9E6929301D8F00AC7927 /* GitLabEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1A29301D8F00AC7927 /* GitLabEvent.swift */; }; - 587B9E6A29301D8F00AC7927 /* GitLabPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1B29301D8F00AC7927 /* GitLabPermissions.swift */; }; - 587B9E6B29301D8F00AC7927 /* GitLabAvatarURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1C29301D8F00AC7927 /* GitLabAvatarURL.swift */; }; - 587B9E6C29301D8F00AC7927 /* GitLabNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1D29301D8F00AC7927 /* GitLabNamespace.swift */; }; - 587B9E6D29301D8F00AC7927 /* GitLabEventNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1E29301D8F00AC7927 /* GitLabEventNote.swift */; }; - 587B9E6E29301D8F00AC7927 /* GitLabProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E1F29301D8F00AC7927 /* GitLabProject.swift */; }; - 587B9E6F29301D8F00AC7927 /* GitLabProjectAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2029301D8F00AC7927 /* GitLabProjectAccess.swift */; }; - 587B9E7029301D8F00AC7927 /* GitLabUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2129301D8F00AC7927 /* GitLabUser.swift */; }; - 587B9E7129301D8F00AC7927 /* GitURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2329301D8F00AC7927 /* GitURLSession.swift */; }; - 587B9E7229301D8F00AC7927 /* GitJSONPostRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2429301D8F00AC7927 /* GitJSONPostRouter.swift */; }; - 587B9E7329301D8F00AC7927 /* GitRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2529301D8F00AC7927 /* GitRouter.swift */; }; - 587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2729301D8F00AC7927 /* URL+URLParameters.swift */; }; - 587B9E7529301D8F00AC7927 /* String+QueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2829301D8F00AC7927 /* String+QueryParameters.swift */; }; - 587B9E7629301D8F00AC7927 /* GitTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2929301D8F00AC7927 /* GitTime.swift */; }; - 587B9E7729301D8F00AC7927 /* String+PercentEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2A29301D8F00AC7927 /* String+PercentEncoding.swift */; }; - 587B9E7929301D8F00AC7927 /* GitHubIssueRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2E29301D8F00AC7927 /* GitHubIssueRouter.swift */; }; - 587B9E7A29301D8F00AC7927 /* GitHubReviewsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E2F29301D8F00AC7927 /* GitHubReviewsRouter.swift */; }; - 587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3029301D8F00AC7927 /* GitHubRouter.swift */; }; - 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3129301D8F00AC7927 /* GitHubRepositoryRouter.swift */; }; - 587B9E7D29301D8F00AC7927 /* GitHubPullRequestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3229301D8F00AC7927 /* GitHubPullRequestRouter.swift */; }; - 587B9E7E29301D8F00AC7927 /* GitHubGistRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3329301D8F00AC7927 /* GitHubGistRouter.swift */; }; - 587B9E7F29301D8F00AC7927 /* GitHubUserRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3429301D8F00AC7927 /* GitHubUserRouter.swift */; }; - 587B9E8029301D8F00AC7927 /* GitHubConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3529301D8F00AC7927 /* GitHubConfiguration.swift */; }; - 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3629301D8F00AC7927 /* PublicKey.swift */; }; - 587B9E8229301D8F00AC7927 /* GitHubPreviewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3729301D8F00AC7927 /* GitHubPreviewHeader.swift */; }; - 587B9E8329301D8F00AC7927 /* GitHubPullRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3929301D8F00AC7927 /* GitHubPullRequest.swift */; }; - 587B9E8429301D8F00AC7927 /* GitHubUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3A29301D8F00AC7927 /* GitHubUser.swift */; }; - 587B9E8529301D8F00AC7927 /* GitHubReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3B29301D8F00AC7927 /* GitHubReview.swift */; }; - 587B9E8729301D8F00AC7927 /* GitHubRepositories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3D29301D8F00AC7927 /* GitHubRepositories.swift */; }; - 587B9E8829301D8F00AC7927 /* GitHubFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3E29301D8F00AC7927 /* GitHubFiles.swift */; }; - 587B9E8929301D8F00AC7927 /* GitHubGist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3F29301D8F00AC7927 /* GitHubGist.swift */; }; - 587B9E8A29301D8F00AC7927 /* GitHubIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4029301D8F00AC7927 /* GitHubIssue.swift */; }; - 587B9E8B29301D8F00AC7927 /* GitHubAccount+deleteReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4129301D8F00AC7927 /* GitHubAccount+deleteReference.swift */; }; - 587B9E8C29301D8F00AC7927 /* GitHubOpenness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4229301D8F00AC7927 /* GitHubOpenness.swift */; }; - 587B9E8D29301D8F00AC7927 /* GitHubAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4329301D8F00AC7927 /* GitHubAccount.swift */; }; - 587B9E8E29301D8F00AC7927 /* BitBucketRepositoryRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4629301D8F00AC7927 /* BitBucketRepositoryRouter.swift */; }; - 587B9E8F29301D8F00AC7927 /* BitBucketUserRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4729301D8F00AC7927 /* BitBucketUserRouter.swift */; }; - 587B9E9029301D8F00AC7927 /* BitBucketTokenRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4829301D8F00AC7927 /* BitBucketTokenRouter.swift */; }; - 587B9E9129301D8F00AC7927 /* BitBucketOAuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4929301D8F00AC7927 /* BitBucketOAuthRouter.swift */; }; - 587B9E9229301D8F00AC7927 /* BitBucketAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4A29301D8F00AC7927 /* BitBucketAccount.swift */; }; - 587B9E9329301D8F00AC7927 /* BitBucketOAuthConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4B29301D8F00AC7927 /* BitBucketOAuthConfiguration.swift */; }; - 587B9E9429301D8F00AC7927 /* BitBucketTokenConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4C29301D8F00AC7927 /* BitBucketTokenConfiguration.swift */; }; - 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4E29301D8F00AC7927 /* BitBucketUser.swift */; }; - 587B9E9629301D8F00AC7927 /* BitBucketRepositories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E4F29301D8F00AC7927 /* BitBucketRepositories.swift */; }; - 587B9E9729301D8F00AC7927 /* BitBucketAccount+Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E5029301D8F00AC7927 /* BitBucketAccount+Token.swift */; }; - 587B9E9829301D8F00AC7927 /* GitCommit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E5329301D8F00AC7927 /* GitCommit.swift */; }; - 587B9E9929301D8F00AC7927 /* GitChangedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E5429301D8F00AC7927 /* GitChangedFile.swift */; }; - 587B9E9A29301D8F00AC7927 /* GitType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E5529301D8F00AC7927 /* GitType.swift */; }; - 587FB99029C1246400B519DD /* EditorTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587FB98F29C1246400B519DD /* EditorTabView.swift */; }; - 58822524292C280D00E83CDE /* StatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822509292C280D00E83CDE /* StatusBarView.swift */; }; - 58822525292C280D00E83CDE /* StatusBarMenuStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882250B292C280D00E83CDE /* StatusBarMenuStyle.swift */; }; - 58822526292C280D00E83CDE /* StatusBarBreakpointButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882250C292C280D00E83CDE /* StatusBarBreakpointButton.swift */; }; - 58822527292C280D00E83CDE /* StatusBarIndentSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882250D292C280D00E83CDE /* StatusBarIndentSelector.swift */; }; - 58822528292C280D00E83CDE /* StatusBarEncodingSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882250E292C280D00E83CDE /* StatusBarEncodingSelector.swift */; }; - 58822529292C280D00E83CDE /* StatusBarLineEndSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882250F292C280D00E83CDE /* StatusBarLineEndSelector.swift */; }; - 5882252A292C280D00E83CDE /* StatusBarToggleUtilityAreaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822510292C280D00E83CDE /* StatusBarToggleUtilityAreaButton.swift */; }; - 5882252B292C280D00E83CDE /* StatusBarCursorLocationLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822511292C280D00E83CDE /* StatusBarCursorLocationLabel.swift */; }; - 5882252C292C280D00E83CDE /* UtilityAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822513292C280D00E83CDE /* UtilityAreaView.swift */; }; - 5882252D292C280D00E83CDE /* StatusBarSplitTerminalButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822515292C280D00E83CDE /* StatusBarSplitTerminalButton.swift */; }; - 5882252E292C280D00E83CDE /* StatusBarMaximizeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822516292C280D00E83CDE /* StatusBarMaximizeButton.swift */; }; - 5882252F292C280D00E83CDE /* StatusBarClearButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822517292C280D00E83CDE /* StatusBarClearButton.swift */; }; - 58822530292C280D00E83CDE /* FilterTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58822518292C280D00E83CDE /* FilterTextField.swift */; }; - 58822531292C280D00E83CDE /* View+isHovering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882251A292C280D00E83CDE /* View+isHovering.swift */; }; - 58822532292C280D00E83CDE /* UtilityAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882251C292C280D00E83CDE /* UtilityAreaViewModel.swift */; }; - 58822534292C280D00E83CDE /* CursorLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5882251E292C280D00E83CDE /* CursorLocation.swift */; }; - 588847632992A2A200996D95 /* CEWorkspaceFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588847622992A2A200996D95 /* CEWorkspaceFile.swift */; }; - 588847692992ABCA00996D95 /* Array+SortURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588847682992ABCA00996D95 /* Array+SortURLs.swift */; }; - 5894E59729FEF7740077E59C /* CEWorkspaceFile+Recursion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5894E59629FEF7740077E59C /* CEWorkspaceFile+Recursion.swift */; }; - 58A2E40C29C3975D005CB615 /* CEWorkspaceFileIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A2E40629C3975D005CB615 /* CEWorkspaceFileIcon.swift */; }; - 58A5DF7D2931787A00D1BD5D /* ShellClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A5DF7C2931787A00D1BD5D /* ShellClient.swift */; }; - 58A5DF8029325B5A00D1BD5D /* GitClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A5DF7F29325B5A00D1BD5D /* GitClient.swift */; }; - 58A5DFA229339F6400D1BD5D /* KeybindingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A5DF9E29339F6400D1BD5D /* KeybindingManager.swift */; }; - 58A5DFA329339F6400D1BD5D /* CommandManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A5DF9F29339F6400D1BD5D /* CommandManager.swift */; }; - 58A5DFA529339F6400D1BD5D /* default_keybindings.json in Resources */ = {isa = PBXBuildFile; fileRef = 58A5DFA129339F6400D1BD5D /* default_keybindings.json */; }; - 58AFAA2E2933C69E00482B53 /* EditorTabRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFAA2C2933C69E00482B53 /* EditorTabRepresentable.swift */; }; - 58AFAA2F2933C69E00482B53 /* EditorItemID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFAA2D2933C69E00482B53 /* EditorItemID.swift */; }; - 58D01C94293167DC00C5B6B4 /* Color+HEX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C88293167DC00C5B6B4 /* Color+HEX.swift */; }; - 58D01C95293167DC00C5B6B4 /* Bundle+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C89293167DC00C5B6B4 /* Bundle+Info.swift */; }; - 58D01C96293167DC00C5B6B4 /* Date+Formatted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C8A293167DC00C5B6B4 /* Date+Formatted.swift */; }; - 58D01C97293167DC00C5B6B4 /* String+SHA256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C8C293167DC00C5B6B4 /* String+SHA256.swift */; }; - 58D01C98293167DC00C5B6B4 /* String+RemoveOccurrences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C8D293167DC00C5B6B4 /* String+RemoveOccurrences.swift */; }; - 58D01C99293167DC00C5B6B4 /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C8E293167DC00C5B6B4 /* String+MD5.swift */; }; - 58D01C9A293167DC00C5B6B4 /* CodeEditKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C90293167DC00C5B6B4 /* CodeEditKeychain.swift */; }; - 58D01C9B293167DC00C5B6B4 /* CodeEditKeychainConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C91293167DC00C5B6B4 /* CodeEditKeychainConstants.swift */; }; - 58D01C9D293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D01C93293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift */; }; - 58F2EAEC292FB2B0004A9BDE /* IgnoredFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAAA292FB2B0004A9BDE /* IgnoredFiles.swift */; }; - 58F2EAEF292FB2B0004A9BDE /* ThemeSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAAF292FB2B0004A9BDE /* ThemeSettingsView.swift */; }; 58F2EB03292FB2B0004A9BDE /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EACE292FB2B0004A9BDE /* Documentation.docc */; }; - 58F2EB04292FB2B0004A9BDE /* SourceControlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAD1292FB2B0004A9BDE /* SourceControlSettings.swift */; }; - 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAD2292FB2B0004A9BDE /* Settings.swift */; }; - 58F2EB06292FB2B0004A9BDE /* KeybindingsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAD4292FB2B0004A9BDE /* KeybindingsSettings.swift */; }; - 58F2EB07292FB2B0004A9BDE /* GeneralSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAD6292FB2B0004A9BDE /* GeneralSettings.swift */; }; - 58F2EB08292FB2B0004A9BDE /* TextEditingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAD8292FB2B0004A9BDE /* TextEditingSettings.swift */; }; - 58F2EB09292FB2B0004A9BDE /* TerminalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EADA292FB2B0004A9BDE /* TerminalSettings.swift */; }; - 58F2EB0A292FB2B0004A9BDE /* SettingsData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EADB292FB2B0004A9BDE /* SettingsData.swift */; }; - 58F2EB0B292FB2B0004A9BDE /* AccountsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EADD292FB2B0004A9BDE /* AccountsSettings.swift */; }; - 58F2EB0D292FB2B0004A9BDE /* ThemeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAE0292FB2B0004A9BDE /* ThemeSettings.swift */; }; - 58F2EB0E292FB2B0004A9BDE /* SoftwareUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EAE1292FB2B0004A9BDE /* SoftwareUpdater.swift */; }; - 58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 58F2EB1D292FB954004A9BDE /* Sparkle */; }; - 58FD7608291EA1CB0051D6E4 /* CommandPaletteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD7605291EA1CB0051D6E4 /* CommandPaletteViewModel.swift */; }; - 58FD7609291EA1CB0051D6E4 /* CommandPaletteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD7607291EA1CB0051D6E4 /* CommandPaletteView.swift */; }; - 5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B241BF22B6DDBFF0016E616 /* IgnorePatternListItemView.swift */; }; - 5B698A0A2B262FA000DE9392 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B698A092B262FA000DE9392 /* SearchSettingsView.swift */; }; - 5B698A0D2B26327800DE9392 /* SearchSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B698A0C2B26327800DE9392 /* SearchSettings.swift */; }; - 5B698A0F2B2636A700DE9392 /* SearchSettingsIgnoreGlobPatternItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B698A0E2B2636A700DE9392 /* SearchSettingsIgnoreGlobPatternItemView.swift */; }; - 5B698A162B263BCE00DE9392 /* SearchSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B698A152B263BCE00DE9392 /* SearchSettingsModel.swift */; }; - 5C4BB1E128212B1E00A92FB2 /* World.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4BB1E028212B1E00A92FB2 /* World.swift */; }; - 610C0FDA2B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610C0FD92B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift */; }; - 611191FA2B08CC9000D4459B /* SearchIndexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611191F92B08CC9000D4459B /* SearchIndexer.swift */; }; - 611191FC2B08CCB800D4459B /* SearchIndexer+AsyncController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611191FB2B08CCB800D4459B /* SearchIndexer+AsyncController.swift */; }; - 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611191FD2B08CCD200D4459B /* SearchIndexer+File.swift */; }; - 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611191FF2B08CCD700D4459B /* SearchIndexer+Memory.swift */; }; - 611192022B08CCDC00D4459B /* SearchIndexer+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611192012B08CCDC00D4459B /* SearchIndexer+Search.swift */; }; - 611192042B08CCED00D4459B /* SearchIndexer+ProgressiveSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611192032B08CCED00D4459B /* SearchIndexer+ProgressiveSearch.swift */; }; - 611192062B08CCF600D4459B /* SearchIndexer+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611192052B08CCF600D4459B /* SearchIndexer+Add.swift */; }; - 611192082B08CCFD00D4459B /* SearchIndexer+Terms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611192072B08CCFD00D4459B /* SearchIndexer+Terms.swift */; }; - 6111920C2B08CD0B00D4459B /* SearchIndexer+InternalMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6111920B2B08CD0B00D4459B /* SearchIndexer+InternalMethods.swift */; }; 6130535C2B23933D00D767E3 /* MemoryIndexingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6130535B2B23933D00D767E3 /* MemoryIndexingTests.swift */; }; 6130535F2B23A31300D767E3 /* MemorySearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6130535E2B23A31300D767E3 /* MemorySearchTests.swift */; }; 613053652B23A49300D767E3 /* TemporaryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613053642B23A49300D767E3 /* TemporaryFile.swift */; }; 6130536B2B24722C00D767E3 /* AsyncIndexingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6130536A2B24722C00D767E3 /* AsyncIndexingTests.swift */; }; - 613899B12B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899B02B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift */; }; - 613899B32B6E6FEE00A5CAF6 /* FuzzySearchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899B22B6E6FEE00A5CAF6 /* FuzzySearchable.swift */; }; - 613899B52B6E700300A5CAF6 /* FuzzySearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899B42B6E700300A5CAF6 /* FuzzySearchModels.swift */; }; - 613899B72B6E702F00A5CAF6 /* String+LengthOfMatchingPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899B62B6E702F00A5CAF6 /* String+LengthOfMatchingPrefix.swift */; }; - 613899B92B6E704500A5CAF6 /* String+Normalise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899B82B6E704500A5CAF6 /* String+Normalise.swift */; }; - 613899BC2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899BB2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift */; }; 613899C02B6E70FE00A5CAF6 /* FuzzySearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613899BF2B6E70FE00A5CAF6 /* FuzzySearchTests.swift */; }; - 613DF55E2B08DD5D00E9D902 /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613DF55D2B08DD5D00E9D902 /* FileHelper.swift */; }; - 61538B902B111FE800A88846 /* String+AppearancesOfSubstring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */; }; - 61538B932B11201900A88846 /* String+Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61538B922B11201900A88846 /* String+Character.swift */; }; - 615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615AA2192B0CFD480013FCCC /* LazyStringLoader.swift */; }; 6195E30D2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6195E30C2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift */; }; 6195E30F2B640474007261CA /* WorkspaceDocument+SearchState+FindAndReplaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6195E30E2B640474007261CA /* WorkspaceDocument+SearchState+FindAndReplaceTests.swift */; }; 6195E3112B640485007261CA /* WorkspaceDocument+SearchState+IndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6195E3102B640485007261CA /* WorkspaceDocument+SearchState+IndexTests.swift */; }; - 61A53A7E2B4449870093BF8A /* WorkspaceDocument+Find.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A53A7D2B4449870093BF8A /* WorkspaceDocument+Find.swift */; }; - 61A53A812B4449F00093BF8A /* WorkspaceDocument+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */; }; - 6C049A372A49E2DB00D42923 /* DirectoryEventStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */; }; - 6C05A8AF284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */; }; - 6C092EDA2A53A58600489202 /* EditorLayout+StateRestoration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C092ED92A53A58600489202 /* EditorLayout+StateRestoration.swift */; }; - 6C092EE02A53BFCF00489202 /* WorkspaceStateKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C092EDF2A53BFCF00489202 /* WorkspaceStateKey.swift */; }; - 6C0D0C6829E861B000AE4D3F /* SettingsSidebarFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C0D0C6729E861B000AE4D3F /* SettingsSidebarFix.swift */; }; - 6C0F3A3C2A1D0D5000223D19 /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0F3A3B2A1D0D5000223D19 /* CodeEditKit */; }; - 6C147C4029A328BC0089B630 /* SplitViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3F29A328560089B630 /* SplitViewData.swift */; }; - 6C147C4129A328BF0089B630 /* EditorLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3E29A3281D0089B630 /* EditorLayout.swift */; }; - 6C147C4229A328C10089B630 /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3D29A3281D0089B630 /* Editor.swift */; }; - 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 6C147C4429A329350089B630 /* OrderedCollections */; }; - 6C147C4929A32A080089B630 /* EditorLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C4829A32A080089B630 /* EditorLayoutView.swift */; }; - 6C147C4B29A32A7B0089B630 /* Environment+SplitEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */; }; - 6C147C4D29A32AA30089B630 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C4C29A32AA30089B630 /* EditorView.swift */; }; - 6C14CEB028777D3C001468FE /* FindNavigatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */; }; - 6C14CEB32877A68F001468FE /* FindNavigatorMatchListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */; }; - 6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */; }; - 6C1CC9982B1E770B0002349B /* AsyncFileIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */; }; - 6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */; }; - 6C2149412A1BB9AB00748382 /* LogStream in Frameworks */ = {isa = PBXBuildFile; productRef = 6C2149402A1BB9AB00748382 /* LogStream */; }; - 6C2C155829B4F49100EA60A5 /* SplitViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */; }; - 6C2C155A29B4F4CC00EA60A5 /* Variadic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */; }; - 6C2C155D29B4F4E500EA60A5 /* SplitViewReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */; }; - 6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C156029B4F52F00EA60A5 /* SplitViewModifiers.swift */; }; 6C2C20172A4016FF0047EDF2 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; - 6C4104E3297C87A000F472BA /* BlurButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */; }; - 6C4104E6297C884F00F472BA /* AboutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E5297C884F00F472BA /* AboutDetailView.swift */; }; - 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */; }; - 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */; }; - 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */; }; - 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; - 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */; }; - 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; - 6C578D8129CD294800DC73B2 /* ExtensionActivatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */; }; - 6C578D8429CD343800DC73B2 /* ExtensionDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8329CD343800DC73B2 /* ExtensionDetailView.swift */; }; - 6C578D8729CD345900DC73B2 /* ExtensionSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8629CD345900DC73B2 /* ExtensionSceneView.swift */; }; - 6C578D8929CD36E400DC73B2 /* Commands+ForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8829CD36E400DC73B2 /* Commands+ForEach.swift */; }; - 6C578D8C29CD372700DC73B2 /* ExtensionCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8B29CD372700DC73B2 /* ExtensionCommands.swift */; }; - 6C5B63DE29C76213005454BA /* WindowCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5B63DD29C76213005454BA /* WindowCodeFileView.swift */; }; - 6C5C891B2A3F736500A94FE1 /* FocusedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */; }; - 6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */; }; - 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; }; - 6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */; }; - 6C6BD6F129CD13FA00235D17 /* ExtensionDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */; }; - 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; }; - 6C6BD6F629CD145F00235D17 /* ExtensionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */; }; - 6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F729CD14D100235D17 /* CodeEditKit */; }; - 6C6BD6F929CD14D100235D17 /* CodeEditKit in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F729CD14D100235D17 /* CodeEditKit */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 6C6BD6FC29CD152400235D17 /* codeedit.extension.appextensionpoint in Resources */ = {isa = PBXBuildFile; fileRef = 6C6BD6FB29CD152400235D17 /* codeedit.extension.appextensionpoint */; }; - 6C6BD6FE29CD155200235D17 /* codeedit.extension.appextensionpoint in Embed ExtensionKit ExtensionPoint */ = {isa = PBXBuildFile; fileRef = 6C6BD6FB29CD152400235D17 /* codeedit.extension.appextensionpoint */; }; - 6C6BD70129CD172700235D17 /* ExtensionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD70029CD172700235D17 /* ExtensionsListView.swift */; }; - 6C6BD70429CD17B600235D17 /* ExtensionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD70329CD17B600235D17 /* ExtensionsManager.swift */; }; - 6C7256D729A3D7D000C2D3E0 /* SplitViewControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */; }; - 6C7F37FE2A3EA6FA00217B83 /* View+focusedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7F37FD2A3EA6FA00217B83 /* View+focusedValue.swift */; }; - 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */; }; - 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 6C81916A29B41DD300B75C92 /* DequeModule */; }; - 6C82D6B329BFD88700495C54 /* NavigateCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C82D6B229BFD88700495C54 /* NavigateCommands.swift */; }; - 6C82D6B929BFE34900495C54 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C82D6B829BFE34900495C54 /* HelpCommands.swift */; }; - 6C82D6BC29C00CD900495C54 /* FirstResponderPropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C82D6BB29C00CD900495C54 /* FirstResponderPropertyWrapper.swift */; }; - 6C82D6C629C012AD00495C54 /* NSApp+openWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C82D6C529C012AD00495C54 /* NSApp+openWindow.swift */; }; - 6C91D57229B176FF0059A90D /* EditorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* EditorManager.swift */; }; - 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; - 6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA1AE942B46950000378EAB /* EditorInstance.swift */; }; 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; 6CAAF69229BCC71C00A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; 6CAAF69429BCD78600A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; - 6CABB19E29C5591D00340467 /* NSTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CABB19C29C5591D00340467 /* NSTableViewWrapper.swift */; }; - 6CABB1A129C5593800340467 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CABB1A029C5593800340467 /* OverlayView.swift */; }; - 6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */; }; - 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CB52DC82AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift */; }; 6CB9144B29BEC7F100BC47F2 /* (null) in Sources */ = {isa = PBXBuildFile; }; - 6CBA0D512A1BF524002C6FAA /* SegmentedControlImproved.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */; }; - 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */; }; - 6CBE1CFB2B71DAA6003AC32E /* Loopable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBE1CFA2B71DAA6003AC32E /* Loopable.swift */; }; - 6CBE1D002B720565003AC32E /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CBE1CFF2B720565003AC32E /* CodeEditSourceEditor */; }; - 6CC9E4B229B5669900C97388 /* Environment+ActiveEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */; }; - 6CD03B6A29FC773F001BD1D0 /* SettingsInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */; }; - 6CDA84AD284C1BA000C1CC3A /* EditorTabBarContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDA84AC284C1BA000C1CC3A /* EditorTabBarContextMenu.swift */; }; - 6CDEFC9629E22C2700B7C684 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 6CDEFC9529E22C2700B7C684 /* Introspect */; }; - 6CE622692A2A174A0013085C /* InspectorTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE622682A2A174A0013085C /* InspectorTab.swift */; }; - 6CE6226B2A2A1C730013085C /* UtilityAreaTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE6226A2A2A1C730013085C /* UtilityAreaTab.swift */; }; - 6CE6226E2A2A1CDE0013085C /* NavigatorTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE6226D2A2A1CDE0013085C /* NavigatorTab.swift */; }; - 6CED16E42A3E660D000EC962 /* String+Lines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CED16E32A3E660D000EC962 /* String+Lines.swift */; }; - 6CFF967429BEBCC300182D6F /* FindCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967329BEBCC300182D6F /* FindCommands.swift */; }; - 6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967529BEBCD900182D6F /* FileCommands.swift */; }; - 6CFF967829BEBCF600182D6F /* MainCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967729BEBCF600182D6F /* MainCommands.swift */; }; - 6CFF967A29BEBD2400182D6F /* ViewCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967929BEBD2400182D6F /* ViewCommands.swift */; }; - 6CFF967C29BEBD5200182D6F /* WindowCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */; }; - 850C631029D6B01D00E1444C /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C630F29D6B01D00E1444C /* SettingsView.swift */; }; - 850C631229D6B03400E1444C /* SettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C631129D6B03400E1444C /* SettingsPage.swift */; }; - 852C7E332A587279006BA599 /* SearchableSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852C7E322A587279006BA599 /* SearchableSettingsPage.swift */; }; - 852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852E62002A5C17E500447138 /* PageAndSettings.swift */; }; - 85745D632A38F8D900089AAB /* String+HighlightOccurrences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */; }; - 85773E1E2A3E0A1F00C5D926 /* SettingsSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */; }; - 85CD0C5F2A10CC3200E531FD /* URL+isImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */; }; - 85E4122A2A46C8CA00183F2B /* LocationsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E412292A46C8CA00183F2B /* LocationsSettings.swift */; }; - 9D36E1BF2B5E7D7500443C41 /* GitBranchesGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D36E1BE2B5E7D7500443C41 /* GitBranchesGroup.swift */; }; - B6041F4D29D7A4E9000F3454 /* SettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */; }; - B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */; }; - B607181D2B0C5BE3009CDAB4 /* GitClient+Stash.swift in Sources */ = {isa = PBXBuildFile; fileRef = B607181C2B0C5BE3009CDAB4 /* GitClient+Stash.swift */; }; - B60718202B0C6CE7009CDAB4 /* GitStashEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B607181F2B0C6CE7009CDAB4 /* GitStashEntry.swift */; }; - B60718312B15A9A3009CDAB4 /* CEOutlineGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60718302B15A9A3009CDAB4 /* CEOutlineGroup.swift */; }; - B60718372B170638009CDAB4 /* SourceControlNavigatorRenameBranchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60718362B170638009CDAB4 /* SourceControlNavigatorRenameBranchView.swift */; }; - B607183F2B17DB07009CDAB4 /* SourceControlNavigatorRepositoryView+contextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B607183E2B17DB07009CDAB4 /* SourceControlNavigatorRepositoryView+contextMenu.swift */; }; - B60718422B17DB93009CDAB4 /* SourceControlNavigatorRepositoryView+outlineGroupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60718412B17DB93009CDAB4 /* SourceControlNavigatorRepositoryView+outlineGroupData.swift */; }; - B60718442B17DBE5009CDAB4 /* SourceControlNavigatorRepositoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60718432B17DBE5009CDAB4 /* SourceControlNavigatorRepositoryItem.swift */; }; - B60718462B17DC15009CDAB4 /* RepoOutlineGroupItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60718452B17DC15009CDAB4 /* RepoOutlineGroupItem.swift */; }; - B607184C2B17E037009CDAB4 /* SourceControlStashChangesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B607184B2B17E037009CDAB4 /* SourceControlStashChangesView.swift */; }; - B60BE8BD297A167600841125 /* AcknowledgementRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */; }; - B6152B802ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */; }; - B61A606129F188AB009B43F9 /* ExternalLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61A606029F188AB009B43F9 /* ExternalLink.swift */; }; - B61A606929F4481A009B43F9 /* MonospacedFontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */; }; - B61DA9DF29D929E100BF4A43 /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */; }; - B628B7932B18369800F9775A /* GitClient+Validate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B628B7922B18369800F9775A /* GitClient+Validate.swift */; }; - B628B7B72B223BAD00F9775A /* FindModePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B628B7B62B223BAD00F9775A /* FindModePicker.swift */; }; - B62AEDAA2A1FCBE5009A9F52 /* AreaTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDA92A1FCBE5009A9F52 /* AreaTabBar.swift */; }; - B62AEDB32A1FD95B009A9F52 /* UtilityAreaTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDB22A1FD95B009A9F52 /* UtilityAreaTerminalView.swift */; }; - B62AEDB52A1FE295009A9F52 /* UtilityAreaDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDB42A1FE295009A9F52 /* UtilityAreaDebugView.swift */; }; - B62AEDB82A1FE2DC009A9F52 /* UtilityAreaOutputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDB72A1FE2DC009A9F52 /* UtilityAreaOutputView.swift */; }; - B62AEDBC2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDBB2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift */; }; - B62AEDC92A2704F3009A9F52 /* UtilityAreaTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDC82A2704F3009A9F52 /* UtilityAreaTabView.swift */; }; - B62AEDD12A27B264009A9F52 /* View+paneToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDD02A27B264009A9F52 /* View+paneToolbar.swift */; }; - B62AEDD42A27B29F009A9F52 /* PaneToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDD32A27B29F009A9F52 /* PaneToolbar.swift */; }; - B62AEDD72A27B3D0009A9F52 /* UtilityAreaTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDD62A27B3D0009A9F52 /* UtilityAreaTabViewModel.swift */; }; - B62AEDDC2A27C1B3009A9F52 /* OSLogType+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62AEDDA2A27C1B3009A9F52 /* OSLogType+Color.swift */; }; - B640A99E29E2184700715F20 /* SettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A99D29E2184700715F20 /* SettingsForm.swift */; }; - B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */; }; B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3327DA9E1000EA4DBD /* Assets.xcassets */; }; B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */; }; - B65B10EC2B073913002852CF /* CEContentUnavailableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10EB2B073913002852CF /* CEContentUnavailableView.swift */; }; - B65B10EF2B07C454002852CF /* GitClient+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10EE2B07C454002852CF /* GitClient+Remote.swift */; }; - B65B10F22B07D34F002852CF /* GitRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10F12B07D34F002852CF /* GitRemote.swift */; }; - B65B10F52B081A0C002852CF /* SourceControlAddRemoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10F42B081A0C002852CF /* SourceControlAddRemoteView.swift */; }; - B65B10F82B081A34002852CF /* SourceControlNavigatorNoRemotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10F72B081A34002852CF /* SourceControlNavigatorNoRemotesView.swift */; }; - B65B10FB2B08B054002852CF /* Divided.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10FA2B08B054002852CF /* Divided.swift */; }; - B65B10FE2B08B07D002852CF /* SourceControlNavigatorChangesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10FD2B08B07D002852CF /* SourceControlNavigatorChangesList.swift */; }; - B65B11012B09D5D4002852CF /* GitClient+Pull.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B11002B09D5D4002852CF /* GitClient+Pull.swift */; }; - B65B11042B09DB1C002852CF /* GitClient+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B11032B09DB1C002852CF /* GitClient+Fetch.swift */; }; - B664C3B02B965F6C00816B4E /* NavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B664C3AF2B965F6C00816B4E /* NavigationSettings.swift */; }; - B664C3B32B96634F00816B4E /* NavigationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B664C3B22B96634F00816B4E /* NavigationSettingsView.swift */; }; - B66A4E4529C8E86D004573B4 /* CommandsFixes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */; }; B66A4E4C29C9179B004573B4 /* CodeEditApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4B29C9179B004573B4 /* CodeEditApp.swift */; }; - B66A4E4F29C917B8004573B4 /* WelcomeWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4E29C917B8004573B4 /* WelcomeWindow.swift */; }; - B66A4E5129C917D5004573B4 /* AboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5029C917D5004573B4 /* AboutWindow.swift */; }; - B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5229C91831004573B4 /* CodeEditCommands.swift */; }; - B66A4E5629C918A0004573B4 /* SceneID.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5529C918A0004573B4 /* SceneID.swift */; }; - B67DB0EF2AF3E381002DC647 /* PaneTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0EE2AF3E381002DC647 /* PaneTextField.swift */; }; - B67DB0F62AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0F52AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift */; }; - B67DB0F92AFDF638002DC647 /* IconButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0F82AFDF638002DC647 /* IconButtonStyle.swift */; }; - B67DB0FC2AFDF71F002DC647 /* IconToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0FB2AFDF71F002DC647 /* IconToggleStyle.swift */; }; - B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; }; - B68C7C212A01DEFE004EA6D6 /* GitHubComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C7C202A01DEFE004EA6D6 /* GitHubComment.swift */; }; - B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */; }; - B69BFDC72B0686910050D9A6 /* GitClient+Initiate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */; }; - B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */; }; - B6AB09A12AAABAAE0003A3A6 /* EditorTabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */; }; - B6AB09A32AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */; }; - B6AB09A52AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A42AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift */; }; - B6AB09B32AB919CF0003A3A6 /* View+actionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09B22AB919CF0003A3A6 /* View+actionBar.swift */; }; - B6C4F2A12B3CA37500B2B140 /* SourceControlNavigatorHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C4F2A02B3CA37500B2B140 /* SourceControlNavigatorHistoryView.swift */; }; - B6C4F2A32B3CA74800B2B140 /* CommitDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C4F2A22B3CA74800B2B140 /* CommitDetailsView.swift */; }; - B6C4F2A62B3CABD200B2B140 /* HistoryInspectorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C4F2A52B3CABD200B2B140 /* HistoryInspectorItemView.swift */; }; - B6C4F2A92B3CB00100B2B140 /* CommitDetailsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C4F2A82B3CB00100B2B140 /* CommitDetailsHeaderView.swift */; }; - B6C4F2AC2B3CC4D000B2B140 /* CommitChangedFileListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C4F2AB2B3CC4D000B2B140 /* CommitChangedFileListItemView.swift */; }; - B6C6A42A297716A500A3D28F /* EditorTabCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A429297716A500A3D28F /* EditorTabCloseButton.swift */; }; - B6C6A42E29771A8D00A3D28F /* EditorTabButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42D29771A8D00A3D28F /* EditorTabButtonStyle.swift */; }; - B6C6A43029771F7100A3D28F /* EditorTabBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42F29771F7100A3D28F /* EditorTabBackground.swift */; }; - B6D7EA592971078500301FAC /* InspectorSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D7EA582971078500301FAC /* InspectorSection.swift */; }; - B6D7EA5C297107DD00301FAC /* InspectorField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D7EA5B297107DD00301FAC /* InspectorField.swift */; }; - B6E41C7029DD157F0088F9F4 /* AccountsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */; }; - B6E41C7429DD40010088F9F4 /* View+HideSidebarToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */; }; - B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */; }; - B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */; }; - B6E41C8B29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */; }; - B6E41C8F29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */; }; - B6E41C9429DEAE260088F9F4 /* SourceControlAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */; }; - B6E55C3B2A95368E003ECC7D /* EditorTabsOverflowShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E55C3A2A95368E003ECC7D /* EditorTabsOverflowShadow.swift */; }; - B6EA1FE529DA33DB001BF195 /* ThemeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FE429DA33DB001BF195 /* ThemeModel.swift */; }; - B6EA1FE729DA341D001BF195 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FE629DA341D001BF195 /* Theme.swift */; }; - B6EA1FF529DA380E001BF195 /* TextEditingSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FF429DA380E001BF195 /* TextEditingSettingsView.swift */; }; - B6EA1FF829DB78DB001BF195 /* ThemeSettingThemeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FF729DB78DB001BF195 /* ThemeSettingThemeRow.swift */; }; - B6EA1FFB29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FFA29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift */; }; - B6EA1FFD29DB792C001BF195 /* ThemeSettingsColorPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FFC29DB792C001BF195 /* ThemeSettingsColorPreview.swift */; }; - B6EA200029DB7966001BF195 /* SettingsColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA1FFF29DB7966001BF195 /* SettingsColorPicker.swift */; }; - B6EA200229DB7F81001BF195 /* View+ConstrainHeightToWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EA200129DB7F81001BF195 /* View+ConstrainHeightToWindow.swift */; }; - B6EE989027E8879A00CDD8AB /* InspectorAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EE988F27E8879A00CDD8AB /* InspectorAreaView.swift */; }; - B6F0517029D9E36800D72287 /* LocationsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F0516F29D9E36800D72287 /* LocationsSettingsView.swift */; }; - B6F0517729D9E3AD00D72287 /* SourceControlGeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F0517629D9E3AD00D72287 /* SourceControlGeneralView.swift */; }; - B6F0517929D9E3C900D72287 /* SourceControlGitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F0517829D9E3C900D72287 /* SourceControlGitView.swift */; }; - B6F0517B29D9E46400D72287 /* SourceControlSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F0517A29D9E46400D72287 /* SourceControlSettingsView.swift */; }; - B6F0517D29D9E4B100D72287 /* TerminalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F0517C29D9E4B100D72287 /* TerminalSettingsView.swift */; }; B6FF04782B6C08AC002C2C78 /* DefaultThemes in Resources */ = {isa = PBXBuildFile; fileRef = B6FF04772B6C08AC002C2C78 /* DefaultThemes */; }; - D7012EE827E757850001E1EF /* FindNavigatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7012EE727E757850001E1EF /* FindNavigatorView.swift */; }; D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */; }; D7211D4727E06BFE008F2ED7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D7211D4927E06BFE008F2ED7 /* Localizable.strings */; }; - D7DC4B76298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */; }; - D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; - D7E201B227E8D50000CB86D0 /* FindNavigatorForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* FindNavigatorForm.swift */; }; - DE513F52281B672D002260B9 /* EditorTabBarAccessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE513F51281B672D002260B9 /* EditorTabBarAccessory.swift */; }; - DE6405A62817734700881FDF /* EditorTabBarNative.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6405A52817734700881FDF /* EditorTabBarNative.swift */; }; - DE6F77872813625500D00A76 /* EditorTabBarDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */; }; - EC0870F72A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 2BE487F228245162003F3F64 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = B658FB2427DA9E0F00EA4DBD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 2BE487EB28245162003F3F64; - remoteInfo = OpenWithCodeEdit; - }; B658FB3E27DA9E1000EA4DBD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B658FB2427DA9E0F00EA4DBD /* Project object */; @@ -515,29 +96,16 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 6C6BD6F929CD14D100235D17 /* CodeEditKit in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - 2BE487F528245162003F3F64 /* Embed Foundation Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - 2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */, - ); - name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; 6C6BD6FD29CD154900235D17 /* Embed ExtensionKit ExtensionPoint */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(EXTENSIONS_FOLDER_PATH)"; dstSubfolderSpec = 16; files = ( - 6C6BD6FE29CD155200235D17 /* codeedit.extension.appextensionpoint in Embed ExtensionKit ExtensionPoint */, ); name = "Embed ExtensionKit ExtensionPoint"; runOnlyForDeploymentPostprocessing = 0; @@ -545,78 +113,41 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 041FC6AC2AE437CE00C1F65A /* SourceControlNavigatorNewBranchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorNewBranchView.swift; sourceTree = ""; }; - 043BCF02281DA18A000AC47C /* WorkspaceDocument+SearchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+SearchState.swift"; sourceTree = ""; }; - 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditDocumentController.swift; sourceTree = ""; }; - 043C321527E3201F006AE443 /* WorkspaceDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceDocument.swift; sourceTree = ""; }; 04660F6027E3A68A00477777 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditWindowController.swift; sourceTree = ""; }; - 0468438427DC76E200F8E88E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCodeFileView.swift; sourceTree = ""; }; - 04BA7C0A2AE2A2D100584E1C /* GitBranch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitBranch.swift; sourceTree = ""; }; - 04BA7C0D2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorChangesCommitView.swift; sourceTree = ""; }; - 04BA7C112AE2AA7300584E1C /* GitCheckoutBranchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitCheckoutBranchViewModel.swift; sourceTree = ""; }; - 04BA7C122AE2AA7300584E1C /* GitCloneViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitCloneViewModel.swift; sourceTree = ""; }; - 04BA7C182AE2D7C600584E1C /* GitClient+Branches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Branches.swift"; sourceTree = ""; }; - 04BA7C1B2AE2D84100584E1C /* GitClient+Commit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Commit.swift"; sourceTree = ""; }; - 04BA7C1D2AE2D8A000584E1C /* GitClient+Clone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Clone.swift"; sourceTree = ""; }; - 04BA7C1F2AE2D92B00584E1C /* GitClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Status.swift"; sourceTree = ""; }; - 04BA7C212AE2D95E00584E1C /* GitClient+CommitHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+CommitHistory.swift"; sourceTree = ""; }; - 04BA7C232AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorSyncView.swift; sourceTree = ""; }; - 04BA7C262AE2E9F100584E1C /* GitClient+Push.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Push.swift"; sourceTree = ""; }; - 04BC1CDD2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorFileTabCloseButton.swift; sourceTree = ""; }; - 201169D62837B2E300F92B46 /* SourceControlNavigatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorView.swift; sourceTree = ""; }; - 201169DA2837B34000F92B46 /* SourceControlNavigatorChangedFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorChangedFileView.swift; sourceTree = ""; }; - 201169DC2837B3AC00F92B46 /* SourceControlNavigatorToolbarBottom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorToolbarBottom.swift; sourceTree = ""; }; - 201169E12837B3D800F92B46 /* SourceControlNavigatorChangesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorChangesView.swift; sourceTree = ""; }; - 201169E42837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorRepositoryView.swift; sourceTree = ""; }; - 201169E62837B5CA00F92B46 /* SourceControlManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlManager.swift; sourceTree = ""; }; - 2072FA12280D74ED00C7F8D4 /* HistoryInspectorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryInspectorModel.swift; sourceTree = ""; }; - 20D839AA280DEB2900B27357 /* NoSelectionInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoSelectionInspectorView.swift; sourceTree = ""; }; - 20D839AD280E0CA700B27357 /* HistoryPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryPopoverView.swift; sourceTree = ""; }; - 20EBB500280C325D00F3A5DA /* FileInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileInspectorView.swift; sourceTree = ""; }; - 20EBB502280C327C00F3A5DA /* HistoryInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryInspectorView.swift; sourceTree = ""; }; - 20EBB504280C329800F3A5DA /* CommitListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommitListItemView.swift; sourceTree = ""; }; 28052DFB29730DE300F4F90A /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 28052DFC29730DF600F4F90A /* Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Alpha.xcconfig; sourceTree = ""; }; 28052DFD29730E0300F4F90A /* Beta.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Beta.xcconfig; sourceTree = ""; }; 28052DFE29730E0B00F4F90A /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 2806E9012979588B000040F4 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = ""; }; - 2806E903297958B9000040F4 /* ContributorRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorRowView.swift; sourceTree = ""; }; - 283BDCBC2972EEBD002AFF81 /* Package.resolved */ = {isa = PBXFileReference; lastKnownFileType = text; name = Package.resolved; path = CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = ""; }; 283BDCC42972F236002AFF81 /* AcknowledgementsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsTests.swift; sourceTree = ""; }; - 2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorOutlineView.swift; sourceTree = ""; }; - 284DC84E2978B7B400BF2770 /* ContributorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsView.swift; sourceTree = ""; }; 284DC8502978BA2600BF2770 /* .all-contributorsrc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ".all-contributorsrc"; sourceTree = ""; }; - 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorViewController.swift; sourceTree = ""; }; - 285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorTableViewCell.swift; sourceTree = ""; }; - 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorMenu.swift; sourceTree = ""; }; - 286471AA27ED51FD0039369D /* ProjectNavigatorView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorView.swift; sourceTree = ""; tabWidth = 4; }; - 287776E627E3413200D46668 /* NavigatorAreaView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = NavigatorAreaView.swift; sourceTree = ""; tabWidth = 4; }; - 287776E827E34BC700D46668 /* EditorTabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarView.swift; sourceTree = ""; }; - 2897E1C62979A29200741E32 /* TrackableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackableScrollView.swift; sourceTree = ""; }; - 28B8F883280FFE4600596236 /* NSTableView+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Background.swift"; sourceTree = ""; }; 2B15CA0028254139004E8F22 /* OpenWithCodeEdit.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = OpenWithCodeEdit.entitlements; sourceTree = ""; }; 2B7AC06A282452FB0082A5B8 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenWithCodeEdit.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 2BE487EE28245162003F3F64 /* FinderSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderSync.swift; sourceTree = ""; }; 2BE487F028245162003F3F64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = ""; }; - 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = ""; }; + 30F67AB22BAF76A200AB0168 /* EffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectView.swift; sourceTree = ""; }; + 30F67AB42BAF76A200AB0168 /* RecentProject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProject.swift; sourceTree = ""; }; + 30F67AB62BAF76A200AB0168 /* ServiceContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceContainer.swift; sourceTree = ""; }; + 30F67AB72BAF76A200AB0168 /* ServiceTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTypes.swift; sourceTree = ""; }; + 30F67AB82BAF76A200AB0168 /* ServiceWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = ""; }; + 30F67ABA2BAF76A200AB0168 /* DocumentService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentService.swift; sourceTree = ""; }; + 30F67ABC2BAF76A200AB0168 /* NSPasteboardProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPasteboardProvider.swift; sourceTree = ""; }; + 30F67ABD2BAF76A200AB0168 /* PasteboardService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasteboardService.swift; sourceTree = ""; }; + 30F67ABE2BAF76A200AB0168 /* UIPasteboardProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIPasteboardProvider.swift; sourceTree = ""; }; + 30F67AC12BAF76A200AB0168 /* Bundle+Info.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Info.swift"; sourceTree = ""; }; + 30F67AC32BAF76A200AB0168 /* NSApp+openWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSApp+openWindow.swift"; sourceTree = ""; }; + 30F67AC62BAF76A200AB0168 /* ProjectManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectManager.swift; sourceTree = ""; }; + 30F67AC72BAF76A200AB0168 /* SceneID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneID.swift; sourceTree = ""; }; + 30F67AC92BAF76A200AB0168 /* RecentProjectItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectItem.swift; sourceTree = ""; }; + 30F67ACA2BAF76A200AB0168 /* RecentProjectsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectsListView.swift; sourceTree = ""; }; + 30F67ACB2BAF76A200AB0168 /* WelcomeActionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeActionView.swift; sourceTree = ""; }; + 30F67ACC2BAF76A200AB0168 /* WelcomeScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeScene.swift; sourceTree = ""; }; + 30F67ACD2BAF76A200AB0168 /* WelcomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; + 30F67ACE2BAF76A200AB0168 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = ""; }; 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = ""; }; - 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditSplitViewController.swift; sourceTree = ""; }; 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentsUnitTests.swift; sourceTree = ""; }; 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSHapticFeedbackPerformerMock.swift; sourceTree = ""; }; - 581550CC29FBD30400684881 /* StandardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardTableViewCell.swift; sourceTree = ""; }; - 581550CD29FBD30400684881 /* FileSystemTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileSystemTableViewCell.swift; sourceTree = ""; }; - 581550CE29FBD30400684881 /* TextTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextTableViewCell.swift; sourceTree = ""; }; - 581550D329FBD37D00684881 /* ProjectNavigatorToolbarBottom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorToolbarBottom.swift; sourceTree = ""; }; - 581BFB5A2926431000D251EC /* WelcomeWindowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; - 581BFB5B2926431000D251EC /* WelcomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; - 581BFB5C2926431000D251EC /* WelcomeActionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeActionView.swift; sourceTree = ""; }; - 581BFB5E2926431000D251EC /* RecentProjectItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectItem.swift; sourceTree = ""; }; - 582213EF291834A500EFE361 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 583E527529361B39001AB554 /* CodeEditUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditUITests.swift; sourceTree = ""; }; 583E527929361B39001AB554 /* testHelpButtonDark.1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testHelpButtonDark.1.png; sourceTree = ""; }; 583E527A29361B39001AB554 /* testEffectViewLight.1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testEffectViewLight.1.png; sourceTree = ""; }; @@ -631,389 +162,30 @@ 583E528329361B39001AB554 /* testEffectViewDark.1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testEffectViewDark.1.png; sourceTree = ""; }; 583E528429361B39001AB554 /* testBranchPickerLight.1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = testBranchPickerLight.1.png; sourceTree = ""; }; 583E52A129361BFD001AB554 /* CodeEditUITests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CodeEditUITests-Bridging-Header.h"; sourceTree = ""; }; - 58710158298EB80000951BA4 /* CEWorkspaceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceFileManager.swift; sourceTree = ""; }; - 5878DA81291863F900DD95A3 /* AcknowledgementsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowledgementsView.swift; sourceTree = ""; }; - 5878DA832918642000DD95A3 /* ParsePackagesResolved.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsePackagesResolved.swift; sourceTree = ""; }; - 5878DA862918642F00DD95A3 /* AcknowledgementsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowledgementsViewModel.swift; sourceTree = ""; }; - 5878DAA1291AE76700DD95A3 /* QuickOpenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenView.swift; sourceTree = ""; }; - 5878DAA2291AE76700DD95A3 /* QuickOpenPreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenPreviewView.swift; sourceTree = ""; }; - 5878DAA3291AE76700DD95A3 /* QuickOpenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenViewModel.swift; sourceTree = ""; }; - 5878DAA4291AE76700DD95A3 /* QuickOpenItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenItem.swift; sourceTree = ""; }; - 5878DAAD291D627C00DD95A3 /* EditorPathBarMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPathBarMenu.swift; sourceTree = ""; }; - 5878DAAE291D627C00DD95A3 /* EditorPathBarComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPathBarComponent.swift; sourceTree = ""; }; - 5878DAAF291D627C00DD95A3 /* EditorPathBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPathBarView.swift; sourceTree = ""; }; - 58798213292D92370085B254 /* String+SafeOffset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SafeOffset.swift"; sourceTree = ""; }; - 58798215292D92370085B254 /* SearchModeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchModeModel.swift; sourceTree = ""; }; - 58798216292D92370085B254 /* SearchResultModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultModel.swift; sourceTree = ""; }; - 58798217292D92370085B254 /* SearchResultMatchModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultMatchModel.swift; sourceTree = ""; }; - 5879822B292E30B90085B254 /* FeedbackToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackToolbar.swift; sourceTree = ""; }; - 5879822D292E30B90085B254 /* FeedbackIssueArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackIssueArea.swift; sourceTree = ""; }; - 5879822E292E30B90085B254 /* FeedbackModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackModel.swift; sourceTree = ""; }; - 5879822F292E30B90085B254 /* FeedbackType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackType.swift; sourceTree = ""; }; - 58798230292E30B90085B254 /* FeedbackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = ""; }; - 58798232292E30B90085B254 /* FeedbackWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackWindowController.swift; sourceTree = ""; }; - 58798248292E78D80085B254 /* CodeFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeFileView.swift; sourceTree = ""; }; - 58798249292E78D80085B254 /* CodeFileDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeFileDocument.swift; sourceTree = ""; }; - 5879824B292E78D80085B254 /* OtherFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtherFileView.swift; sourceTree = ""; }; - 5879824D292E78D80085B254 /* ImageFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFileView.swift; sourceTree = ""; }; - 58798280292ED0FB0085B254 /* TerminalEmulatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TerminalEmulatorView.swift; sourceTree = ""; }; - 58798281292ED0FB0085B254 /* TerminalEmulatorView+Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TerminalEmulatorView+Coordinator.swift"; sourceTree = ""; }; - 58798283292ED0FB0085B254 /* SwiftTerm+Color+Init.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftTerm+Color+Init.swift"; sourceTree = ""; }; 587B60F72934124100D5CD8F /* CEWorkspaceFileManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CEWorkspaceFileManagerTests.swift; sourceTree = ""; }; 587B61002934170A00D5CD8F /* UnitTests_Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnitTests_Extensions.swift; sourceTree = ""; }; 587B612D293419B700D5CD8F /* CodeFileTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeFileTests.swift; sourceTree = ""; }; - 587B9D8829300ABD00AC7927 /* SegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControl.swift; sourceTree = ""; }; - 587B9D8929300ABD00AC7927 /* PanelDivider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanelDivider.swift; sourceTree = ""; }; - 587B9D8B29300ABD00AC7927 /* EffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectView.swift; sourceTree = ""; }; - 587B9D8C29300ABD00AC7927 /* SettingsTextEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTextEditor.swift; sourceTree = ""; }; - 587B9D8D29300ABD00AC7927 /* OverlayPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayPanel.swift; sourceTree = ""; }; - 587B9D8E29300ABD00AC7927 /* PressActionsModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PressActionsModifier.swift; sourceTree = ""; }; - 587B9D8F29300ABD00AC7927 /* ToolbarBranchPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarBranchPicker.swift; sourceTree = ""; }; - 587B9D9029300ABD00AC7927 /* HelpButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpButton.swift; sourceTree = ""; }; - 587B9E0729301D8F00AC7927 /* GitCloneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitCloneView.swift; sourceTree = ""; }; - 587B9E0829301D8F00AC7927 /* GitCheckoutBranchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitCheckoutBranchView.swift; sourceTree = ""; }; - 587B9E0A29301D8F00AC7927 /* Parameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parameters.swift; sourceTree = ""; }; - 587B9E0D29301D8F00AC7927 /* GitLabUserRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabUserRouter.swift; sourceTree = ""; }; - 587B9E0E29301D8F00AC7927 /* GitLabCommitRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabCommitRouter.swift; sourceTree = ""; }; - 587B9E0F29301D8F00AC7927 /* GitLabProjectRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabProjectRouter.swift; sourceTree = ""; }; - 587B9E1029301D8F00AC7927 /* GitLabOAuthRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabOAuthRouter.swift; sourceTree = ""; }; - 587B9E1129301D8F00AC7927 /* GitLabOAuthConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabOAuthConfiguration.swift; sourceTree = ""; }; - 587B9E1229301D8F00AC7927 /* GitLabConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabConfiguration.swift; sourceTree = ""; }; - 587B9E1329301D8F00AC7927 /* GitLabAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabAccount.swift; sourceTree = ""; }; - 587B9E1529301D8F00AC7927 /* GitLabCommit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabCommit.swift; sourceTree = ""; }; - 587B9E1629301D8F00AC7927 /* GitLabGroupAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabGroupAccess.swift; sourceTree = ""; }; - 587B9E1729301D8F00AC7927 /* GitLabProjectHook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabProjectHook.swift; sourceTree = ""; }; - 587B9E1829301D8F00AC7927 /* GitLabEventData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabEventData.swift; sourceTree = ""; }; - 587B9E1929301D8F00AC7927 /* GitLabAccountModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabAccountModel.swift; sourceTree = ""; }; - 587B9E1A29301D8F00AC7927 /* GitLabEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabEvent.swift; sourceTree = ""; }; - 587B9E1B29301D8F00AC7927 /* GitLabPermissions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabPermissions.swift; sourceTree = ""; }; - 587B9E1C29301D8F00AC7927 /* GitLabAvatarURL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabAvatarURL.swift; sourceTree = ""; }; - 587B9E1D29301D8F00AC7927 /* GitLabNamespace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabNamespace.swift; sourceTree = ""; }; - 587B9E1E29301D8F00AC7927 /* GitLabEventNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabEventNote.swift; sourceTree = ""; }; - 587B9E1F29301D8F00AC7927 /* GitLabProject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabProject.swift; sourceTree = ""; }; - 587B9E2029301D8F00AC7927 /* GitLabProjectAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabProjectAccess.swift; sourceTree = ""; }; - 587B9E2129301D8F00AC7927 /* GitLabUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitLabUser.swift; sourceTree = ""; }; - 587B9E2329301D8F00AC7927 /* GitURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitURLSession.swift; sourceTree = ""; }; - 587B9E2429301D8F00AC7927 /* GitJSONPostRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitJSONPostRouter.swift; sourceTree = ""; }; - 587B9E2529301D8F00AC7927 /* GitRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitRouter.swift; sourceTree = ""; }; - 587B9E2729301D8F00AC7927 /* URL+URLParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+URLParameters.swift"; sourceTree = ""; }; - 587B9E2829301D8F00AC7927 /* String+QueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+QueryParameters.swift"; sourceTree = ""; }; - 587B9E2929301D8F00AC7927 /* GitTime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitTime.swift; sourceTree = ""; }; - 587B9E2A29301D8F00AC7927 /* String+PercentEncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+PercentEncoding.swift"; sourceTree = ""; }; - 587B9E2E29301D8F00AC7927 /* GitHubIssueRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubIssueRouter.swift; sourceTree = ""; }; - 587B9E2F29301D8F00AC7927 /* GitHubReviewsRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubReviewsRouter.swift; sourceTree = ""; }; - 587B9E3029301D8F00AC7927 /* GitHubRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubRouter.swift; sourceTree = ""; }; - 587B9E3129301D8F00AC7927 /* GitHubRepositoryRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubRepositoryRouter.swift; sourceTree = ""; }; - 587B9E3229301D8F00AC7927 /* GitHubPullRequestRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubPullRequestRouter.swift; sourceTree = ""; }; - 587B9E3329301D8F00AC7927 /* GitHubGistRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubGistRouter.swift; sourceTree = ""; }; - 587B9E3429301D8F00AC7927 /* GitHubUserRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubUserRouter.swift; sourceTree = ""; }; - 587B9E3529301D8F00AC7927 /* GitHubConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubConfiguration.swift; sourceTree = ""; }; - 587B9E3629301D8F00AC7927 /* PublicKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKey.swift; sourceTree = ""; }; - 587B9E3729301D8F00AC7927 /* GitHubPreviewHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubPreviewHeader.swift; sourceTree = ""; }; - 587B9E3929301D8F00AC7927 /* GitHubPullRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubPullRequest.swift; sourceTree = ""; }; - 587B9E3A29301D8F00AC7927 /* GitHubUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubUser.swift; sourceTree = ""; }; - 587B9E3B29301D8F00AC7927 /* GitHubReview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubReview.swift; sourceTree = ""; }; - 587B9E3C29301D8F00AC7927 /* GitHubComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubComment.swift; sourceTree = ""; }; - 587B9E3D29301D8F00AC7927 /* GitHubRepositories.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubRepositories.swift; sourceTree = ""; }; - 587B9E3E29301D8F00AC7927 /* GitHubFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubFiles.swift; sourceTree = ""; }; - 587B9E3F29301D8F00AC7927 /* GitHubGist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubGist.swift; sourceTree = ""; }; - 587B9E4029301D8F00AC7927 /* GitHubIssue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubIssue.swift; sourceTree = ""; }; - 587B9E4129301D8F00AC7927 /* GitHubAccount+deleteReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GitHubAccount+deleteReference.swift"; sourceTree = ""; }; - 587B9E4229301D8F00AC7927 /* GitHubOpenness.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubOpenness.swift; sourceTree = ""; }; - 587B9E4329301D8F00AC7927 /* GitHubAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubAccount.swift; sourceTree = ""; }; - 587B9E4629301D8F00AC7927 /* BitBucketRepositoryRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketRepositoryRouter.swift; sourceTree = ""; }; - 587B9E4729301D8F00AC7927 /* BitBucketUserRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketUserRouter.swift; sourceTree = ""; }; - 587B9E4829301D8F00AC7927 /* BitBucketTokenRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketTokenRouter.swift; sourceTree = ""; }; - 587B9E4929301D8F00AC7927 /* BitBucketOAuthRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketOAuthRouter.swift; sourceTree = ""; }; - 587B9E4A29301D8F00AC7927 /* BitBucketAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketAccount.swift; sourceTree = ""; }; - 587B9E4B29301D8F00AC7927 /* BitBucketOAuthConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketOAuthConfiguration.swift; sourceTree = ""; }; - 587B9E4C29301D8F00AC7927 /* BitBucketTokenConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketTokenConfiguration.swift; sourceTree = ""; }; - 587B9E4E29301D8F00AC7927 /* BitBucketUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketUser.swift; sourceTree = ""; }; - 587B9E4F29301D8F00AC7927 /* BitBucketRepositories.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketRepositories.swift; sourceTree = ""; }; - 587B9E5029301D8F00AC7927 /* BitBucketAccount+Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BitBucketAccount+Token.swift"; sourceTree = ""; }; - 587B9E5329301D8F00AC7927 /* GitCommit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitCommit.swift; sourceTree = ""; }; - 587B9E5429301D8F00AC7927 /* GitChangedFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitChangedFile.swift; sourceTree = ""; }; - 587B9E5529301D8F00AC7927 /* GitType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitType.swift; sourceTree = ""; }; - 587FB98F29C1246400B519DD /* EditorTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorTabView.swift; sourceTree = ""; }; - 58822509292C280D00E83CDE /* StatusBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarView.swift; sourceTree = ""; }; - 5882250B292C280D00E83CDE /* StatusBarMenuStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarMenuStyle.swift; sourceTree = ""; }; - 5882250C292C280D00E83CDE /* StatusBarBreakpointButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarBreakpointButton.swift; sourceTree = ""; }; - 5882250D292C280D00E83CDE /* StatusBarIndentSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarIndentSelector.swift; sourceTree = ""; }; - 5882250E292C280D00E83CDE /* StatusBarEncodingSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarEncodingSelector.swift; sourceTree = ""; }; - 5882250F292C280D00E83CDE /* StatusBarLineEndSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarLineEndSelector.swift; sourceTree = ""; }; - 58822510292C280D00E83CDE /* StatusBarToggleUtilityAreaButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarToggleUtilityAreaButton.swift; sourceTree = ""; }; - 58822511292C280D00E83CDE /* StatusBarCursorLocationLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarCursorLocationLabel.swift; sourceTree = ""; }; - 58822513292C280D00E83CDE /* UtilityAreaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityAreaView.swift; sourceTree = ""; }; - 58822515292C280D00E83CDE /* StatusBarSplitTerminalButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarSplitTerminalButton.swift; sourceTree = ""; }; - 58822516292C280D00E83CDE /* StatusBarMaximizeButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarMaximizeButton.swift; sourceTree = ""; }; - 58822517292C280D00E83CDE /* StatusBarClearButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarClearButton.swift; sourceTree = ""; }; - 58822518292C280D00E83CDE /* FilterTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterTextField.swift; sourceTree = ""; }; - 5882251A292C280D00E83CDE /* View+isHovering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+isHovering.swift"; sourceTree = ""; }; - 5882251C292C280D00E83CDE /* UtilityAreaViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityAreaViewModel.swift; sourceTree = ""; }; - 5882251E292C280D00E83CDE /* CursorLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorLocation.swift; sourceTree = ""; }; - 588847622992A2A200996D95 /* CEWorkspaceFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceFile.swift; sourceTree = ""; }; - 588847682992ABCA00996D95 /* Array+SortURLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SortURLs.swift"; sourceTree = ""; }; - 5894E59629FEF7740077E59C /* CEWorkspaceFile+Recursion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CEWorkspaceFile+Recursion.swift"; sourceTree = ""; }; 589F3E342936185400E1A4DA /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; - 58A2E40629C3975D005CB615 /* CEWorkspaceFileIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CEWorkspaceFileIcon.swift; sourceTree = ""; }; - 58A5DF7C2931787A00D1BD5D /* ShellClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellClient.swift; sourceTree = ""; }; - 58A5DF7F29325B5A00D1BD5D /* GitClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitClient.swift; sourceTree = ""; }; - 58A5DF9E29339F6400D1BD5D /* KeybindingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeybindingManager.swift; sourceTree = ""; }; - 58A5DF9F29339F6400D1BD5D /* CommandManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandManager.swift; sourceTree = ""; }; - 58A5DFA129339F6400D1BD5D /* default_keybindings.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = default_keybindings.json; sourceTree = ""; }; - 58AFAA2C2933C69E00482B53 /* EditorTabRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorTabRepresentable.swift; sourceTree = ""; }; - 58AFAA2D2933C69E00482B53 /* EditorItemID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorItemID.swift; sourceTree = ""; }; - 58D01C88293167DC00C5B6B4 /* Color+HEX.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Color+HEX.swift"; sourceTree = ""; }; - 58D01C89293167DC00C5B6B4 /* Bundle+Info.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Info.swift"; sourceTree = ""; }; - 58D01C8A293167DC00C5B6B4 /* Date+Formatted.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Formatted.swift"; sourceTree = ""; }; - 58D01C8C293167DC00C5B6B4 /* String+SHA256.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SHA256.swift"; sourceTree = ""; }; - 58D01C8D293167DC00C5B6B4 /* String+RemoveOccurrences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+RemoveOccurrences.swift"; sourceTree = ""; }; - 58D01C8E293167DC00C5B6B4 /* String+MD5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+MD5.swift"; sourceTree = ""; }; - 58D01C90293167DC00C5B6B4 /* CodeEditKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditKeychain.swift; sourceTree = ""; }; - 58D01C91293167DC00C5B6B4 /* CodeEditKeychainConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditKeychainConstants.swift; sourceTree = ""; }; - 58D01C93293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainSwiftAccessOptions.swift; sourceTree = ""; }; - 58F2EAAA292FB2B0004A9BDE /* IgnoredFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IgnoredFiles.swift; sourceTree = ""; }; - 58F2EAAF292FB2B0004A9BDE /* ThemeSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeSettingsView.swift; sourceTree = ""; }; 58F2EACE292FB2B0004A9BDE /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = ""; }; - 58F2EAD1292FB2B0004A9BDE /* SourceControlSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceControlSettings.swift; sourceTree = ""; }; - 58F2EAD2292FB2B0004A9BDE /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; - 58F2EAD4292FB2B0004A9BDE /* KeybindingsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeybindingsSettings.swift; sourceTree = ""; }; - 58F2EAD6292FB2B0004A9BDE /* GeneralSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralSettings.swift; sourceTree = ""; }; - 58F2EAD8292FB2B0004A9BDE /* TextEditingSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEditingSettings.swift; sourceTree = ""; }; - 58F2EADA292FB2B0004A9BDE /* TerminalSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TerminalSettings.swift; sourceTree = ""; }; - 58F2EADB292FB2B0004A9BDE /* SettingsData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsData.swift; sourceTree = ""; }; - 58F2EADD292FB2B0004A9BDE /* AccountsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsSettings.swift; sourceTree = ""; }; - 58F2EAE0292FB2B0004A9BDE /* ThemeSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeSettings.swift; sourceTree = ""; }; - 58F2EAE1292FB2B0004A9BDE /* SoftwareUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoftwareUpdater.swift; sourceTree = ""; }; - 58FD7605291EA1CB0051D6E4 /* CommandPaletteViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandPaletteViewModel.swift; sourceTree = ""; }; - 58FD7607291EA1CB0051D6E4 /* CommandPaletteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandPaletteView.swift; sourceTree = ""; }; - 5B241BF22B6DDBFF0016E616 /* IgnorePatternListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnorePatternListItemView.swift; sourceTree = ""; }; - 5B698A092B262FA000DE9392 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = ""; }; - 5B698A0C2B26327800DE9392 /* SearchSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettings.swift; sourceTree = ""; }; - 5B698A0E2B2636A700DE9392 /* SearchSettingsIgnoreGlobPatternItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsIgnoreGlobPatternItemView.swift; sourceTree = ""; }; - 5B698A152B263BCE00DE9392 /* SearchSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsModel.swift; sourceTree = ""; }; - 5C4BB1E028212B1E00A92FB2 /* World.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = World.swift; sourceTree = ""; }; - 610C0FD92B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+FindAndReplace.swift"; sourceTree = ""; }; - 611191F92B08CC9000D4459B /* SearchIndexer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchIndexer.swift; sourceTree = ""; }; - 611191FB2B08CCB800D4459B /* SearchIndexer+AsyncController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+AsyncController.swift"; sourceTree = ""; }; - 611191FD2B08CCD200D4459B /* SearchIndexer+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+File.swift"; sourceTree = ""; }; - 611191FF2B08CCD700D4459B /* SearchIndexer+Memory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+Memory.swift"; sourceTree = ""; }; - 611192012B08CCDC00D4459B /* SearchIndexer+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+Search.swift"; sourceTree = ""; }; - 611192032B08CCED00D4459B /* SearchIndexer+ProgressiveSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+ProgressiveSearch.swift"; sourceTree = ""; }; - 611192052B08CCF600D4459B /* SearchIndexer+Add.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+Add.swift"; sourceTree = ""; }; - 611192072B08CCFD00D4459B /* SearchIndexer+Terms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+Terms.swift"; sourceTree = ""; }; - 6111920B2B08CD0B00D4459B /* SearchIndexer+InternalMethods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchIndexer+InternalMethods.swift"; sourceTree = ""; }; 6130535B2B23933D00D767E3 /* MemoryIndexingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryIndexingTests.swift; sourceTree = ""; }; 6130535E2B23A31300D767E3 /* MemorySearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemorySearchTests.swift; sourceTree = ""; }; 613053642B23A49300D767E3 /* TemporaryFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryFile.swift; sourceTree = ""; }; 6130536A2B24722C00D767E3 /* AsyncIndexingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncIndexingTests.swift; sourceTree = ""; }; - 613899B02B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+FuzzySearch.swift"; sourceTree = ""; }; - 613899B22B6E6FEE00A5CAF6 /* FuzzySearchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzySearchable.swift; sourceTree = ""; }; - 613899B42B6E700300A5CAF6 /* FuzzySearchModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzySearchModels.swift; sourceTree = ""; }; - 613899B62B6E702F00A5CAF6 /* String+LengthOfMatchingPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+LengthOfMatchingPrefix.swift"; sourceTree = ""; }; - 613899B82B6E704500A5CAF6 /* String+Normalise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Normalise.swift"; sourceTree = ""; }; - 613899BB2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+FuzzySearchable.swift"; sourceTree = ""; }; 613899BF2B6E70FE00A5CAF6 /* FuzzySearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzySearchTests.swift; sourceTree = ""; }; - 613DF55D2B08DD5D00E9D902 /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; }; - 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AppearancesOfSubstring.swift"; sourceTree = ""; }; - 61538B922B11201900A88846 /* String+Character.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Character.swift"; sourceTree = ""; }; - 615AA2192B0CFD480013FCCC /* LazyStringLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyStringLoader.swift; sourceTree = ""; }; 6195E30C2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+SearchState+FindTests.swift"; sourceTree = ""; }; 6195E30E2B640474007261CA /* WorkspaceDocument+SearchState+FindAndReplaceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+SearchState+FindAndReplaceTests.swift"; sourceTree = ""; }; 6195E3102B640485007261CA /* WorkspaceDocument+SearchState+IndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+SearchState+IndexTests.swift"; sourceTree = ""; }; - 61A53A7D2B4449870093BF8A /* WorkspaceDocument+Find.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Find.swift"; sourceTree = ""; }; - 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Index.swift"; sourceTree = ""; }; - 6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryEventStream.swift; sourceTree = ""; }; - 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Listeners.swift"; sourceTree = ""; }; - 6C092ED92A53A58600489202 /* EditorLayout+StateRestoration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EditorLayout+StateRestoration.swift"; sourceTree = ""; }; - 6C092EDF2A53BFCF00489202 /* WorkspaceStateKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceStateKey.swift; sourceTree = ""; }; - 6C0D0C6729E861B000AE4D3F /* SettingsSidebarFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSidebarFix.swift; sourceTree = ""; }; - 6C147C3D29A3281D0089B630 /* Editor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = ""; }; - 6C147C3E29A3281D0089B630 /* EditorLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorLayout.swift; sourceTree = ""; }; - 6C147C3F29A328560089B630 /* SplitViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewData.swift; sourceTree = ""; }; - 6C147C4829A32A080089B630 /* EditorLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorLayoutView.swift; sourceTree = ""; }; - 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+SplitEditor.swift"; sourceTree = ""; }; - 6C147C4C29A32AA30089B630 /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = ""; }; - 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorListViewController.swift; sourceTree = ""; }; - 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorMatchListCell.swift; sourceTree = ""; }; - 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectsListView.swift; sourceTree = ""; }; - 6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncFileIterator.swift; sourceTree = ""; }; - 6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorIndexBar.swift; sourceTree = ""; }; - 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewItem.swift; sourceTree = ""; }; - 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variadic.swift; sourceTree = ""; }; - 6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewReader.swift; sourceTree = ""; }; - 6C2C156029B4F52F00EA60A5 /* SplitViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewModifiers.swift; sourceTree = ""; }; - 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurButtonStyle.swift; sourceTree = ""; }; - 6C4104E5297C884F00F472BA /* AboutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDetailView.swift; sourceTree = ""; }; - 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDefaultView.swift; sourceTree = ""; }; - 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+IsFullscreen.swift"; sourceTree = ""; }; - 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = ""; }; - 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; - 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = ""; }; - 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; - 6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionActivatorView.swift; sourceTree = ""; }; - 6C578D8329CD343800DC73B2 /* ExtensionDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDetailView.swift; sourceTree = ""; }; - 6C578D8629CD345900DC73B2 /* ExtensionSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionSceneView.swift; sourceTree = ""; }; - 6C578D8829CD36E400DC73B2 /* Commands+ForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Commands+ForEach.swift"; sourceTree = ""; }; - 6C578D8B29CD372700DC73B2 /* ExtensionCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionCommands.swift; sourceTree = ""; }; - 6C5B63DD29C76213005454BA /* WindowCodeFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowCodeFileView.swift; sourceTree = ""; }; - 6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = ""; }; - 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; - 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionManagerWindow.swift; sourceTree = ""; }; - 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDiscovery.swift; sourceTree = ""; }; - 6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInfo.swift; sourceTree = ""; }; - 6C6BD6FB29CD152400235D17 /* codeedit.extension.appextensionpoint */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = codeedit.extension.appextensionpoint; sourceTree = ""; }; - 6C6BD70029CD172700235D17 /* ExtensionsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsListView.swift; sourceTree = ""; }; - 6C6BD70329CD17B600235D17 /* ExtensionsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsManager.swift; sourceTree = ""; }; - 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewControllerView.swift; sourceTree = ""; }; - 6C7F37FD2A3EA6FA00217B83 /* View+focusedValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+focusedValue.swift"; sourceTree = ""; }; - 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierKeysObserver.swift; sourceTree = ""; }; - 6C82D6B229BFD88700495C54 /* NavigateCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigateCommands.swift; sourceTree = ""; }; - 6C82D6B829BFE34900495C54 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = ""; }; - 6C82D6BB29C00CD900495C54 /* FirstResponderPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstResponderPropertyWrapper.swift; sourceTree = ""; }; - 6C82D6C529C012AD00495C54 /* NSApp+openWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApp+openWindow.swift"; sourceTree = ""; }; - 6C91D57129B176FF0059A90D /* EditorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorManager.swift; sourceTree = ""; }; - 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; - 6CA1AE942B46950000378EAB /* EditorInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorInstance.swift; sourceTree = ""; }; - 6CABB19C29C5591D00340467 /* NSTableViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSTableViewWrapper.swift; path = CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift; sourceTree = SOURCE_ROOT; }; - 6CABB1A029C5593800340467 /* OverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = ""; }; - 6CB52DC82AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CEWorkspaceFileManager+FileManagement.swift"; sourceTree = ""; }; - 6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlImproved.swift; sourceTree = ""; }; - 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Caption3.swift"; sourceTree = ""; }; - 6CBE1CFA2B71DAA6003AC32E /* Loopable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Loopable.swift; sourceTree = ""; }; - 6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ActiveEditor.swift"; sourceTree = ""; }; - 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInjector.swift; sourceTree = ""; }; - 6CDA84AC284C1BA000C1CC3A /* EditorTabBarContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarContextMenu.swift; sourceTree = ""; }; - 6CE622682A2A174A0013085C /* InspectorTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorTab.swift; sourceTree = ""; }; - 6CE6226A2A2A1C730013085C /* UtilityAreaTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTab.swift; sourceTree = ""; }; - 6CE6226D2A2A1CDE0013085C /* NavigatorTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorTab.swift; sourceTree = ""; }; - 6CED16E32A3E660D000EC962 /* String+Lines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Lines.swift"; sourceTree = ""; }; - 6CFF967329BEBCC300182D6F /* FindCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindCommands.swift; sourceTree = ""; }; - 6CFF967529BEBCD900182D6F /* FileCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCommands.swift; sourceTree = ""; }; - 6CFF967729BEBCF600182D6F /* MainCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCommands.swift; sourceTree = ""; }; - 6CFF967929BEBD2400182D6F /* ViewCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCommands.swift; sourceTree = ""; }; - 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCommands.swift; sourceTree = ""; }; - 850C630F29D6B01D00E1444C /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 850C631129D6B03400E1444C /* SettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPage.swift; sourceTree = ""; }; - 852C7E322A587279006BA599 /* SearchableSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchableSettingsPage.swift; sourceTree = ""; }; - 852E62002A5C17E500447138 /* PageAndSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageAndSettings.swift; sourceTree = ""; }; - 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+HighlightOccurrences.swift"; sourceTree = ""; }; - 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchResult.swift; sourceTree = ""; }; - 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+isImage.swift"; sourceTree = ""; }; - 85E412292A46C8CA00183F2B /* LocationsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsSettings.swift; sourceTree = ""; }; 8B9A0E162B9FE84B007E2DBF /* Pre.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Pre.xcconfig; sourceTree = ""; }; - 9D36E1BE2B5E7D7500443C41 /* GitBranchesGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitBranchesGroup.swift; sourceTree = ""; }; - B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPageView.swift; sourceTree = ""; }; - B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindow.swift; sourceTree = ""; }; - B607181C2B0C5BE3009CDAB4 /* GitClient+Stash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Stash.swift"; sourceTree = ""; }; - B607181F2B0C6CE7009CDAB4 /* GitStashEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitStashEntry.swift; sourceTree = ""; }; - B60718302B15A9A3009CDAB4 /* CEOutlineGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEOutlineGroup.swift; sourceTree = ""; }; - B60718362B170638009CDAB4 /* SourceControlNavigatorRenameBranchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorRenameBranchView.swift; sourceTree = ""; }; - B607183E2B17DB07009CDAB4 /* SourceControlNavigatorRepositoryView+contextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceControlNavigatorRepositoryView+contextMenu.swift"; sourceTree = ""; }; - B60718412B17DB93009CDAB4 /* SourceControlNavigatorRepositoryView+outlineGroupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceControlNavigatorRepositoryView+outlineGroupData.swift"; sourceTree = ""; }; - B60718432B17DBE5009CDAB4 /* SourceControlNavigatorRepositoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorRepositoryItem.swift; sourceTree = ""; }; - B60718452B17DC15009CDAB4 /* RepoOutlineGroupItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoOutlineGroupItem.swift; sourceTree = ""; }; - B607184B2B17E037009CDAB4 /* SourceControlStashChangesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlStashChangesView.swift; sourceTree = ""; }; - B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementRowView.swift; sourceTree = ""; }; - B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditWindowControllerExtensions.swift; sourceTree = ""; }; - B61A606029F188AB009B43F9 /* ExternalLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLink.swift; sourceTree = ""; }; - B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonospacedFontPicker.swift; sourceTree = ""; }; - B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = ""; }; - B628B7922B18369800F9775A /* GitClient+Validate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Validate.swift"; sourceTree = ""; }; - B628B7B62B223BAD00F9775A /* FindModePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindModePicker.swift; sourceTree = ""; }; - B62AEDA92A1FCBE5009A9F52 /* AreaTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AreaTabBar.swift; sourceTree = ""; }; - B62AEDB22A1FD95B009A9F52 /* UtilityAreaTerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTerminalView.swift; sourceTree = ""; }; - B62AEDB42A1FE295009A9F52 /* UtilityAreaDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaDebugView.swift; sourceTree = ""; }; - B62AEDB72A1FE2DC009A9F52 /* UtilityAreaOutputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityAreaOutputView.swift; sourceTree = ""; }; - B62AEDBB2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTerminalTab.swift; sourceTree = ""; }; - B62AEDC82A2704F3009A9F52 /* UtilityAreaTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTabView.swift; sourceTree = ""; }; - B62AEDD02A27B264009A9F52 /* View+paneToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+paneToolbar.swift"; sourceTree = ""; }; - B62AEDD32A27B29F009A9F52 /* PaneToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneToolbar.swift; sourceTree = ""; }; - B62AEDD62A27B3D0009A9F52 /* UtilityAreaTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTabViewModel.swift; sourceTree = ""; }; - B62AEDDA2A27C1B3009A9F52 /* OSLogType+Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OSLogType+Color.swift"; sourceTree = ""; }; - B640A99D29E2184700715F20 /* SettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsForm.swift; sourceTree = ""; }; - B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+NavigationBarBackButtonVisible.swift"; sourceTree = ""; }; B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; - B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WorkspaceView.swift; sourceTree = ""; tabWidth = 4; }; B658FB3327DA9E1000EA4DBD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; B658FB3827DA9E1000EA4DBD /* CodeEdit.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CodeEdit.entitlements; sourceTree = ""; }; B658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CodeEditTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B658FB4727DA9E1000EA4DBD /* CodeEditUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CodeEditUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B65B10EB2B073913002852CF /* CEContentUnavailableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEContentUnavailableView.swift; sourceTree = ""; }; - B65B10EE2B07C454002852CF /* GitClient+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Remote.swift"; sourceTree = ""; }; - B65B10F12B07D34F002852CF /* GitRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitRemote.swift; sourceTree = ""; }; - B65B10F42B081A0C002852CF /* SourceControlAddRemoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlAddRemoteView.swift; sourceTree = ""; }; - B65B10F72B081A34002852CF /* SourceControlNavigatorNoRemotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorNoRemotesView.swift; sourceTree = ""; }; - B65B10FA2B08B054002852CF /* Divided.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Divided.swift; sourceTree = ""; }; - B65B10FD2B08B07D002852CF /* SourceControlNavigatorChangesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorChangesList.swift; sourceTree = ""; }; - B65B11002B09D5D4002852CF /* GitClient+Pull.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Pull.swift"; sourceTree = ""; }; - B65B11032B09DB1C002852CF /* GitClient+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Fetch.swift"; sourceTree = ""; }; - B664C3AF2B965F6C00816B4E /* NavigationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettings.swift; sourceTree = ""; }; - B664C3B22B96634F00816B4E /* NavigationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettingsView.swift; sourceTree = ""; }; - B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandsFixes.swift; sourceTree = ""; }; B66A4E4B29C9179B004573B4 /* CodeEditApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditApp.swift; sourceTree = ""; }; - B66A4E4E29C917B8004573B4 /* WelcomeWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeWindow.swift; sourceTree = ""; }; - B66A4E5029C917D5004573B4 /* AboutWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutWindow.swift; sourceTree = ""; }; - B66A4E5229C91831004573B4 /* CodeEditCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditCommands.swift; sourceTree = ""; }; - B66A4E5529C918A0004573B4 /* SceneID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneID.swift; sourceTree = ""; }; - B67DB0EE2AF3E381002DC647 /* PaneTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneTextField.swift; sourceTree = ""; }; - B67DB0F52AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorToolbarBottom.swift; sourceTree = ""; }; - B67DB0F82AFDF638002DC647 /* IconButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconButtonStyle.swift; sourceTree = ""; }; - B67DB0FB2AFDF71F002DC647 /* IconToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconToggleStyle.swift; sourceTree = ""; }; - B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; - B68C7C202A01DEFE004EA6D6 /* GitHubComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubComment.swift; sourceTree = ""; }; - B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsAccountLink.swift; sourceTree = ""; }; - B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Initiate.swift"; sourceTree = ""; }; - B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSSHKeyView.swift; sourceTree = ""; }; - B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabs.swift; sourceTree = ""; }; - B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarLeadingAccessories.swift; sourceTree = ""; }; - B6AB09A42AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarTrailingAccessories.swift; sourceTree = ""; }; - B6AB09B22AB919CF0003A3A6 /* View+actionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+actionBar.swift"; sourceTree = ""; }; - B6C4F2A02B3CA37500B2B140 /* SourceControlNavigatorHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorHistoryView.swift; sourceTree = ""; }; - B6C4F2A22B3CA74800B2B140 /* CommitDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommitDetailsView.swift; sourceTree = ""; }; - B6C4F2A52B3CABD200B2B140 /* HistoryInspectorItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryInspectorItemView.swift; sourceTree = ""; }; - B6C4F2A82B3CB00100B2B140 /* CommitDetailsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommitDetailsHeaderView.swift; sourceTree = ""; }; - B6C4F2AB2B3CC4D000B2B140 /* CommitChangedFileListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommitChangedFileListItemView.swift; sourceTree = ""; }; - B6C6A429297716A500A3D28F /* EditorTabCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabCloseButton.swift; sourceTree = ""; }; - B6C6A42D29771A8D00A3D28F /* EditorTabButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorTabButtonStyle.swift; sourceTree = ""; }; - B6C6A42F29771F7100A3D28F /* EditorTabBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBackground.swift; sourceTree = ""; }; - B6D7EA582971078500301FAC /* InspectorSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorSection.swift; sourceTree = ""; }; - B6D7EA5B297107DD00301FAC /* InspectorField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorField.swift; sourceTree = ""; }; - B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = ""; }; - B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+HideSidebarToggle.swift"; sourceTree = ""; }; - B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSelectionView.swift; sourceTree = ""; }; - B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsProviderRow.swift; sourceTree = ""; }; - B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsSigninView.swift; sourceTree = ""; }; - B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsDetailsView.swift; sourceTree = ""; }; - B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlAccount.swift; sourceTree = ""; }; - B6E55C3A2A95368E003ECC7D /* EditorTabsOverflowShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabsOverflowShadow.swift; sourceTree = ""; }; - B6EA1FE429DA33DB001BF195 /* ThemeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeModel.swift; sourceTree = ""; }; - B6EA1FE629DA341D001BF195 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - B6EA1FF429DA380E001BF195 /* TextEditingSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditingSettingsView.swift; sourceTree = ""; }; - B6EA1FF729DB78DB001BF195 /* ThemeSettingThemeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingThemeRow.swift; sourceTree = ""; }; - B6EA1FFA29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsThemeDetails.swift; sourceTree = ""; }; - B6EA1FFC29DB792C001BF195 /* ThemeSettingsColorPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsColorPreview.swift; sourceTree = ""; }; - B6EA1FFF29DB7966001BF195 /* SettingsColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsColorPicker.swift; sourceTree = ""; }; - B6EA200129DB7F81001BF195 /* View+ConstrainHeightToWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ConstrainHeightToWindow.swift"; sourceTree = ""; }; - B6EE988F27E8879A00CDD8AB /* InspectorAreaView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = InspectorAreaView.swift; sourceTree = ""; tabWidth = 4; }; - B6F0516F29D9E36800D72287 /* LocationsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsSettingsView.swift; sourceTree = ""; }; - B6F0517629D9E3AD00D72287 /* SourceControlGeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlGeneralView.swift; sourceTree = ""; }; - B6F0517829D9E3C900D72287 /* SourceControlGitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlGitView.swift; sourceTree = ""; }; - B6F0517A29D9E46400D72287 /* SourceControlSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlSettingsView.swift; sourceTree = ""; }; - B6F0517C29D9E4B100D72287 /* TerminalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSettingsView.swift; sourceTree = ""; }; B6FF04772B6C08AC002C2C78 /* DefaultThemes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DefaultThemes; sourceTree = ""; }; - D7012EE727E757850001E1EF /* FindNavigatorView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = FindNavigatorView.swift; sourceTree = ""; tabWidth = 4; }; D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Localized+Ex.swift"; sourceTree = ""; }; D7211D4827E06BFE008F2ED7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift"; sourceTree = ""; }; - D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; - D7E201B127E8D50000CB86D0 /* FindNavigatorForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorForm.swift; sourceTree = ""; }; - D7E201B327E9989900CB86D0 /* FindNavigatorResultList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorResultList.swift; sourceTree = ""; }; - DE513F51281B672D002260B9 /* EditorTabBarAccessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarAccessory.swift; sourceTree = ""; }; - DE6405A52817734700881FDF /* EditorTabBarNative.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarNative.swift; sourceTree = ""; }; - DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarDivider.swift; sourceTree = ""; }; - EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+NSMenuDelegate.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1028,20 +200,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6C0F3A3C2A1D0D5000223D19 /* CodeEditKit in Frameworks */, - 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */, - 58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */, - 6C2149412A1BB9AB00748382 /* LogStream in Frameworks */, - 6CBE1D002B720565003AC32E /* CodeEditSourceEditor in Frameworks */, - 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */, - 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */, - 6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */, - 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */, - 6CDEFC9629E22C2700B7C684 /* Introspect in Frameworks */, - 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, - 6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */, 6C2C20172A4016FF0047EDF2 /* (null) in Frameworks */, - 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1049,7 +208,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 583E529C29361BAB001AB554 /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1063,1848 +221,404 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 043C321227E31FE8006AE443 /* Documents */ = { - isa = PBXGroup; - children = ( - 5831E3CE2933F3DE00D5A6D2 /* Controllers */, - 5831E3CF2933F4E000D5A6D2 /* Views */, - 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, - 043BCF02281DA18A000AC47C /* WorkspaceDocument+SearchState.swift */, - 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */, - 61A53A7D2B4449870093BF8A /* WorkspaceDocument+Find.swift */, - 610C0FD92B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift */, - 615AA2192B0CFD480013FCCC /* LazyStringLoader.swift */, - 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */, - 6C092EDF2A53BFCF00489202 /* WorkspaceStateKey.swift */, - 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */, - 61538B922B11201900A88846 /* String+Character.swift */, - 611191F82B08CC8000D4459B /* Indexer */, - ); - path = Documents; - sourceTree = ""; - }; - 04BA7C102AE2AA7300584E1C /* ViewModels */ = { - isa = PBXGroup; - children = ( - 04BA7C112AE2AA7300584E1C /* GitCheckoutBranchViewModel.swift */, - 04BA7C122AE2AA7300584E1C /* GitCloneViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 201169D52837B29600F92B46 /* SourceControlNavigator */ = { - isa = PBXGroup; - children = ( - B66DD19E2B3C0E0C0004FFEC /* History */, - 201169E02837B3D100F92B46 /* Changes */, - 201169E32837B3EF00F92B46 /* Repository */, - 201169DE2837B3C700F92B46 /* Views */, - ); - path = SourceControlNavigator; - sourceTree = ""; - }; - 201169DE2837B3C700F92B46 /* Views */ = { - isa = PBXGroup; - children = ( - 201169D62837B2E300F92B46 /* SourceControlNavigatorView.swift */, - 201169DC2837B3AC00F92B46 /* SourceControlNavigatorToolbarBottom.swift */, - B65B10F42B081A0C002852CF /* SourceControlAddRemoteView.swift */, - B607184B2B17E037009CDAB4 /* SourceControlStashChangesView.swift */, - 041FC6AC2AE437CE00C1F65A /* SourceControlNavigatorNewBranchView.swift */, - B60718362B170638009CDAB4 /* SourceControlNavigatorRenameBranchView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 201169E02837B3D100F92B46 /* Changes */ = { - isa = PBXGroup; - children = ( - B60718492B17DC85009CDAB4 /* Views */, - ); - path = Changes; - sourceTree = ""; - }; - 201169E32837B3EF00F92B46 /* Repository */ = { - isa = PBXGroup; - children = ( - B60718482B17DC64009CDAB4 /* Models */, - B60718472B17DC5B009CDAB4 /* Views */, - ); - path = Repository; - sourceTree = ""; - }; - 20EBB4FF280C325000F3A5DA /* Views */ = { - isa = PBXGroup; - children = ( - B6EE988F27E8879A00CDD8AB /* InspectorAreaView.swift */, - B6D7EA582971078500301FAC /* InspectorSection.swift */, - B6D7EA5B297107DD00301FAC /* InspectorField.swift */, - 20D839AA280DEB2900B27357 /* NoSelectionInspectorView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 20EBB50B280C382800F3A5DA /* Models */ = { - isa = PBXGroup; - children = ( - 6CE622682A2A174A0013085C /* InspectorTab.swift */, - ); - path = Models; - sourceTree = ""; - }; 28052E0129730F2F00F4F90A /* Configs */ = { isa = PBXGroup; children = ( - 28052DFB29730DE300F4F90A /* Debug.xcconfig */, - 28052DFC29730DF600F4F90A /* Alpha.xcconfig */, - 28052DFD29730E0300F4F90A /* Beta.xcconfig */, - 28052DFE29730E0B00F4F90A /* Release.xcconfig */, - 8B9A0E162B9FE84B007E2DBF /* Pre.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 2806E8FE2979587A000040F4 /* Model */ = { - isa = PBXGroup; - children = ( - 2806E9012979588B000040F4 /* Contributor.swift */, - ); - path = Model; - sourceTree = ""; - }; - 283BDCC22972F211002AFF81 /* Acknowledgements */ = { - isa = PBXGroup; - children = ( - 283BDCC42972F236002AFF81 /* AcknowledgementsTests.swift */, - ); - path = Acknowledgements; - sourceTree = ""; - }; - 284DC84B2978B5EB00BF2770 /* Contributors */ = { - isa = PBXGroup; - children = ( - 2806E8FE2979587A000040F4 /* Model */, - 284DC84E2978B7B400BF2770 /* ContributorsView.swift */, - 2806E903297958B9000040F4 /* ContributorRowView.swift */, - ); - path = Contributors; - sourceTree = ""; - }; - 285FEC6C27FE4AC700E57D53 /* OutlineView */ = { - isa = PBXGroup; - children = ( - 2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */, - 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */, - 285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */, - 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */, - D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */, - EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */, - ); - path = OutlineView; - sourceTree = ""; - }; - 286471AC27ED52950039369D /* ProjectNavigator */ = { - isa = PBXGroup; - children = ( - 285FEC6C27FE4AC700E57D53 /* OutlineView */, - 581550D329FBD37D00684881 /* ProjectNavigatorToolbarBottom.swift */, - 286471AA27ED51FD0039369D /* ProjectNavigatorView.swift */, - ); - path = ProjectNavigator; - sourceTree = ""; - }; - 287776EA27E350A100D46668 /* NavigatorArea */ = { - isa = PBXGroup; - children = ( - D7012EE627E757660001E1EF /* FindNavigator */, - 581550CB29FBD30400684881 /* OutlineView */, - 286471AC27ED52950039369D /* ProjectNavigator */, - 201169D52837B29600F92B46 /* SourceControlNavigator */, - B67660682AA972D400CD56B0 /* Models */, - B67660692AA972DC00CD56B0 /* Views */, - 307AC4CB2ABABD9800163376 /* ViewModels */, - ); - path = NavigatorArea; - sourceTree = ""; - }; - 287776EB27E350BA00D46668 /* TabBar */ = { - isa = PBXGroup; - children = ( - B6AB09AB2AAACBF70003A3A6 /* Tabs */, - 58AFAA262933C65000482B53 /* Views */, - ); - path = TabBar; - sourceTree = ""; - }; - 2BE487ED28245162003F3F64 /* OpenWithCodeEdit */ = { - isa = PBXGroup; - children = ( - 2BE487EE28245162003F3F64 /* FinderSync.swift */, - 2BE487F028245162003F3F64 /* Info.plist */, - 2B7AC06A282452FB0082A5B8 /* Media.xcassets */, - 2B15CA0028254139004E8F22 /* OpenWithCodeEdit.entitlements */, - ); - path = OpenWithCodeEdit; - sourceTree = ""; - }; - 3026F50B2AC006A10061227E /* ViewModels */ = { - isa = PBXGroup; - children = ( - 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 307AC4CB2ABABD9800163376 /* ViewModels */ = { - isa = PBXGroup; - children = ( - 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 3E0196712A392170002648D8 /* ShellIntegration */ = { - isa = PBXGroup; - children = ( - 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */, - 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */, - ); - path = ShellIntegration; - sourceTree = ""; - }; - 4EE96EC82960562000FFBEA8 /* Documents */ = { - isa = PBXGroup; - children = ( - 4EE96ECC296059D200FFBEA8 /* Mocks */, - 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */, - 6195E3102B640485007261CA /* WorkspaceDocument+SearchState+IndexTests.swift */, - 6195E30C2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift */, - 6195E30E2B640474007261CA /* WorkspaceDocument+SearchState+FindAndReplaceTests.swift */, - 613053582B23916D00D767E3 /* Indexer */, - ); - path = Documents; - sourceTree = ""; - }; - 4EE96ECC296059D200FFBEA8 /* Mocks */ = { - isa = PBXGroup; - children = ( - 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */, - ); - path = Mocks; - sourceTree = ""; - }; - 581550CB29FBD30400684881 /* OutlineView */ = { - isa = PBXGroup; - children = ( - 581550CC29FBD30400684881 /* StandardTableViewCell.swift */, - 581550CD29FBD30400684881 /* FileSystemTableViewCell.swift */, - 581550CE29FBD30400684881 /* TextTableViewCell.swift */, - ); - path = OutlineView; - sourceTree = ""; - }; - 581BFB4B2926431000D251EC /* Welcome */ = { - isa = PBXGroup; - children = ( - 581BFB562926431000D251EC /* Views */, - ); - path = Welcome; - sourceTree = ""; - }; - 581BFB562926431000D251EC /* Views */ = { - isa = PBXGroup; - children = ( - B66A4E4E29C917B8004573B4 /* WelcomeWindow.swift */, - 581BFB5A2926431000D251EC /* WelcomeWindowView.swift */, - 581BFB5B2926431000D251EC /* WelcomeView.swift */, - 581BFB5C2926431000D251EC /* WelcomeActionView.swift */, - 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */, - 581BFB5E2926431000D251EC /* RecentProjectItem.swift */, - ); - path = Views; - sourceTree = ""; - }; - 582213EE2918345500EFE361 /* About */ = { - isa = PBXGroup; - children = ( - 582213F1291834E500EFE361 /* Views */, - ); - path = About; - sourceTree = ""; - }; - 582213F1291834E500EFE361 /* Views */ = { - isa = PBXGroup; - children = ( - 582213EF291834A500EFE361 /* AboutView.swift */, - 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */, - 6C4104E5297C884F00F472BA /* AboutDetailView.swift */, - 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */, - B66A4E5029C917D5004573B4 /* AboutWindow.swift */, - ); - path = Views; - sourceTree = ""; - }; - 5831E3C52933E6CB00D5A6D2 /* Features */ = { - isa = PBXGroup; - children = ( - 582213EE2918345500EFE361 /* About */, - 5878DA7D291862BC00DD95A3 /* Acknowledgements */, - 588847642992A30900996D95 /* CEWorkspace */, - 587B9D7529300ABD00AC7927 /* CodeEditUI */, - 58798244292E78D80085B254 /* CodeFile */, - 58FD7603291EA1CB0051D6E4 /* Commands */, - 284DC84B2978B5EB00BF2770 /* Contributors */, - 043C321227E31FE8006AE443 /* Documents */, - 6C147C3C29A328020089B630 /* Editor */, - 6C6BD6ED29CD123000235D17 /* Extensions */, - 58798228292E30B90085B254 /* Feedback */, - 587B9E0129301D8F00AC7927 /* Git */, - B6EE988E27E8877C00CDD8AB /* InspectorArea */, - 58A5DF9D29339F6400D1BD5D /* Keybindings */, - 287776EA27E350A100D46668 /* NavigatorArea */, - 5878DAA0291AE76700DD95A3 /* QuickOpen */, - 58798210292D92370085B254 /* Search */, - B67B270029D7868000FB9301 /* Settings */, - 6C147C4729A329E50089B630 /* SplitView */, - 588224FF292C280D00E83CDE /* StatusBar */, - 5879827E292ED0FB0085B254 /* TerminalEmulator */, - 58822512292C280D00E83CDE /* UtilityArea */, - 581BFB4B2926431000D251EC /* Welcome */, - 6CAAF68F29BCC6F900A1F48A /* WindowCommands */, - ); - path = Features; - sourceTree = ""; - }; - 5831E3C62933E7E600D5A6D2 /* Color */ = { - isa = PBXGroup; - children = ( - 58D01C88293167DC00C5B6B4 /* Color+HEX.swift */, - ); - path = Color; - sourceTree = ""; - }; - 5831E3C72933E7F700D5A6D2 /* Bundle */ = { - isa = PBXGroup; - children = ( - 58D01C89293167DC00C5B6B4 /* Bundle+Info.swift */, - ); - path = Bundle; - sourceTree = ""; - }; - 5831E3C82933E80500D5A6D2 /* Date */ = { - isa = PBXGroup; - children = ( - 58D01C8A293167DC00C5B6B4 /* Date+Formatted.swift */, - ); - path = Date; - sourceTree = ""; - }; - 5831E3C92933E83400D5A6D2 /* Protocols */ = { - isa = PBXGroup; - children = ( - 6CBE1CFA2B71DAA6003AC32E /* Loopable.swift */, - 852C7E322A587279006BA599 /* SearchableSettingsPage.swift */, - ); - path = Protocols; - sourceTree = ""; - }; - 5831E3CA2933E86F00D5A6D2 /* View */ = { - isa = PBXGroup; - children = ( - 5882251A292C280D00E83CDE /* View+isHovering.swift */, - 6C7F37FD2A3EA6FA00217B83 /* View+focusedValue.swift */, - ); - path = View; - sourceTree = ""; - }; - 5831E3CB2933E89A00D5A6D2 /* SwiftTerm */ = { - isa = PBXGroup; - children = ( - 5831E3CC2933E8BE00D5A6D2 /* Color */, - ); - path = SwiftTerm; - sourceTree = ""; - }; - 5831E3CC2933E8BE00D5A6D2 /* Color */ = { - isa = PBXGroup; - children = ( - 58798283292ED0FB0085B254 /* SwiftTerm+Color+Init.swift */, - ); - path = Color; - sourceTree = ""; - }; - 5831E3CE2933F3DE00D5A6D2 /* Controllers */ = { - isa = PBXGroup; - children = ( - 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */, - 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */, - B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */, - 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */, - ); - path = Controllers; - sourceTree = ""; - }; - 5831E3CF2933F4E000D5A6D2 /* Views */ = { - isa = PBXGroup; - children = ( - 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 5831E3D02934036D00D5A6D2 /* NSTableView */ = { - isa = PBXGroup; - children = ( - 28B8F883280FFE4600596236 /* NSTableView+Background.swift */, - ); - path = NSTableView; - sourceTree = ""; - }; - 583E527429361B39001AB554 /* CodeEditUI */ = { - isa = PBXGroup; - children = ( - 583E527729361B39001AB554 /* __Snapshots__ */, - 583E527529361B39001AB554 /* CodeEditUITests.swift */, - 583E52A129361BFD001AB554 /* CodeEditUITests-Bridging-Header.h */, - ); - path = CodeEditUI; - sourceTree = ""; - }; - 583E527729361B39001AB554 /* __Snapshots__ */ = { - isa = PBXGroup; - children = ( - 583E527829361B39001AB554 /* UnitTests */, - ); - path = __Snapshots__; - sourceTree = ""; - }; - 583E527829361B39001AB554 /* UnitTests */ = { - isa = PBXGroup; - children = ( - 583E527929361B39001AB554 /* testHelpButtonDark.1.png */, - 583E527A29361B39001AB554 /* testEffectViewLight.1.png */, - 583E527B29361B39001AB554 /* testSegmentedControlLight.1.png */, - 583E527C29361B39001AB554 /* testSegmentedControlProminentLight.1.png */, - 583E527D29361B39001AB554 /* testHelpButtonLight.1.png */, - 583E527E29361B39001AB554 /* testBranchPickerDark.1.png */, - 583E527F29361B39001AB554 /* testFontPickerViewDark.1.png */, - 583E528029361B39001AB554 /* testFontPickerViewLight.1.png */, - 583E528129361B39001AB554 /* testSegmentedControlProminentDark.1.png */, - 583E528229361B39001AB554 /* testSegmentedControlDark.1.png */, - 583E528329361B39001AB554 /* testEffectViewDark.1.png */, - 583E528429361B39001AB554 /* testBranchPickerLight.1.png */, - ); - path = UnitTests; - sourceTree = ""; - }; - 5875680E29316BDC00C965A3 /* ShellClient */ = { - isa = PBXGroup; - children = ( - 58A5DF7B2931784D00D1BD5D /* Models */, - ); - path = ShellClient; - sourceTree = ""; - }; - 5878DA7D291862BC00DD95A3 /* Acknowledgements */ = { - isa = PBXGroup; - children = ( - 5878DA852918642F00DD95A3 /* ViewModels */, - 5878DA7E291862F200DD95A3 /* Views */, - ); - path = Acknowledgements; - sourceTree = ""; - }; - 5878DA7E291862F200DD95A3 /* Views */ = { - isa = PBXGroup; - children = ( - 5878DA81291863F900DD95A3 /* AcknowledgementsView.swift */, - B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */, - 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */, - 5878DA832918642000DD95A3 /* ParsePackagesResolved.swift */, - ); - path = Views; - sourceTree = ""; - }; - 5878DA852918642F00DD95A3 /* ViewModels */ = { - isa = PBXGroup; - children = ( - 5878DA862918642F00DD95A3 /* AcknowledgementsViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 5878DAA0291AE76700DD95A3 /* QuickOpen */ = { - isa = PBXGroup; - children = ( - 5878DAAA291D5CAA00DD95A3 /* ViewModels */, - 5878DAA9291D5CA100DD95A3 /* Views */, - ); - path = QuickOpen; - sourceTree = ""; - }; - 5878DAA9291D5CA100DD95A3 /* Views */ = { - isa = PBXGroup; - children = ( - 6CABB19C29C5591D00340467 /* NSTableViewWrapper.swift */, - 5878DAA1291AE76700DD95A3 /* QuickOpenView.swift */, - 5878DAA2291AE76700DD95A3 /* QuickOpenPreviewView.swift */, - 5878DAA4291AE76700DD95A3 /* QuickOpenItem.swift */, - ); - path = Views; - sourceTree = ""; - }; - 5878DAAA291D5CAA00DD95A3 /* ViewModels */ = { - isa = PBXGroup; - children = ( - 5878DAA3291AE76700DD95A3 /* QuickOpenViewModel.swift */, - 613899BB2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 5878DAAB291D627C00DD95A3 /* PathBar */ = { - isa = PBXGroup; - children = ( - 5878DAAC291D627C00DD95A3 /* Views */, - ); - path = PathBar; - sourceTree = ""; - }; - 5878DAAC291D627C00DD95A3 /* Views */ = { - isa = PBXGroup; - children = ( - 5878DAAD291D627C00DD95A3 /* EditorPathBarMenu.swift */, - 5878DAAE291D627C00DD95A3 /* EditorPathBarComponent.swift */, - 5878DAAF291D627C00DD95A3 /* EditorPathBarView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 58798210292D92370085B254 /* Search */ = { - isa = PBXGroup; - children = ( - 613899AF2B6E6FB800A5CAF6 /* FuzzySearch */, - 58798212292D92370085B254 /* Extensions */, - 58798214292D92370085B254 /* Model */, - ); - path = Search; - sourceTree = ""; - }; - 58798212292D92370085B254 /* Extensions */ = { - isa = PBXGroup; - children = ( - 58798213292D92370085B254 /* String+SafeOffset.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - 58798214292D92370085B254 /* Model */ = { - isa = PBXGroup; - children = ( - 58798215292D92370085B254 /* SearchModeModel.swift */, - 58798216292D92370085B254 /* SearchResultModel.swift */, - 58798217292D92370085B254 /* SearchResultMatchModel.swift */, - ); - path = Model; - sourceTree = ""; - }; - 58798228292E30B90085B254 /* Feedback */ = { - isa = PBXGroup; - children = ( - 58798231292E30B90085B254 /* Controllers */, - 5879822A292E30B90085B254 /* HelperView */, - 5879822C292E30B90085B254 /* Model */, - 58798230292E30B90085B254 /* FeedbackView.swift */, - ); - path = Feedback; - sourceTree = ""; - }; - 5879822A292E30B90085B254 /* HelperView */ = { - isa = PBXGroup; - children = ( - 5879822B292E30B90085B254 /* FeedbackToolbar.swift */, - ); - path = HelperView; - sourceTree = ""; - }; - 5879822C292E30B90085B254 /* Model */ = { - isa = PBXGroup; - children = ( - 5879822D292E30B90085B254 /* FeedbackIssueArea.swift */, - 5879822E292E30B90085B254 /* FeedbackModel.swift */, - 5879822F292E30B90085B254 /* FeedbackType.swift */, - ); - path = Model; - sourceTree = ""; - }; - 58798231292E30B90085B254 /* Controllers */ = { - isa = PBXGroup; - children = ( - 58798232292E30B90085B254 /* FeedbackWindowController.swift */, - ); - path = Controllers; - sourceTree = ""; - }; - 58798244292E78D80085B254 /* CodeFile */ = { - isa = PBXGroup; - children = ( - 5879824C292E78D80085B254 /* Image */, - 5879824A292E78D80085B254 /* Other */, - 58798249292E78D80085B254 /* CodeFileDocument.swift */, - 58798248292E78D80085B254 /* CodeFileView.swift */, - 6C5B63DD29C76213005454BA /* WindowCodeFileView.swift */, - ); - path = CodeFile; - sourceTree = ""; - }; - 5879824A292E78D80085B254 /* Other */ = { - isa = PBXGroup; - children = ( - 5879824B292E78D80085B254 /* OtherFileView.swift */, - ); - path = Other; - sourceTree = ""; - }; - 5879824C292E78D80085B254 /* Image */ = { - isa = PBXGroup; - children = ( - 5879824D292E78D80085B254 /* ImageFileView.swift */, - ); - path = Image; - sourceTree = ""; - }; - 5879827E292ED0FB0085B254 /* TerminalEmulator */ = { - isa = PBXGroup; - children = ( - 58798280292ED0FB0085B254 /* TerminalEmulatorView.swift */, - 58798281292ED0FB0085B254 /* TerminalEmulatorView+Coordinator.swift */, - ); - path = TerminalEmulator; - sourceTree = ""; - }; - 587B60F329340A8000D5CD8F /* CodeEditTests */ = { - isa = PBXGroup; - children = ( - 587B60FE293416C900D5CD8F /* Features */, - 587B60F42934122D00D5CD8F /* Utils */, - ); - path = CodeEditTests; - sourceTree = ""; - }; - 587B60F42934122D00D5CD8F /* Utils */ = { - isa = PBXGroup; - children = ( - 587B61002934170A00D5CD8F /* UnitTests_Extensions.swift */, - 587B60F52934124100D5CD8F /* CEWorkspaceFileManager */, - ); - path = Utils; - sourceTree = ""; - }; - 587B60F52934124100D5CD8F /* CEWorkspaceFileManager */ = { - isa = PBXGroup; - children = ( - 587B60F72934124100D5CD8F /* CEWorkspaceFileManagerTests.swift */, - ); - path = CEWorkspaceFileManager; - sourceTree = ""; - }; - 587B60FE293416C900D5CD8F /* Features */ = { - isa = PBXGroup; - children = ( - 613899BD2B6E70E200A5CAF6 /* Search */, - 283BDCC22972F211002AFF81 /* Acknowledgements */, - 4EE96EC82960562000FFBEA8 /* Documents */, - 583E527429361B39001AB554 /* CodeEditUI */, - 587B612C2934199800D5CD8F /* CodeFile */, - ); - path = Features; - sourceTree = ""; - }; - 587B612C2934199800D5CD8F /* CodeFile */ = { - isa = PBXGroup; - children = ( - 587B612D293419B700D5CD8F /* CodeFileTests.swift */, - ); - path = CodeFile; - sourceTree = ""; - }; - 587B9D7529300ABD00AC7927 /* CodeEditUI */ = { - isa = PBXGroup; - children = ( - 587B9D8629300ABD00AC7927 /* Views */, - ); - path = CodeEditUI; - sourceTree = ""; - }; - 587B9D8629300ABD00AC7927 /* Views */ = { - isa = PBXGroup; - children = ( - B62AEDA92A1FCBE5009A9F52 /* AreaTabBar.swift */, - B65B10EB2B073913002852CF /* CEContentUnavailableView.swift */, - B65B10FA2B08B054002852CF /* Divided.swift */, - 587B9D8B29300ABD00AC7927 /* EffectView.swift */, - 587B9D9029300ABD00AC7927 /* HelpButton.swift */, - B67DB0F82AFDF638002DC647 /* IconButtonStyle.swift */, - B67DB0FB2AFDF71F002DC647 /* IconToggleStyle.swift */, - 587B9D8D29300ABD00AC7927 /* OverlayPanel.swift */, - 6CABB1A029C5593800340467 /* OverlayView.swift */, - 587B9D8929300ABD00AC7927 /* PanelDivider.swift */, - B67DB0EE2AF3E381002DC647 /* PaneTextField.swift */, - 587B9D8E29300ABD00AC7927 /* PressActionsModifier.swift */, - 587B9D8829300ABD00AC7927 /* SegmentedControl.swift */, - 6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */, - 587B9D8C29300ABD00AC7927 /* SettingsTextEditor.swift */, - 587B9D8F29300ABD00AC7927 /* ToolbarBranchPicker.swift */, - 2897E1C62979A29200741E32 /* TrackableScrollView.swift */, - B60718302B15A9A3009CDAB4 /* CEOutlineGroup.swift */, - ); - path = Views; - sourceTree = ""; - }; - 587B9E0129301D8F00AC7927 /* Git */ = { - isa = PBXGroup; - children = ( - 587B9E0529301D8F00AC7927 /* Clone */, - 587B9E0929301D8F00AC7927 /* Accounts */, - 587B9E5129301D8F00AC7927 /* Client */, - 201169E62837B5CA00F92B46 /* SourceControlManager.swift */, - ); - path = Git; - sourceTree = ""; - }; - 587B9E0529301D8F00AC7927 /* Clone */ = { - isa = PBXGroup; - children = ( - 04BA7C102AE2AA7300584E1C /* ViewModels */, - 587B9E0729301D8F00AC7927 /* GitCloneView.swift */, - 587B9E0829301D8F00AC7927 /* GitCheckoutBranchView.swift */, - ); - path = Clone; - sourceTree = ""; - }; - 587B9E0929301D8F00AC7927 /* Accounts */ = { - isa = PBXGroup; - children = ( - 587B9E4429301D8F00AC7927 /* Bitbucket */, - 587B9E2C29301D8F00AC7927 /* GitHub */, - 587B9E0B29301D8F00AC7927 /* GitLab */, - 587B9E2229301D8F00AC7927 /* Networking */, - 587B9E2629301D8F00AC7927 /* Utils */, - 587B9E0A29301D8F00AC7927 /* Parameters.swift */, - ); - path = Accounts; - sourceTree = ""; - }; - 587B9E0B29301D8F00AC7927 /* GitLab */ = { - isa = PBXGroup; - children = ( - 587B9E1429301D8F00AC7927 /* Model */, - 587B9E0C29301D8F00AC7927 /* Routers */, - 587B9E1129301D8F00AC7927 /* GitLabOAuthConfiguration.swift */, - 587B9E1229301D8F00AC7927 /* GitLabConfiguration.swift */, - 587B9E1329301D8F00AC7927 /* GitLabAccount.swift */, - ); - path = GitLab; - sourceTree = ""; - }; - 587B9E0C29301D8F00AC7927 /* Routers */ = { - isa = PBXGroup; - children = ( - 587B9E0D29301D8F00AC7927 /* GitLabUserRouter.swift */, - 587B9E0E29301D8F00AC7927 /* GitLabCommitRouter.swift */, - 587B9E0F29301D8F00AC7927 /* GitLabProjectRouter.swift */, - 587B9E1029301D8F00AC7927 /* GitLabOAuthRouter.swift */, - ); - path = Routers; - sourceTree = ""; - }; - 587B9E1429301D8F00AC7927 /* Model */ = { - isa = PBXGroup; - children = ( - 587B9E1529301D8F00AC7927 /* GitLabCommit.swift */, - 587B9E1629301D8F00AC7927 /* GitLabGroupAccess.swift */, - 587B9E1729301D8F00AC7927 /* GitLabProjectHook.swift */, - 587B9E1829301D8F00AC7927 /* GitLabEventData.swift */, - 587B9E1929301D8F00AC7927 /* GitLabAccountModel.swift */, - 587B9E1A29301D8F00AC7927 /* GitLabEvent.swift */, - 587B9E1B29301D8F00AC7927 /* GitLabPermissions.swift */, - 587B9E1C29301D8F00AC7927 /* GitLabAvatarURL.swift */, - 587B9E1D29301D8F00AC7927 /* GitLabNamespace.swift */, - 587B9E1E29301D8F00AC7927 /* GitLabEventNote.swift */, - 587B9E1F29301D8F00AC7927 /* GitLabProject.swift */, - 587B9E2029301D8F00AC7927 /* GitLabProjectAccess.swift */, - 587B9E2129301D8F00AC7927 /* GitLabUser.swift */, - ); - path = Model; - sourceTree = ""; - }; - 587B9E2229301D8F00AC7927 /* Networking */ = { - isa = PBXGroup; - children = ( - 587B9E2329301D8F00AC7927 /* GitURLSession.swift */, - 587B9E2429301D8F00AC7927 /* GitJSONPostRouter.swift */, - 587B9E2529301D8F00AC7927 /* GitRouter.swift */, - ); - path = Networking; - sourceTree = ""; - }; - 587B9E2629301D8F00AC7927 /* Utils */ = { - isa = PBXGroup; - children = ( - 587B9E2729301D8F00AC7927 /* URL+URLParameters.swift */, - 587B9E2829301D8F00AC7927 /* String+QueryParameters.swift */, - 587B9E2929301D8F00AC7927 /* GitTime.swift */, - 587B9E2A29301D8F00AC7927 /* String+PercentEncoding.swift */, - ); - path = Utils; - sourceTree = ""; - }; - 587B9E2C29301D8F00AC7927 /* GitHub */ = { - isa = PBXGroup; - children = ( - 587B9E3829301D8F00AC7927 /* Model */, - 587B9E2D29301D8F00AC7927 /* Routers */, - 587B9E3529301D8F00AC7927 /* GitHubConfiguration.swift */, - 587B9E3629301D8F00AC7927 /* PublicKey.swift */, - 587B9E3729301D8F00AC7927 /* GitHubPreviewHeader.swift */, - 587B9E4229301D8F00AC7927 /* GitHubOpenness.swift */, - 587B9E4329301D8F00AC7927 /* GitHubAccount.swift */, - ); - path = GitHub; - sourceTree = ""; - }; - 587B9E2D29301D8F00AC7927 /* Routers */ = { - isa = PBXGroup; - children = ( - 587B9E2E29301D8F00AC7927 /* GitHubIssueRouter.swift */, - 587B9E2F29301D8F00AC7927 /* GitHubReviewsRouter.swift */, - 587B9E3029301D8F00AC7927 /* GitHubRouter.swift */, - 587B9E3129301D8F00AC7927 /* GitHubRepositoryRouter.swift */, - 587B9E3229301D8F00AC7927 /* GitHubPullRequestRouter.swift */, - 587B9E3329301D8F00AC7927 /* GitHubGistRouter.swift */, - 587B9E3429301D8F00AC7927 /* GitHubUserRouter.swift */, - ); - path = Routers; - sourceTree = ""; - }; - 587B9E3829301D8F00AC7927 /* Model */ = { - isa = PBXGroup; - children = ( - 587B9E3929301D8F00AC7927 /* GitHubPullRequest.swift */, - 587B9E3A29301D8F00AC7927 /* GitHubUser.swift */, - 587B9E3B29301D8F00AC7927 /* GitHubReview.swift */, - 587B9E3C29301D8F00AC7927 /* GitHubComment.swift */, - 587B9E3D29301D8F00AC7927 /* GitHubRepositories.swift */, - 587B9E3E29301D8F00AC7927 /* GitHubFiles.swift */, - 587B9E3F29301D8F00AC7927 /* GitHubGist.swift */, - 587B9E4029301D8F00AC7927 /* GitHubIssue.swift */, - 587B9E4129301D8F00AC7927 /* GitHubAccount+deleteReference.swift */, - B68C7C202A01DEFE004EA6D6 /* GitHubComment.swift */, - ); - path = Model; - sourceTree = ""; - }; - 587B9E4429301D8F00AC7927 /* Bitbucket */ = { - isa = PBXGroup; - children = ( - 587B9E4D29301D8F00AC7927 /* Model */, - 587B9E4529301D8F00AC7927 /* Routers */, - 587B9E4A29301D8F00AC7927 /* BitBucketAccount.swift */, - 587B9E4B29301D8F00AC7927 /* BitBucketOAuthConfiguration.swift */, - 587B9E4C29301D8F00AC7927 /* BitBucketTokenConfiguration.swift */, - 587B9E5029301D8F00AC7927 /* BitBucketAccount+Token.swift */, - ); - path = Bitbucket; - sourceTree = ""; - }; - 587B9E4529301D8F00AC7927 /* Routers */ = { - isa = PBXGroup; - children = ( - 587B9E4629301D8F00AC7927 /* BitBucketRepositoryRouter.swift */, - 587B9E4729301D8F00AC7927 /* BitBucketUserRouter.swift */, - 587B9E4829301D8F00AC7927 /* BitBucketTokenRouter.swift */, - 587B9E4929301D8F00AC7927 /* BitBucketOAuthRouter.swift */, - ); - path = Routers; - sourceTree = ""; - }; - 587B9E4D29301D8F00AC7927 /* Model */ = { - isa = PBXGroup; - children = ( - 587B9E4E29301D8F00AC7927 /* BitBucketUser.swift */, - 587B9E4F29301D8F00AC7927 /* BitBucketRepositories.swift */, - ); - path = Model; - sourceTree = ""; - }; - 587B9E5129301D8F00AC7927 /* Client */ = { - isa = PBXGroup; - children = ( - 587B9E5229301D8F00AC7927 /* Models */, - 58A5DF7F29325B5A00D1BD5D /* GitClient.swift */, - 04BA7C182AE2D7C600584E1C /* GitClient+Branches.swift */, - 04BA7C1B2AE2D84100584E1C /* GitClient+Commit.swift */, - 04BA7C1D2AE2D8A000584E1C /* GitClient+Clone.swift */, - B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */, - 04BA7C1F2AE2D92B00584E1C /* GitClient+Status.swift */, - 04BA7C212AE2D95E00584E1C /* GitClient+CommitHistory.swift */, - B65B11002B09D5D4002852CF /* GitClient+Pull.swift */, - 04BA7C262AE2E9F100584E1C /* GitClient+Push.swift */, - B65B10EE2B07C454002852CF /* GitClient+Remote.swift */, - B65B11032B09DB1C002852CF /* GitClient+Fetch.swift */, - B607181C2B0C5BE3009CDAB4 /* GitClient+Stash.swift */, - B628B7922B18369800F9775A /* GitClient+Validate.swift */, - ); - path = Client; - sourceTree = ""; - }; - 587B9E5229301D8F00AC7927 /* Models */ = { - isa = PBXGroup; - children = ( - 587B9E5329301D8F00AC7927 /* GitCommit.swift */, - 587B9E5429301D8F00AC7927 /* GitChangedFile.swift */, - 587B9E5529301D8F00AC7927 /* GitType.swift */, - 04BA7C0A2AE2A2D100584E1C /* GitBranch.swift */, - B65B10F12B07D34F002852CF /* GitRemote.swift */, - B607181F2B0C6CE7009CDAB4 /* GitStashEntry.swift */, - 9D36E1BE2B5E7D7500443C41 /* GitBranchesGroup.swift */, - ); - path = Models; - sourceTree = ""; - }; - 588224FF292C280D00E83CDE /* StatusBar */ = { - isa = PBXGroup; - children = ( - 5882251B292C280D00E83CDE /* Models */, - 58822508292C280D00E83CDE /* Views */, - ); - path = StatusBar; - sourceTree = ""; - }; - 58822508292C280D00E83CDE /* Views */ = { - isa = PBXGroup; - children = ( - 5882250A292C280D00E83CDE /* StatusBarItems */, - 58822509292C280D00E83CDE /* StatusBarView.swift */, - B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */, - ); - path = Views; - sourceTree = ""; - }; - 5882250A292C280D00E83CDE /* StatusBarItems */ = { - isa = PBXGroup; - children = ( - 5882250B292C280D00E83CDE /* StatusBarMenuStyle.swift */, - 5882250C292C280D00E83CDE /* StatusBarBreakpointButton.swift */, - 5882250D292C280D00E83CDE /* StatusBarIndentSelector.swift */, - 5882250E292C280D00E83CDE /* StatusBarEncodingSelector.swift */, - 5882250F292C280D00E83CDE /* StatusBarLineEndSelector.swift */, - 58822510292C280D00E83CDE /* StatusBarToggleUtilityAreaButton.swift */, - 58822511292C280D00E83CDE /* StatusBarCursorLocationLabel.swift */, - ); - path = StatusBarItems; - sourceTree = ""; - }; - 58822512292C280D00E83CDE /* UtilityArea */ = { - isa = PBXGroup; - children = ( - B676606A2AA973A500CD56B0 /* TerminalUtility */, - B676606B2AA973B200CD56B0 /* DebugUtility */, - B676606C2AA973C000CD56B0 /* OutputUtility */, - 58822514292C280D00E83CDE /* Toolbar */, - B676606D2AA9741900CD56B0 /* Models */, - 58822539292C333600E83CDE /* ViewModels */, - B62AEDB12A1FD7F0009A9F52 /* Views */, - ); - path = UtilityArea; - sourceTree = ""; - }; - 58822514292C280D00E83CDE /* Toolbar */ = { - isa = PBXGroup; - children = ( - 58822515292C280D00E83CDE /* StatusBarSplitTerminalButton.swift */, - 58822516292C280D00E83CDE /* StatusBarMaximizeButton.swift */, - 58822517292C280D00E83CDE /* StatusBarClearButton.swift */, - 58822518292C280D00E83CDE /* FilterTextField.swift */, - ); - path = Toolbar; - sourceTree = ""; - }; - 5882251B292C280D00E83CDE /* Models */ = { - isa = PBXGroup; - children = ( - 5882251E292C280D00E83CDE /* CursorLocation.swift */, - ); - path = Models; - sourceTree = ""; - }; - 58822539292C333600E83CDE /* ViewModels */ = { - isa = PBXGroup; - children = ( - 5882251C292C280D00E83CDE /* UtilityAreaViewModel.swift */, - B62AEDD62A27B3D0009A9F52 /* UtilityAreaTabViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 588847642992A30900996D95 /* CEWorkspace */ = { - isa = PBXGroup; - children = ( - 588847652992A35800996D95 /* Models */, - ); - path = CEWorkspace; - sourceTree = ""; - }; - 588847652992A35800996D95 /* Models */ = { - isa = PBXGroup; - children = ( - 588847622992A2A200996D95 /* CEWorkspaceFile.swift */, - 5894E59629FEF7740077E59C /* CEWorkspaceFile+Recursion.swift */, - 58A2E40629C3975D005CB615 /* CEWorkspaceFileIcon.swift */, - 58710158298EB80000951BA4 /* CEWorkspaceFileManager.swift */, - 6CB52DC82AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift */, - 6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */, - ); - path = Models; - sourceTree = ""; - }; - 588847672992AAB800996D95 /* Array */ = { - isa = PBXGroup; - children = ( - 588847682992ABCA00996D95 /* Array+SortURLs.swift */, - ); - path = Array; - sourceTree = ""; - }; - 58A5DF7B2931784D00D1BD5D /* Models */ = { - isa = PBXGroup; - children = ( - 58A5DF7C2931787A00D1BD5D /* ShellClient.swift */, - ); - path = Models; - sourceTree = ""; - }; - 58A5DF9D29339F6400D1BD5D /* Keybindings */ = { - isa = PBXGroup; - children = ( - 58A5DF9F29339F6400D1BD5D /* CommandManager.swift */, - 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */, - 58A5DFA129339F6400D1BD5D /* default_keybindings.json */, - 58A5DF9E29339F6400D1BD5D /* KeybindingManager.swift */, - ); - path = Keybindings; - sourceTree = ""; - }; - 58AFAA262933C65000482B53 /* Views */ = { - isa = PBXGroup; - children = ( - DE513F51281B672D002260B9 /* EditorTabBarAccessory.swift */, - 6CDA84AC284C1BA000C1CC3A /* EditorTabBarContextMenu.swift */, - DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */, - DE6405A52817734700881FDF /* EditorTabBarNative.swift */, - 287776E827E34BC700D46668 /* EditorTabBarView.swift */, - B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */, - B6AB09A42AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift */, - ); - path = Views; - sourceTree = ""; - }; - 58AFAA272933C65C00482B53 /* Models */ = { - isa = PBXGroup; - children = ( - 58AFAA2D2933C69E00482B53 /* EditorItemID.swift */, - 58AFAA2C2933C69E00482B53 /* EditorTabRepresentable.swift */, - ); - path = Models; - sourceTree = ""; - }; - 58D01C85293167DC00C5B6B4 /* Utils */ = { - isa = PBXGroup; - children = ( - 6C48D8EF2972DAC300D6D205 /* Environment */, - 58D01C87293167DC00C5B6B4 /* Extensions */, - 58D01C8F293167DC00C5B6B4 /* KeyChain */, - 5831E3C92933E83400D5A6D2 /* Protocols */, - 5875680E29316BDC00C965A3 /* ShellClient */, - 6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */, - ); - path = Utils; - sourceTree = ""; - }; - 58D01C87293167DC00C5B6B4 /* Extensions */ = { - isa = PBXGroup; - children = ( - 85CD0C5D2A10CC2500E531FD /* URL */, - 6C82D6C429C0129E00495C54 /* NSApplication */, - 588847672992AAB800996D95 /* Array */, - 6CBD1BC42978DE3E006639D5 /* Text */, - 5831E3D02934036D00D5A6D2 /* NSTableView */, - 5831E3CA2933E86F00D5A6D2 /* View */, - 5831E3C72933E7F700D5A6D2 /* Bundle */, - 5831E3C62933E7E600D5A6D2 /* Color */, - 5831E3C82933E80500D5A6D2 /* Date */, - 58D01C8B293167DC00C5B6B4 /* String */, - 5831E3CB2933E89A00D5A6D2 /* SwiftTerm */, - ); - path = Extensions; - sourceTree = ""; - }; - 58D01C8B293167DC00C5B6B4 /* String */ = { - isa = PBXGroup; - children = ( - 58D01C8E293167DC00C5B6B4 /* String+MD5.swift */, - D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */, - 6CED16E32A3E660D000EC962 /* String+Lines.swift */, - 58D01C8D293167DC00C5B6B4 /* String+RemoveOccurrences.swift */, - 58D01C8C293167DC00C5B6B4 /* String+SHA256.swift */, - 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */, - ); - path = String; - sourceTree = ""; - }; - 58D01C8F293167DC00C5B6B4 /* KeyChain */ = { - isa = PBXGroup; - children = ( - 58D01C90293167DC00C5B6B4 /* CodeEditKeychain.swift */, - 58D01C91293167DC00C5B6B4 /* CodeEditKeychainConstants.swift */, - 58D01C93293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift */, - ); - path = KeyChain; - sourceTree = ""; - }; - 58F2EAA9292FB2B0004A9BDE /* Models */ = { - isa = PBXGroup; - children = ( - 58F2EAD1292FB2B0004A9BDE /* SourceControlSettings.swift */, - 58F2EAAA292FB2B0004A9BDE /* IgnoredFiles.swift */, - ); - path = Models; - sourceTree = ""; - }; - 58F2EAAE292FB2B0004A9BDE /* ThemeSettings */ = { - isa = PBXGroup; - children = ( - B6F0518D29DA29F900D72287 /* Models */, - 58F2EAAF292FB2B0004A9BDE /* ThemeSettingsView.swift */, - B6EA1FF729DB78DB001BF195 /* ThemeSettingThemeRow.swift */, - B6EA1FFA29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift */, - B6EA1FFC29DB792C001BF195 /* ThemeSettingsColorPreview.swift */, - ); - path = ThemeSettings; - sourceTree = ""; - }; - 58FD7603291EA1CB0051D6E4 /* Commands */ = { - isa = PBXGroup; - children = ( - 58FD7604291EA1CB0051D6E4 /* ViewModels */, - 58FD7606291EA1CB0051D6E4 /* Views */, - ); - path = Commands; - sourceTree = ""; - }; - 58FD7604291EA1CB0051D6E4 /* ViewModels */ = { - isa = PBXGroup; - children = ( - 58FD7605291EA1CB0051D6E4 /* CommandPaletteViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 58FD7606291EA1CB0051D6E4 /* Views */ = { - isa = PBXGroup; - children = ( - 58FD7607291EA1CB0051D6E4 /* CommandPaletteView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 5B698A082B262F8400DE9392 /* SearchSettings */ = { - isa = PBXGroup; - children = ( - 5B698A0B2B26326000DE9392 /* Models */, - 5B698A092B262FA000DE9392 /* SearchSettingsView.swift */, - 5B241BF22B6DDBFF0016E616 /* IgnorePatternListItemView.swift */, - 5B698A0E2B2636A700DE9392 /* SearchSettingsIgnoreGlobPatternItemView.swift */, - ); - path = SearchSettings; - sourceTree = ""; - }; - 5B698A0B2B26326000DE9392 /* Models */ = { - isa = PBXGroup; - children = ( - 5B698A0C2B26327800DE9392 /* SearchSettings.swift */, - 5B698A152B263BCE00DE9392 /* SearchSettingsModel.swift */, - ); - path = Models; - sourceTree = ""; - }; - 5C403B8D27E20F8000788241 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 589F3E342936185400E1A4DA /* XCTest.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 611191F82B08CC8000D4459B /* Indexer */ = { - isa = PBXGroup; - children = ( - 6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */, - 611191F92B08CC9000D4459B /* SearchIndexer.swift */, - 611191FB2B08CCB800D4459B /* SearchIndexer+AsyncController.swift */, - 611191FF2B08CCD700D4459B /* SearchIndexer+Memory.swift */, - 611191FD2B08CCD200D4459B /* SearchIndexer+File.swift */, - 611192012B08CCDC00D4459B /* SearchIndexer+Search.swift */, - 611192032B08CCED00D4459B /* SearchIndexer+ProgressiveSearch.swift */, - 611192052B08CCF600D4459B /* SearchIndexer+Add.swift */, - 611192072B08CCFD00D4459B /* SearchIndexer+Terms.swift */, - 6111920B2B08CD0B00D4459B /* SearchIndexer+InternalMethods.swift */, - 613DF55D2B08DD5D00E9D902 /* FileHelper.swift */, - ); - path = Indexer; - sourceTree = ""; - }; - 613053582B23916D00D767E3 /* Indexer */ = { - isa = PBXGroup; - children = ( - 6130535B2B23933D00D767E3 /* MemoryIndexingTests.swift */, - 6130535E2B23A31300D767E3 /* MemorySearchTests.swift */, - 6130536A2B24722C00D767E3 /* AsyncIndexingTests.swift */, - 613053642B23A49300D767E3 /* TemporaryFile.swift */, - ); - path = Indexer; - sourceTree = ""; - }; - 613899AF2B6E6FB800A5CAF6 /* FuzzySearch */ = { - isa = PBXGroup; - children = ( - 613899B02B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift */, - 613899B22B6E6FEE00A5CAF6 /* FuzzySearchable.swift */, - 613899B42B6E700300A5CAF6 /* FuzzySearchModels.swift */, - 613899B62B6E702F00A5CAF6 /* String+LengthOfMatchingPrefix.swift */, - 613899B82B6E704500A5CAF6 /* String+Normalise.swift */, - ); - path = FuzzySearch; - sourceTree = ""; - }; - 613899BD2B6E70E200A5CAF6 /* Search */ = { - isa = PBXGroup; - children = ( - 613899BE2B6E70EA00A5CAF6 /* FuzzySearch */, - ); - path = Search; - sourceTree = ""; - }; - 613899BE2B6E70EA00A5CAF6 /* FuzzySearch */ = { - isa = PBXGroup; - children = ( - 613899BF2B6E70FE00A5CAF6 /* FuzzySearchTests.swift */, - ); - path = FuzzySearch; - sourceTree = ""; - }; - 6C092EDC2A53A63E00489202 /* Views */ = { - isa = PBXGroup; - children = ( - 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */, - 6C2C156029B4F52F00EA60A5 /* SplitViewModifiers.swift */, - 6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */, - 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */, - 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 6C092EDD2A53A64900489202 /* Model */ = { - isa = PBXGroup; - children = ( - 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */, - 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */, - 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */, - 6C147C3F29A328560089B630 /* SplitViewData.swift */, - ); - path = Model; - sourceTree = ""; - }; - 6C147C3C29A328020089B630 /* Editor */ = { - isa = PBXGroup; - children = ( - 5878DAAB291D627C00DD95A3 /* PathBar */, - 287776EB27E350BA00D46668 /* TabBar */, - B67660642AA970ED00CD56B0 /* Models */, - B67660632AA970E300CD56B0 /* Views */, - ); - path = Editor; - sourceTree = ""; - }; - 6C147C4729A329E50089B630 /* SplitView */ = { - isa = PBXGroup; - children = ( - 6C092EDD2A53A64900489202 /* Model */, - 6C092EDC2A53A63E00489202 /* Views */, - ); - path = SplitView; - sourceTree = ""; - }; - 6C14CEB12877A5BE001468FE /* FindNavigatorResultList */ = { - isa = PBXGroup; - children = ( - D7E201B327E9989900CB86D0 /* FindNavigatorResultList.swift */, - 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */, - 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */, - ); - path = FindNavigatorResultList; - sourceTree = ""; - }; - 6C48D8EF2972DAC300D6D205 /* Environment */ = { - isa = PBXGroup; - children = ( - 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */, - 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */, - ); - path = Environment; - sourceTree = ""; - }; - 6C6BD6ED29CD123000235D17 /* Extensions */ = { - isa = PBXGroup; - children = ( - 6C6BD6FB29CD152400235D17 /* codeedit.extension.appextensionpoint */, - 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */, - 6C6BD70029CD172700235D17 /* ExtensionsListView.swift */, - 6C578D8629CD345900DC73B2 /* ExtensionSceneView.swift */, - 6C578D8329CD343800DC73B2 /* ExtensionDetailView.swift */, - 6C6BD70329CD17B600235D17 /* ExtensionsManager.swift */, - 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */, - 6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */, - 6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */, - 6C578D8829CD36E400DC73B2 /* Commands+ForEach.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - 6C82D6BF29C00EE300495C54 /* Utils */ = { - isa = PBXGroup; - children = ( - B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */, - 6C82D6BB29C00CD900495C54 /* FirstResponderPropertyWrapper.swift */, - ); - path = Utils; - sourceTree = ""; - }; - 6C82D6C429C0129E00495C54 /* NSApplication */ = { - isa = PBXGroup; - children = ( - 6C82D6C529C012AD00495C54 /* NSApp+openWindow.swift */, - ); - path = NSApplication; - sourceTree = ""; - }; - 6CAAF68F29BCC6F900A1F48A /* WindowCommands */ = { - isa = PBXGroup; - children = ( - B66A4E5229C91831004573B4 /* CodeEditCommands.swift */, - 6CFF967729BEBCF600182D6F /* MainCommands.swift */, - 6CFF967529BEBCD900182D6F /* FileCommands.swift */, - 6CFF967929BEBD2400182D6F /* ViewCommands.swift */, - 6CFF967329BEBCC300182D6F /* FindCommands.swift */, - 6C82D6B229BFD88700495C54 /* NavigateCommands.swift */, - 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */, - 6C82D6B829BFE34900495C54 /* HelpCommands.swift */, - 6C578D8B29CD372700DC73B2 /* ExtensionCommands.swift */, - 6C82D6BF29C00EE300495C54 /* Utils */, - ); - path = WindowCommands; - sourceTree = ""; - }; - 6CBD1BC42978DE3E006639D5 /* Text */ = { - isa = PBXGroup; - children = ( - 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */, - ); - path = Text; - sourceTree = ""; - }; - 85CD0C5D2A10CC2500E531FD /* URL */ = { - isa = PBXGroup; - children = ( - 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */, - ); - path = URL; - sourceTree = ""; - }; - 85E412282A46C8B900183F2B /* Models */ = { - isa = PBXGroup; - children = ( - 85E412292A46C8CA00183F2B /* LocationsSettings.swift */, - ); - path = Models; - sourceTree = ""; - }; - B60718472B17DC5B009CDAB4 /* Views */ = { - isa = PBXGroup; - children = ( - 201169E42837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift */, - B60718432B17DBE5009CDAB4 /* SourceControlNavigatorRepositoryItem.swift */, - B607183E2B17DB07009CDAB4 /* SourceControlNavigatorRepositoryView+contextMenu.swift */, - B60718412B17DB93009CDAB4 /* SourceControlNavigatorRepositoryView+outlineGroupData.swift */, - ); - path = Views; - sourceTree = ""; - }; - B60718482B17DC64009CDAB4 /* Models */ = { - isa = PBXGroup; - children = ( - B60718452B17DC15009CDAB4 /* RepoOutlineGroupItem.swift */, - ); - path = Models; - sourceTree = ""; - }; - B60718492B17DC85009CDAB4 /* Views */ = { - isa = PBXGroup; - children = ( - 201169E12837B3D800F92B46 /* SourceControlNavigatorChangesView.swift */, - 201169DA2837B34000F92B46 /* SourceControlNavigatorChangedFileView.swift */, - 04BA7C0D2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift */, - 04BA7C232AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift */, - B65B10F72B081A34002852CF /* SourceControlNavigatorNoRemotesView.swift */, - B65B10FD2B08B07D002852CF /* SourceControlNavigatorChangesList.swift */, - ); - path = Views; - sourceTree = ""; - }; - B61DA9DD29D929BF00BF4A43 /* Pages */ = { - isa = PBXGroup; - children = ( - B664C3AD2B965F4500816B4E /* NavigationSettings */, - B61DA9E129D929F900BF4A43 /* GeneralSettings */, - B6E41C6E29DD15540088F9F4 /* AccountsSettings */, - 58F2EAAE292FB2B0004A9BDE /* ThemeSettings */, - B6EA1FF329DA37D3001BF195 /* TextEditingSettings */, - 5B698A082B262F8400DE9392 /* SearchSettings */, - B6CF632629E5417C0085880A /* Keybindings */, - B6F0516E29D9E35300D72287 /* LocationsSettings */, - B6F0516D29D9E34200D72287 /* SourceControlSettings */, - B6F0516C29D9E32700D72287 /* TerminalSettings */, + 28052DFB29730DE300F4F90A /* Debug.xcconfig */, + 28052DFC29730DF600F4F90A /* Alpha.xcconfig */, + 28052DFD29730E0300F4F90A /* Beta.xcconfig */, + 28052DFE29730E0B00F4F90A /* Release.xcconfig */, + 8B9A0E162B9FE84B007E2DBF /* Pre.xcconfig */, ); - path = Pages; + path = Configs; sourceTree = ""; }; - B61DA9E129D929F900BF4A43 /* GeneralSettings */ = { + 283BDCC22972F211002AFF81 /* Acknowledgements */ = { isa = PBXGroup; children = ( - B6CF632529E541520085880A /* Models */, - B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */, - B6AB09B22AB919CF0003A3A6 /* View+actionBar.swift */, + 283BDCC42972F236002AFF81 /* AcknowledgementsTests.swift */, ); - path = GeneralSettings; + path = Acknowledgements; sourceTree = ""; }; - B62AEDB12A1FD7F0009A9F52 /* Views */ = { + 2BE487ED28245162003F3F64 /* OpenWithCodeEdit */ = { isa = PBXGroup; children = ( - B62AEDD32A27B29F009A9F52 /* PaneToolbar.swift */, - 58822513292C280D00E83CDE /* UtilityAreaView.swift */, - B62AEDC82A2704F3009A9F52 /* UtilityAreaTabView.swift */, - B62AEDDA2A27C1B3009A9F52 /* OSLogType+Color.swift */, - B62AEDD02A27B264009A9F52 /* View+paneToolbar.swift */, + 2BE487EE28245162003F3F64 /* FinderSync.swift */, + 2BE487F028245162003F3F64 /* Info.plist */, + 2B7AC06A282452FB0082A5B8 /* Media.xcassets */, + 2B15CA0028254139004E8F22 /* OpenWithCodeEdit.entitlements */, ); - path = Views; + path = OpenWithCodeEdit; sourceTree = ""; }; - B640A9A329E218E300715F20 /* Models */ = { + 30F67AB32BAF76A200AB0168 /* Components */ = { isa = PBXGroup; children = ( - 58F2EADB292FB2B0004A9BDE /* SettingsData.swift */, - 58F2EAD2292FB2B0004A9BDE /* Settings.swift */, - 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */, - 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */, - 6C0D0C6729E861B000AE4D3F /* SettingsSidebarFix.swift */, - 850C631129D6B03400E1444C /* SettingsPage.swift */, - 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */, - 852E62002A5C17E500447138 /* PageAndSettings.swift */, + 30F67AB22BAF76A200AB0168 /* EffectView.swift */, ); - path = Models; + path = Components; sourceTree = ""; }; - B658FB2327DA9E0F00EA4DBD = { + 30F67AB52BAF76A200AB0168 /* Models */ = { isa = PBXGroup; children = ( - B658FB2E27DA9E0F00EA4DBD /* CodeEdit */, - 587B60F329340A8000D5CD8F /* CodeEditTests */, - 28052E0129730F2F00F4F90A /* Configs */, - B6FF04772B6C08AC002C2C78 /* DefaultThemes */, - 58F2EACE292FB2B0004A9BDE /* Documentation.docc */, - 2BE487ED28245162003F3F64 /* OpenWithCodeEdit */, - 284DC8502978BA2600BF2770 /* .all-contributorsrc */, - 283BDCBC2972EEBD002AFF81 /* Package.resolved */, - B658FB2D27DA9E0F00EA4DBD /* Products */, - 5C403B8D27E20F8000788241 /* Frameworks */, + 30F67AB42BAF76A200AB0168 /* RecentProject.swift */, ); - indentWidth = 4; + path = Models; sourceTree = ""; - tabWidth = 4; }; - B658FB2D27DA9E0F00EA4DBD /* Products */ = { + 30F67AB92BAF76A200AB0168 /* DependencyInjection */ = { isa = PBXGroup; children = ( - B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */, - B658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */, - B658FB4727DA9E1000EA4DBD /* CodeEditUITests.xctest */, - 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */, + 30F67AB62BAF76A200AB0168 /* ServiceContainer.swift */, + 30F67AB72BAF76A200AB0168 /* ServiceTypes.swift */, + 30F67AB82BAF76A200AB0168 /* ServiceWrapper.swift */, ); - name = Products; + path = DependencyInjection; sourceTree = ""; }; - B658FB2E27DA9E0F00EA4DBD /* CodeEdit */ = { + 30F67ABB2BAF76A200AB0168 /* DocumentService */ = { isa = PBXGroup; children = ( - 5831E3C52933E6CB00D5A6D2 /* Features */, - D7211D4427E066D4008F2ED7 /* Localization */, - B658FB3527DA9E1000EA4DBD /* Preview Content */, - 3E0196712A392170002648D8 /* ShellIntegration */, - 58D01C85293167DC00C5B6B4 /* Utils */, - B66A4E5529C918A0004573B4 /* SceneID.swift */, - 0468438427DC76E200F8E88E /* AppDelegate.swift */, - B658FB3327DA9E1000EA4DBD /* Assets.xcassets */, - B658FB3827DA9E1000EA4DBD /* CodeEdit.entitlements */, - B66A4E4B29C9179B004573B4 /* CodeEditApp.swift */, - 04660F6027E3A68A00477777 /* Info.plist */, - 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */, - B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */, - 5C4BB1E028212B1E00A92FB2 /* World.swift */, + 30F67ABA2BAF76A200AB0168 /* DocumentService.swift */, ); - path = CodeEdit; + path = DocumentService; sourceTree = ""; }; - B658FB3527DA9E1000EA4DBD /* Preview Content */ = { + 30F67ABF2BAF76A200AB0168 /* PasteboardService */ = { isa = PBXGroup; children = ( - B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */, + 30F67ABC2BAF76A200AB0168 /* NSPasteboardProvider.swift */, + 30F67ABD2BAF76A200AB0168 /* PasteboardService.swift */, + 30F67ABE2BAF76A200AB0168 /* UIPasteboardProvider.swift */, ); - path = "Preview Content"; + path = PasteboardService; sourceTree = ""; }; - B664C3AD2B965F4500816B4E /* NavigationSettings */ = { + 30F67AC02BAF76A200AB0168 /* Services */ = { isa = PBXGroup; children = ( - B664C3AE2B965F5500816B4E /* Models */, - B664C3B22B96634F00816B4E /* NavigationSettingsView.swift */, + 30F67AB92BAF76A200AB0168 /* DependencyInjection */, + 30F67ABB2BAF76A200AB0168 /* DocumentService */, + 30F67ABF2BAF76A200AB0168 /* PasteboardService */, ); - path = NavigationSettings; + path = Services; sourceTree = ""; }; - B664C3AE2B965F5500816B4E /* Models */ = { + 30F67AC22BAF76A200AB0168 /* Bundle */ = { isa = PBXGroup; children = ( - B664C3AF2B965F6C00816B4E /* NavigationSettings.swift */, + 30F67AC12BAF76A200AB0168 /* Bundle+Info.swift */, ); - path = Models; + path = Bundle; sourceTree = ""; }; - B66DD19E2B3C0E0C0004FFEC /* History */ = { + 30F67AC42BAF76A200AB0168 /* NSApplication */ = { isa = PBXGroup; children = ( - B66DD19F2B3C0E160004FFEC /* Views */, + 30F67AC32BAF76A200AB0168 /* NSApp+openWindow.swift */, ); - path = History; + path = NSApplication; sourceTree = ""; }; - B66DD19F2B3C0E160004FFEC /* Views */ = { + 30F67AC52BAF76A200AB0168 /* Extensions */ = { isa = PBXGroup; children = ( - B6C4F2A02B3CA37500B2B140 /* SourceControlNavigatorHistoryView.swift */, - 20EBB504280C329800F3A5DA /* CommitListItemView.swift */, - B6C4F2A22B3CA74800B2B140 /* CommitDetailsView.swift */, - B6C4F2A82B3CB00100B2B140 /* CommitDetailsHeaderView.swift */, - B6C4F2AB2B3CC4D000B2B140 /* CommitChangedFileListItemView.swift */, + 30F67AC22BAF76A200AB0168 /* Bundle */, + 30F67AC42BAF76A200AB0168 /* NSApplication */, ); - path = Views; + path = Extensions; sourceTree = ""; }; - B67660622AA961E900CD56B0 /* Tab */ = { + 30F67AC82BAF76A200AB0168 /* Utilities */ = { isa = PBXGroup; children = ( - 58AFAA272933C65C00482B53 /* Models */, - B6C6A42F29771F7100A3D28F /* EditorTabBackground.swift */, - B6C6A42D29771A8D00A3D28F /* EditorTabButtonStyle.swift */, - 04BC1CDD2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift */, - B6C6A429297716A500A3D28F /* EditorTabCloseButton.swift */, - 587FB98F29C1246400B519DD /* EditorTabView.swift */, + 30F67AC52BAF76A200AB0168 /* Extensions */, + 30F67AC62BAF76A200AB0168 /* ProjectManager.swift */, + 30F67AC72BAF76A200AB0168 /* SceneID.swift */, ); - path = Tab; + path = Utilities; sourceTree = ""; }; - B67660632AA970E300CD56B0 /* Views */ = { + 30F67ACF2BAF76A200AB0168 /* Components */ = { isa = PBXGroup; children = ( - 6C147C4C29A32AA30089B630 /* EditorView.swift */, - 6C147C4829A32A080089B630 /* EditorLayoutView.swift */, + 30F67AC92BAF76A200AB0168 /* RecentProjectItem.swift */, + 30F67ACA2BAF76A200AB0168 /* RecentProjectsListView.swift */, + 30F67ACB2BAF76A200AB0168 /* WelcomeActionView.swift */, + 30F67ACC2BAF76A200AB0168 /* WelcomeScene.swift */, + 30F67ACD2BAF76A200AB0168 /* WelcomeView.swift */, + 30F67ACE2BAF76A200AB0168 /* WelcomeWindowView.swift */, ); - path = Views; + path = Components; sourceTree = ""; }; - B67660642AA970ED00CD56B0 /* Models */ = { + 30F67AD02BAF76A200AB0168 /* Welcome */ = { isa = PBXGroup; children = ( - 6C91D57129B176FF0059A90D /* EditorManager.swift */, - 6C147C3E29A3281D0089B630 /* EditorLayout.swift */, - 6C147C3D29A3281D0089B630 /* Editor.swift */, - 6CA1AE942B46950000378EAB /* EditorInstance.swift */, - 6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */, - 6C092ED92A53A58600489202 /* EditorLayout+StateRestoration.swift */, + 30F67ACF2BAF76A200AB0168 /* Components */, ); - path = Models; + path = Welcome; sourceTree = ""; }; - B67660662AA9726F00CD56B0 /* HistoryInspector */ = { + 30F67AD12BAF76A200AB0168 /* Views */ = { isa = PBXGroup; children = ( - 2072FA12280D74ED00C7F8D4 /* HistoryInspectorModel.swift */, - 20D839AD280E0CA700B27357 /* HistoryPopoverView.swift */, - 20EBB502280C327C00F3A5DA /* HistoryInspectorView.swift */, - B6C4F2A52B3CABD200B2B140 /* HistoryInspectorItemView.swift */, + 30F67AD02BAF76A200AB0168 /* Welcome */, ); - path = HistoryInspector; + path = Views; sourceTree = ""; }; - B67660672AA972B000CD56B0 /* FileInspector */ = { + 30F67AD22BAF76A200AB0168 /* CodeEditShared */ = { isa = PBXGroup; children = ( - 20EBB500280C325D00F3A5DA /* FileInspectorView.swift */, + 30F67AB32BAF76A200AB0168 /* Components */, + 30F67AB52BAF76A200AB0168 /* Models */, + 30F67AC02BAF76A200AB0168 /* Services */, + 30F67AC82BAF76A200AB0168 /* Utilities */, + 30F67AD12BAF76A200AB0168 /* Views */, ); - path = FileInspector; + path = CodeEditShared; sourceTree = ""; }; - B67660682AA972D400CD56B0 /* Models */ = { + 30F67AE62BAF76B000AB0168 /* App */ = { isa = PBXGroup; children = ( - 6CE6226D2A2A1CDE0013085C /* NavigatorTab.swift */, ); - path = Models; + path = App; sourceTree = ""; }; - B67660692AA972DC00CD56B0 /* Views */ = { + 30F67AE72BAF76B000AB0168 /* macOS */ = { isa = PBXGroup; children = ( - 287776E627E3413200D46668 /* NavigatorAreaView.swift */, + 30F67AE62BAF76B000AB0168 /* App */, ); - path = Views; + path = macOS; sourceTree = ""; }; - B676606A2AA973A500CD56B0 /* TerminalUtility */ = { + 30F67AE82BAF76B000AB0168 /* Web */ = { isa = PBXGroup; children = ( - B62AEDB22A1FD95B009A9F52 /* UtilityAreaTerminalView.swift */, - B62AEDBB2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift */, ); - path = TerminalUtility; + path = Web; sourceTree = ""; }; - B676606B2AA973B200CD56B0 /* DebugUtility */ = { + 30F67AE92BAF76B100AB0168 /* App */ = { isa = PBXGroup; children = ( - B62AEDB42A1FE295009A9F52 /* UtilityAreaDebugView.swift */, ); - path = DebugUtility; + path = App; sourceTree = ""; }; - B676606C2AA973C000CD56B0 /* OutputUtility */ = { + 30F67AEA2BAF76B100AB0168 /* iPadOS */ = { isa = PBXGroup; children = ( - B62AEDB72A1FE2DC009A9F52 /* UtilityAreaOutputView.swift */, + 30F67AE92BAF76B100AB0168 /* App */, ); - path = OutputUtility; + path = iPadOS; sourceTree = ""; }; - B676606D2AA9741900CD56B0 /* Models */ = { + 3E0196712A392170002648D8 /* ShellIntegration */ = { isa = PBXGroup; children = ( - 6CE6226A2A2A1C730013085C /* UtilityAreaTab.swift */, + 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */, + 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */, ); - path = Models; + path = ShellIntegration; sourceTree = ""; }; - B67B270029D7868000FB9301 /* Settings */ = { + 4EE96EC82960562000FFBEA8 /* Documents */ = { isa = PBXGroup; children = ( - B640A9A329E218E300715F20 /* Models */, - B6CF632A29E5436C0085880A /* Views */, - B61DA9DD29D929BF00BF4A43 /* Pages */, - B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */, - 850C630F29D6B01D00E1444C /* SettingsView.swift */, - 58F2EAE1292FB2B0004A9BDE /* SoftwareUpdater.swift */, + 4EE96ECC296059D200FFBEA8 /* Mocks */, + 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */, + 6195E3102B640485007261CA /* WorkspaceDocument+SearchState+IndexTests.swift */, + 6195E30C2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift */, + 6195E30E2B640474007261CA /* WorkspaceDocument+SearchState+FindAndReplaceTests.swift */, + 613053582B23916D00D767E3 /* Indexer */, ); - path = Settings; + path = Documents; sourceTree = ""; }; - B6AB09AB2AAACBF70003A3A6 /* Tabs */ = { + 4EE96ECC296059D200FFBEA8 /* Mocks */ = { isa = PBXGroup; children = ( - B67660622AA961E900CD56B0 /* Tab */, - B6AB09AC2AAACC190003A3A6 /* Views */, + 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */, ); - path = Tabs; + path = Mocks; sourceTree = ""; }; - B6AB09AC2AAACC190003A3A6 /* Views */ = { + 583E527429361B39001AB554 /* CodeEditUI */ = { isa = PBXGroup; children = ( - B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */, - B6E55C3A2A95368E003ECC7D /* EditorTabsOverflowShadow.swift */, + 583E527729361B39001AB554 /* __Snapshots__ */, + 583E527529361B39001AB554 /* CodeEditUITests.swift */, + 583E52A129361BFD001AB554 /* CodeEditUITests-Bridging-Header.h */, ); - path = Views; + path = CodeEditUI; sourceTree = ""; }; - B6CF632529E541520085880A /* Models */ = { + 583E527729361B39001AB554 /* __Snapshots__ */ = { isa = PBXGroup; children = ( - 58F2EAD6292FB2B0004A9BDE /* GeneralSettings.swift */, + 583E527829361B39001AB554 /* UnitTests */, ); - path = Models; + path = __Snapshots__; sourceTree = ""; }; - B6CF632629E5417C0085880A /* Keybindings */ = { + 583E527829361B39001AB554 /* UnitTests */ = { isa = PBXGroup; children = ( - B6CF632729E5418A0085880A /* Models */, + 583E527929361B39001AB554 /* testHelpButtonDark.1.png */, + 583E527A29361B39001AB554 /* testEffectViewLight.1.png */, + 583E527B29361B39001AB554 /* testSegmentedControlLight.1.png */, + 583E527C29361B39001AB554 /* testSegmentedControlProminentLight.1.png */, + 583E527D29361B39001AB554 /* testHelpButtonLight.1.png */, + 583E527E29361B39001AB554 /* testBranchPickerDark.1.png */, + 583E527F29361B39001AB554 /* testFontPickerViewDark.1.png */, + 583E528029361B39001AB554 /* testFontPickerViewLight.1.png */, + 583E528129361B39001AB554 /* testSegmentedControlProminentDark.1.png */, + 583E528229361B39001AB554 /* testSegmentedControlDark.1.png */, + 583E528329361B39001AB554 /* testEffectViewDark.1.png */, + 583E528429361B39001AB554 /* testBranchPickerLight.1.png */, ); - path = Keybindings; + path = UnitTests; sourceTree = ""; }; - B6CF632729E5418A0085880A /* Models */ = { + 587B60F329340A8000D5CD8F /* CodeEditTests */ = { isa = PBXGroup; children = ( - 58F2EAD4292FB2B0004A9BDE /* KeybindingsSettings.swift */, + 587B60FE293416C900D5CD8F /* Features */, + 587B60F42934122D00D5CD8F /* Utils */, ); - path = Models; + path = CodeEditTests; sourceTree = ""; }; - B6CF632829E541AC0085880A /* Models */ = { + 587B60F42934122D00D5CD8F /* Utils */ = { isa = PBXGroup; children = ( - 58F2EADA292FB2B0004A9BDE /* TerminalSettings.swift */, + 587B61002934170A00D5CD8F /* UnitTests_Extensions.swift */, + 587B60F52934124100D5CD8F /* CEWorkspaceFileManager */, ); - path = Models; + path = Utils; sourceTree = ""; }; - B6CF632929E541C00085880A /* Models */ = { + 587B60F52934124100D5CD8F /* CEWorkspaceFileManager */ = { isa = PBXGroup; children = ( - 58F2EAD8292FB2B0004A9BDE /* TextEditingSettings.swift */, + 587B60F72934124100D5CD8F /* CEWorkspaceFileManagerTests.swift */, ); - path = Models; + path = CEWorkspaceFileManager; sourceTree = ""; }; - B6CF632A29E5436C0085880A /* Views */ = { + 587B60FE293416C900D5CD8F /* Features */ = { isa = PBXGroup; children = ( - B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */, - B640A99D29E2184700715F20 /* SettingsForm.swift */, - B6EA1FFF29DB7966001BF195 /* SettingsColorPicker.swift */, - B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */, - B61A606029F188AB009B43F9 /* ExternalLink.swift */, - B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */, - B6EA200129DB7F81001BF195 /* View+ConstrainHeightToWindow.swift */, - B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */, + 613899BD2B6E70E200A5CAF6 /* Search */, + 283BDCC22972F211002AFF81 /* Acknowledgements */, + 4EE96EC82960562000FFBEA8 /* Documents */, + 583E527429361B39001AB554 /* CodeEditUI */, + 587B612C2934199800D5CD8F /* CodeFile */, ); - path = Views; + path = Features; sourceTree = ""; }; - B6E41C6E29DD15540088F9F4 /* AccountsSettings */ = { + 587B612C2934199800D5CD8F /* CodeFile */ = { isa = PBXGroup; children = ( - B6E41C9229DEAE0A0088F9F4 /* Models */, - B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */, - B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */, - B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */, - B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */, - B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */, - B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */, - B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */, + 587B612D293419B700D5CD8F /* CodeFileTests.swift */, ); - path = AccountsSettings; + path = CodeFile; sourceTree = ""; }; - B6E41C9229DEAE0A0088F9F4 /* Models */ = { + 5C403B8D27E20F8000788241 /* Frameworks */ = { isa = PBXGroup; children = ( - B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */, - 58F2EADD292FB2B0004A9BDE /* AccountsSettings.swift */, + 589F3E342936185400E1A4DA /* XCTest.framework */, ); - path = Models; + name = Frameworks; sourceTree = ""; }; - B6EA1FF329DA37D3001BF195 /* TextEditingSettings */ = { + 613053582B23916D00D767E3 /* Indexer */ = { isa = PBXGroup; children = ( - B6CF632929E541C00085880A /* Models */, - B6EA1FF429DA380E001BF195 /* TextEditingSettingsView.swift */, + 6130535B2B23933D00D767E3 /* MemoryIndexingTests.swift */, + 6130535E2B23A31300D767E3 /* MemorySearchTests.swift */, + 6130536A2B24722C00D767E3 /* AsyncIndexingTests.swift */, + 613053642B23A49300D767E3 /* TemporaryFile.swift */, ); - path = TextEditingSettings; + path = Indexer; sourceTree = ""; }; - B6EE988E27E8877C00CDD8AB /* InspectorArea */ = { + 613899BD2B6E70E200A5CAF6 /* Search */ = { isa = PBXGroup; children = ( - 3026F50B2AC006A10061227E /* ViewModels */, - B67660672AA972B000CD56B0 /* FileInspector */, - B67660662AA9726F00CD56B0 /* HistoryInspector */, - 20EBB50B280C382800F3A5DA /* Models */, - 20EBB4FF280C325000F3A5DA /* Views */, + 613899BE2B6E70EA00A5CAF6 /* FuzzySearch */, ); - path = InspectorArea; + path = Search; sourceTree = ""; }; - B6F0516C29D9E32700D72287 /* TerminalSettings */ = { + 613899BE2B6E70EA00A5CAF6 /* FuzzySearch */ = { isa = PBXGroup; children = ( - B6CF632829E541AC0085880A /* Models */, - B6F0517C29D9E4B100D72287 /* TerminalSettingsView.swift */, + 613899BF2B6E70FE00A5CAF6 /* FuzzySearchTests.swift */, ); - path = TerminalSettings; + path = FuzzySearch; sourceTree = ""; }; - B6F0516D29D9E34200D72287 /* SourceControlSettings */ = { + B658FB2327DA9E0F00EA4DBD = { isa = PBXGroup; children = ( - 58F2EAA9292FB2B0004A9BDE /* Models */, - B6F0517A29D9E46400D72287 /* SourceControlSettingsView.swift */, - B6F0517629D9E3AD00D72287 /* SourceControlGeneralView.swift */, - B6F0517829D9E3C900D72287 /* SourceControlGitView.swift */, + B658FB2E27DA9E0F00EA4DBD /* CodeEdit */, + 587B60F329340A8000D5CD8F /* CodeEditTests */, + 28052E0129730F2F00F4F90A /* Configs */, + B6FF04772B6C08AC002C2C78 /* DefaultThemes */, + 58F2EACE292FB2B0004A9BDE /* Documentation.docc */, + 2BE487ED28245162003F3F64 /* OpenWithCodeEdit */, + 284DC8502978BA2600BF2770 /* .all-contributorsrc */, + B658FB2D27DA9E0F00EA4DBD /* Products */, + 5C403B8D27E20F8000788241 /* Frameworks */, ); - path = SourceControlSettings; + indentWidth = 4; sourceTree = ""; + tabWidth = 4; }; - B6F0516E29D9E35300D72287 /* LocationsSettings */ = { + B658FB2D27DA9E0F00EA4DBD /* Products */ = { isa = PBXGroup; children = ( - 85E412282A46C8B900183F2B /* Models */, - B6F0516F29D9E36800D72287 /* LocationsSettingsView.swift */, + B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */, + B658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */, + B658FB4727DA9E1000EA4DBD /* CodeEditUITests.xctest */, + 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */, ); - path = LocationsSettings; + name = Products; sourceTree = ""; }; - B6F0518D29DA29F900D72287 /* Models */ = { + B658FB2E27DA9E0F00EA4DBD /* CodeEdit */ = { isa = PBXGroup; children = ( - 58F2EAE0292FB2B0004A9BDE /* ThemeSettings.swift */, - B6EA1FE429DA33DB001BF195 /* ThemeModel.swift */, - B6EA1FE629DA341D001BF195 /* Theme.swift */, + 30F67AD22BAF76A200AB0168 /* CodeEditShared */, + D7211D4427E066D4008F2ED7 /* Localization */, + 3E0196712A392170002648D8 /* ShellIntegration */, + 30F67AEA2BAF76B100AB0168 /* iPadOS */, + 30F67AE72BAF76B000AB0168 /* macOS */, + 30F67AE82BAF76B000AB0168 /* Web */, + B658FB3327DA9E1000EA4DBD /* Assets.xcassets */, + B658FB3827DA9E1000EA4DBD /* CodeEdit.entitlements */, + B66A4E4B29C9179B004573B4 /* CodeEditApp.swift */, + 04660F6027E3A68A00477777 /* Info.plist */, + B658FB3527DA9E1000EA4DBD /* Preview Content */, ); - path = Models; + path = CodeEdit; sourceTree = ""; }; - D7012EE627E757660001E1EF /* FindNavigator */ = { + B658FB3527DA9E1000EA4DBD /* Preview Content */ = { isa = PBXGroup; children = ( - D7012EE727E757850001E1EF /* FindNavigatorView.swift */, - 6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */, - D7E201B127E8D50000CB86D0 /* FindNavigatorForm.swift */, - 6C14CEB12877A5BE001468FE /* FindNavigatorResultList */, - B67DB0F52AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift */, - B628B7B62B223BAD00F9775A /* FindModePicker.swift */, + B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */, ); - path = FindNavigator; + path = "Preview Content"; sourceTree = ""; }; D7211D4427E066D4008F2ED7 /* Localization */ = { @@ -2946,30 +660,15 @@ 2B18499A27F8A7A0005119F0 /* Mark // swiftlint:disable:all as errors | Run Script */, 04ADA0CC27E6043B00BF00B2 /* Add TODO/FIXME as warnings | Run Script */, 04C3255A2801B43A00C8DA2D /* Embed Frameworks */, - 2BE487F528245162003F3F64 /* Embed Foundation Extensions */, 6C6BD6FD29CD154900235D17 /* Embed ExtensionKit ExtensionPoint */, ); buildRules = ( ); dependencies = ( 6C7B1C762A1D57CE005CBBFC /* PBXTargetDependency */, - 2BE487F328245162003F3F64 /* PBXTargetDependency */, ); name = CodeEdit; packageProductDependencies = ( - 2816F593280CF50500DD548B /* CodeEditSymbols */, - 58798289292ED15F0085B254 /* SwiftTerm */, - 58F2EB1D292FB954004A9BDE /* Sparkle */, - 6C147C4429A329350089B630 /* OrderedCollections */, - 6C81916A29B41DD300B75C92 /* DequeModule */, - 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */, - 6C6BD6F729CD14D100235D17 /* CodeEditKit */, - 6C66C31229D05CDC00DE9ED2 /* GRDB */, - 6C2149402A1BB9AB00748382 /* LogStream */, - 6CDEFC9529E22C2700B7C684 /* Introspect */, - 6C0F3A3B2A1D0D5000223D19 /* CodeEditKit */, - 6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */, - 6CBE1CFF2B720565003AC32E /* CodeEditSourceEditor */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -2990,7 +689,6 @@ ); name = CodeEditTests; packageProductDependencies = ( - 583E529B29361BAB001AB554 /* SnapshotTesting */, ); productName = CodeEditTests; productReference = B658FB3D27DA9E1000EA4DBD /* CodeEditTests.xctest */; @@ -3053,18 +751,6 @@ ); mainGroup = B658FB2327DA9E0F00EA4DBD; packageReferences = ( - 2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference "CodeEditSymbols" */, - 287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference "SwiftLintPlugin" */, - 58798288292ED15F0085B254 /* XCRemoteSwiftPackageReference "SwiftTerm" */, - 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */, - 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, - 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */, - 6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference "collectionconcurrencykit" */, - 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference "GRDB.swift" */, - 6C21493F2A1BB9AB00748382 /* XCRemoteSwiftPackageReference "LogStream" */, - 6CDEFC9429E22C2700B7C684 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, - 6C0F3A3A2A1D0D5000223D19 /* XCRemoteSwiftPackageReference "CodeEditKit" */, - 6CBE1CFE2B720565003AC32E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */, ); productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */; projectDirPath = ""; @@ -3092,15 +778,12 @@ buildActionMask = 2147483647; files = ( B6FF04782B6C08AC002C2C78 /* DefaultThemes in Resources */, - 283BDCBD2972EEBD002AFF81 /* Package.resolved in Resources */, B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */, 3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */, - 58A5DFA529339F6400D1BD5D /* default_keybindings.json in Resources */, 3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */, D7211D4727E06BFE008F2ED7 /* Localizable.strings in Resources */, 284DC8512978BA2600BF2770 /* .all-contributorsrc in Resources */, B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */, - 6C6BD6FC29CD152400235D17 /* codeedit.extension.appextensionpoint in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3186,424 +869,32 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 58798285292ED0FB0085B254 /* TerminalEmulatorView+Coordinator.swift in Sources */, - 587B9E7D29301D8F00AC7927 /* GitHubPullRequestRouter.swift in Sources */, - B67DB0EF2AF3E381002DC647 /* PaneTextField.swift in Sources */, - 5878DA842918642000DD95A3 /* ParsePackagesResolved.swift in Sources */, - B628B7932B18369800F9775A /* GitClient+Validate.swift in Sources */, - 5879824F292E78D80085B254 /* CodeFileView.swift in Sources */, - 6C0D0C6829E861B000AE4D3F /* SettingsSidebarFix.swift in Sources */, - 587B9E8429301D8F00AC7927 /* GitHubUser.swift in Sources */, - 04BA7C1C2AE2D84100584E1C /* GitClient+Commit.swift in Sources */, - B65B10EC2B073913002852CF /* CEContentUnavailableView.swift in Sources */, - 5B698A0A2B262FA000DE9392 /* SearchSettingsView.swift in Sources */, - B65B10FB2B08B054002852CF /* Divided.swift in Sources */, - B65B11012B09D5D4002852CF /* GitClient+Pull.swift in Sources */, - 2072FA13280D74ED00C7F8D4 /* HistoryInspectorModel.swift in Sources */, - 852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */, - 587B9DA029300ABD00AC7927 /* PanelDivider.swift in Sources */, - 58822534292C280D00E83CDE /* CursorLocation.swift in Sources */, - 201169E52837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift in Sources */, - 587B9E6A29301D8F00AC7927 /* GitLabPermissions.swift in Sources */, - B6EA1FF529DA380E001BF195 /* TextEditingSettingsView.swift in Sources */, - D7DC4B76298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift in Sources */, - 587B9E9229301D8F00AC7927 /* BitBucketAccount.swift in Sources */, - DE513F52281B672D002260B9 /* EditorTabBarAccessory.swift in Sources */, - 2813F93927ECC4C300E305E4 /* NavigatorAreaView.swift in Sources */, - B664C3B02B965F6C00816B4E /* NavigationSettings.swift in Sources */, - 5B698A0F2B2636A700DE9392 /* SearchSettingsIgnoreGlobPatternItemView.swift in Sources */, - 587B9E8A29301D8F00AC7927 /* GitHubIssue.swift in Sources */, - EC0870F72A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift in Sources */, - B60718202B0C6CE7009CDAB4 /* GitStashEntry.swift in Sources */, 6CAAF69429BCD78600A1F48A /* (null) in Sources */, - 3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */, - 6C82D6C629C012AD00495C54 /* NSApp+openWindow.swift in Sources */, - 6C14CEB028777D3C001468FE /* FindNavigatorListViewController.swift in Sources */, - 587B9E7F29301D8F00AC7927 /* GitHubUserRouter.swift in Sources */, - B62AEDBC2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift in Sources */, - B67DB0F62AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift in Sources */, - 04BA7C142AE2AA7300584E1C /* GitCloneViewModel.swift in Sources */, - B61A606129F188AB009B43F9 /* ExternalLink.swift in Sources */, - 587B9E9729301D8F00AC7927 /* BitBucketAccount+Token.swift in Sources */, - 587B9E7729301D8F00AC7927 /* String+PercentEncoding.swift in Sources */, - 587B9E5B29301D8F00AC7927 /* GitCheckoutBranchView.swift in Sources */, - 2813F93827ECC4AA00E305E4 /* FindNavigatorResultList.swift in Sources */, - 613899B92B6E704500A5CAF6 /* String+Normalise.swift in Sources */, - 04BA7C192AE2D7C600584E1C /* GitClient+Branches.swift in Sources */, - 587B9E8829301D8F00AC7927 /* GitHubFiles.swift in Sources */, - 587B9DA729300ABD00AC7927 /* HelpButton.swift in Sources */, - 6C5B63DE29C76213005454BA /* WindowCodeFileView.swift in Sources */, - 58F2EB08292FB2B0004A9BDE /* TextEditingSettings.swift in Sources */, - 201169DB2837B34000F92B46 /* SourceControlNavigatorChangedFileView.swift in Sources */, - 5882252E292C280D00E83CDE /* StatusBarMaximizeButton.swift in Sources */, - 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */, - 587B9E6F29301D8F00AC7927 /* GitLabProjectAccess.swift in Sources */, - 587B9E6929301D8F00AC7927 /* GitLabEvent.swift in Sources */, - B60718442B17DBE5009CDAB4 /* SourceControlNavigatorRepositoryItem.swift in Sources */, - B67DB0F92AFDF638002DC647 /* IconButtonStyle.swift in Sources */, - 587B9E5E29301D8F00AC7927 /* GitLabCommitRouter.swift in Sources */, - 58F2EB0D292FB2B0004A9BDE /* ThemeSettings.swift in Sources */, - 85CD0C5F2A10CC3200E531FD /* URL+isImage.swift in Sources */, - 587B9D9F29300ABD00AC7927 /* SegmentedControl.swift in Sources */, - 6C7256D729A3D7D000C2D3E0 /* SplitViewControllerView.swift in Sources */, - B6EA1FE529DA33DB001BF195 /* ThemeModel.swift in Sources */, - B6EA200029DB7966001BF195 /* SettingsColorPicker.swift in Sources */, - 58FD7609291EA1CB0051D6E4 /* CommandPaletteView.swift in Sources */, - 58A2E40C29C3975D005CB615 /* CEWorkspaceFileIcon.swift in Sources */, - 587B9E8F29301D8F00AC7927 /* BitBucketUserRouter.swift in Sources */, - B66A4E5129C917D5004573B4 /* AboutWindow.swift in Sources */, - B6C4F2A62B3CABD200B2B140 /* HistoryInspectorItemView.swift in Sources */, - B65B10FE2B08B07D002852CF /* SourceControlNavigatorChangesList.swift in Sources */, + 30F67ADF2BAF76A200AB0168 /* SceneID.swift in Sources */, 58F2EB03292FB2B0004A9BDE /* Documentation.docc in Sources */, - 611192042B08CCED00D4459B /* SearchIndexer+ProgressiveSearch.swift in Sources */, - 611192022B08CCDC00D4459B /* SearchIndexer+Search.swift in Sources */, - 04BA7C272AE2E9F100584E1C /* GitClient+Push.swift in Sources */, - B664C3B32B96634F00816B4E /* NavigationSettingsView.swift in Sources */, - 2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */, - D7012EE827E757850001E1EF /* FindNavigatorView.swift in Sources */, - 58A5DF8029325B5A00D1BD5D /* GitClient.swift in Sources */, - D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */, - 6CE6226E2A2A1CDE0013085C /* NavigatorTab.swift in Sources */, - 041FC6AD2AE437CE00C1F65A /* SourceControlNavigatorNewBranchView.swift in Sources */, - 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */, - 6CED16E42A3E660D000EC962 /* String+Lines.swift in Sources */, - 587B9E6B29301D8F00AC7927 /* GitLabAvatarURL.swift in Sources */, - 58798233292E30B90085B254 /* FeedbackToolbar.swift in Sources */, - 587B9E6829301D8F00AC7927 /* GitLabAccountModel.swift in Sources */, - 5878DAA7291AE76700DD95A3 /* QuickOpenViewModel.swift in Sources */, - 6CFF967429BEBCC300182D6F /* FindCommands.swift in Sources */, - 587B9E6529301D8F00AC7927 /* GitLabGroupAccess.swift in Sources */, - 6C91D57229B176FF0059A90D /* EditorManager.swift in Sources */, - 6C82D6BC29C00CD900495C54 /* FirstResponderPropertyWrapper.swift in Sources */, - 58D01C9B293167DC00C5B6B4 /* CodeEditKeychainConstants.swift in Sources */, - B640A99E29E2184700715F20 /* SettingsForm.swift in Sources */, - B62AEDD12A27B264009A9F52 /* View+paneToolbar.swift in Sources */, - 5878DAB1291D627C00DD95A3 /* EditorPathBarComponent.swift in Sources */, - B628B7B72B223BAD00F9775A /* FindModePicker.swift in Sources */, - 587B9E6E29301D8F00AC7927 /* GitLabProject.swift in Sources */, - 58798234292E30B90085B254 /* FeedbackIssueArea.swift in Sources */, - 852C7E332A587279006BA599 /* SearchableSettingsPage.swift in Sources */, - 587B9E5F29301D8F00AC7927 /* GitLabProjectRouter.swift in Sources */, - 587B9E7329301D8F00AC7927 /* GitRouter.swift in Sources */, - 6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */, - 61A53A812B4449F00093BF8A /* WorkspaceDocument+Index.swift in Sources */, - 201169DD2837B3AC00F92B46 /* SourceControlNavigatorToolbarBottom.swift in Sources */, - 587B9E8B29301D8F00AC7927 /* GitHubAccount+deleteReference.swift in Sources */, - 58798237292E30B90085B254 /* FeedbackView.swift in Sources */, - 587B9E9829301D8F00AC7927 /* GitCommit.swift in Sources */, - 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */, - 587B9E9429301D8F00AC7927 /* BitBucketTokenConfiguration.swift in Sources */, - 04BA7C222AE2D95E00584E1C /* GitClient+CommitHistory.swift in Sources */, - 581BFB672926431000D251EC /* WelcomeWindowView.swift in Sources */, - 58A5DFA329339F6400D1BD5D /* CommandManager.swift in Sources */, - 58798284292ED0FB0085B254 /* TerminalEmulatorView.swift in Sources */, - B6C4F2AC2B3CC4D000B2B140 /* CommitChangedFileListItemView.swift in Sources */, - 6C82D6B329BFD88700495C54 /* NavigateCommands.swift in Sources */, + 30F67AD42BAF76A200AB0168 /* RecentProject.swift in Sources */, + 30F67ADE2BAF76A200AB0168 /* ProjectManager.swift in Sources */, + 30F67AE22BAF76A200AB0168 /* WelcomeActionView.swift in Sources */, + 30F67AD32BAF76A200AB0168 /* EffectView.swift in Sources */, + 30F67ADA2BAF76A200AB0168 /* PasteboardService.swift in Sources */, B66A4E4C29C9179B004573B4 /* CodeEditApp.swift in Sources */, - 4E7F066629602E7B00BB3C12 /* CodeEditSplitViewController.swift in Sources */, - 587B9E8D29301D8F00AC7927 /* GitHubAccount.swift in Sources */, - 201169E72837B5CA00F92B46 /* SourceControlManager.swift in Sources */, - 58822528292C280D00E83CDE /* StatusBarEncodingSelector.swift in Sources */, - 6C7F37FE2A3EA6FA00217B83 /* View+focusedValue.swift in Sources */, - B6C4F2A12B3CA37500B2B140 /* SourceControlNavigatorHistoryView.swift in Sources */, - 6CBE1CFB2B71DAA6003AC32E /* Loopable.swift in Sources */, - B6C6A43029771F7100A3D28F /* EditorTabBackground.swift in Sources */, - B60718372B170638009CDAB4 /* SourceControlNavigatorRenameBranchView.swift in Sources */, - 6C578D8129CD294800DC73B2 /* ExtensionActivatorView.swift in Sources */, - B6F0517D29D9E4B100D72287 /* TerminalSettingsView.swift in Sources */, - 587B9E8C29301D8F00AC7927 /* GitHubOpenness.swift in Sources */, - 5894E59729FEF7740077E59C /* CEWorkspaceFile+Recursion.swift in Sources */, - 9D36E1BF2B5E7D7500443C41 /* GitBranchesGroup.swift in Sources */, - 587B9E8229301D8F00AC7927 /* GitHubPreviewHeader.swift in Sources */, - 611191FC2B08CCB800D4459B /* SearchIndexer+AsyncController.swift in Sources */, - 6C578D8929CD36E400DC73B2 /* Commands+ForEach.swift in Sources */, - 611192082B08CCFD00D4459B /* SearchIndexer+Terms.swift in Sources */, - 28B8F884280FFE4600596236 /* NSTableView+Background.swift in Sources */, - 6CBA0D512A1BF524002C6FAA /* SegmentedControlImproved.swift in Sources */, - 58F2EB06292FB2B0004A9BDE /* KeybindingsSettings.swift in Sources */, - 587B9E8E29301D8F00AC7927 /* BitBucketRepositoryRouter.swift in Sources */, - B61A606929F4481A009B43F9 /* MonospacedFontPicker.swift in Sources */, - B61DA9DF29D929E100BF4A43 /* GeneralSettingsView.swift in Sources */, - B6D7EA5C297107DD00301FAC /* InspectorField.swift in Sources */, - 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */, - 85E4122A2A46C8CA00183F2B /* LocationsSettings.swift in Sources */, - 613899B12B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift in Sources */, - 581550D129FBD30400684881 /* TextTableViewCell.swift in Sources */, - 587B9E6629301D8F00AC7927 /* GitLabProjectHook.swift in Sources */, - 587B9E9329301D8F00AC7927 /* BitBucketOAuthConfiguration.swift in Sources */, - 6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */, - 58F2EB0A292FB2B0004A9BDE /* SettingsData.swift in Sources */, - 20EBB503280C327C00F3A5DA /* HistoryInspectorView.swift in Sources */, - B6EA1FE729DA341D001BF195 /* Theme.swift in Sources */, - 587B9E7529301D8F00AC7927 /* String+QueryParameters.swift in Sources */, - B60718312B15A9A3009CDAB4 /* CEOutlineGroup.swift in Sources */, - 58798219292D92370085B254 /* SearchModeModel.swift in Sources */, - 6C5C891B2A3F736500A94FE1 /* FocusedValues.swift in Sources */, - 611192062B08CCF600D4459B /* SearchIndexer+Add.swift in Sources */, - B62AEDD72A27B3D0009A9F52 /* UtilityAreaTabViewModel.swift in Sources */, - 85773E1E2A3E0A1F00C5D926 /* SettingsSearchResult.swift in Sources */, - B66A4E4F29C917B8004573B4 /* WelcomeWindow.swift in Sources */, - 58D01C9D293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift in Sources */, - B6E41C8B29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift in Sources */, - 6C2C155A29B4F4CC00EA60A5 /* Variadic.swift in Sources */, - B6E41C8F29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift in Sources */, - 5882252B292C280D00E83CDE /* StatusBarCursorLocationLabel.swift in Sources */, - 58798252292E78D80085B254 /* ImageFileView.swift in Sources */, - 5882252D292C280D00E83CDE /* StatusBarSplitTerminalButton.swift in Sources */, - 58798238292E30B90085B254 /* FeedbackWindowController.swift in Sources */, - 587B9E6C29301D8F00AC7927 /* GitLabNamespace.swift in Sources */, - 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */, - 587B9E8729301D8F00AC7927 /* GitHubRepositories.swift in Sources */, - 6CE6226B2A2A1C730013085C /* UtilityAreaTab.swift in Sources */, - 587B9DA329300ABD00AC7927 /* SettingsTextEditor.swift in Sources */, - B6F0517B29D9E46400D72287 /* SourceControlSettingsView.swift in Sources */, - 6C147C4D29A32AA30089B630 /* EditorView.swift in Sources */, - B6152B802ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift in Sources */, - 587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */, - 201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */, - 850C631029D6B01D00E1444C /* SettingsView.swift in Sources */, - DE6405A62817734700881FDF /* EditorTabBarNative.swift in Sources */, - 581550CF29FBD30400684881 /* StandardTableViewCell.swift in Sources */, - B62AEDB82A1FE2DC009A9F52 /* UtilityAreaOutputView.swift in Sources */, - B67DB0FC2AFDF71F002DC647 /* IconToggleStyle.swift in Sources */, - 587B9E5C29301D8F00AC7927 /* Parameters.swift in Sources */, - 61538B932B11201900A88846 /* String+Character.swift in Sources */, - 613DF55E2B08DD5D00E9D902 /* FileHelper.swift in Sources */, - 58798235292E30B90085B254 /* FeedbackModel.swift in Sources */, - 04C3255C2801F86900C8DA2D /* ProjectNavigatorMenu.swift in Sources */, - 587B9E6429301D8F00AC7927 /* GitLabCommit.swift in Sources */, - B6E55C3B2A95368E003ECC7D /* EditorTabsOverflowShadow.swift in Sources */, - 58A5DFA229339F6400D1BD5D /* KeybindingManager.swift in Sources */, - B62AEDB32A1FD95B009A9F52 /* UtilityAreaTerminalView.swift in Sources */, - 58AFAA2E2933C69E00482B53 /* EditorTabRepresentable.swift in Sources */, - 6C4104E6297C884F00F472BA /* AboutDetailView.swift in Sources */, - 6C6BD6F129CD13FA00235D17 /* ExtensionDiscovery.swift in Sources */, - 587B9E7A29301D8F00AC7927 /* GitHubReviewsRouter.swift in Sources */, - 04BA7C132AE2AA7300584E1C /* GitCheckoutBranchViewModel.swift in Sources */, - 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */, - DE6F77872813625500D00A76 /* EditorTabBarDivider.swift in Sources */, - 6111920C2B08CD0B00D4459B /* SearchIndexer+InternalMethods.swift in Sources */, - 6CABB1A129C5593800340467 /* OverlayView.swift in Sources */, + 30F67ADD2BAF76A200AB0168 /* NSApp+openWindow.swift in Sources */, + 30F67AE32BAF76A200AB0168 /* WelcomeScene.swift in Sources */, + 30F67AE12BAF76A200AB0168 /* RecentProjectsListView.swift in Sources */, + 30F67AE42BAF76A200AB0168 /* WelcomeView.swift in Sources */, + 30F67AE52BAF76A200AB0168 /* WelcomeWindowView.swift in Sources */, D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */, - 581BFB692926431000D251EC /* WelcomeActionView.swift in Sources */, - 20D839AE280E0CA700B27357 /* HistoryPopoverView.swift in Sources */, - B6E41C7029DD157F0088F9F4 /* AccountsSettingsView.swift in Sources */, - 6CFF967A29BEBD2400182D6F /* ViewCommands.swift in Sources */, - 850C631229D6B03400E1444C /* SettingsPage.swift in Sources */, - 587B9E6729301D8F00AC7927 /* GitLabEventData.swift in Sources */, - B66A4E4529C8E86D004573B4 /* CommandsFixes.swift in Sources */, - 5882252F292C280D00E83CDE /* StatusBarClearButton.swift in Sources */, - 6CE622692A2A174A0013085C /* InspectorTab.swift in Sources */, - 58F2EB04292FB2B0004A9BDE /* SourceControlSettings.swift in Sources */, - 58710159298EB80000951BA4 /* CEWorkspaceFileManager.swift in Sources */, - 582213F0291834A500EFE361 /* AboutView.swift in Sources */, - 6CC9E4B229B5669900C97388 /* Environment+ActiveEditor.swift in Sources */, - 58822526292C280D00E83CDE /* StatusBarBreakpointButton.swift in Sources */, - 58D01C96293167DC00C5B6B4 /* Date+Formatted.swift in Sources */, - B66A4E5629C918A0004573B4 /* SceneID.swift in Sources */, - 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */, - B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */, - 58822529292C280D00E83CDE /* StatusBarLineEndSelector.swift in Sources */, - 5C4BB1E128212B1E00A92FB2 /* World.swift in Sources */, - 581550D029FBD30400684881 /* FileSystemTableViewCell.swift in Sources */, - B607183F2B17DB07009CDAB4 /* SourceControlNavigatorRepositoryView+contextMenu.swift in Sources */, - B62AEDD42A27B29F009A9F52 /* PaneToolbar.swift in Sources */, - D7E201B227E8D50000CB86D0 /* FindNavigatorForm.swift in Sources */, - 287776E927E34BC700D46668 /* EditorTabBarView.swift in Sources */, - B60BE8BD297A167600841125 /* AcknowledgementRowView.swift in Sources */, - B6F0517029D9E36800D72287 /* LocationsSettingsView.swift in Sources */, - B62AEDDC2A27C1B3009A9F52 /* OSLogType+Color.swift in Sources */, - 587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */, - 6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */, - 285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */, + 30F67AD92BAF76A200AB0168 /* NSPasteboardProvider.swift in Sources */, + 30F67ADB2BAF76A200AB0168 /* UIPasteboardProvider.swift in Sources */, + 30F67AD82BAF76A200AB0168 /* DocumentService.swift in Sources */, 6CB9144B29BEC7F100BC47F2 /* (null) in Sources */, - 587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */, - 61538B902B111FE800A88846 /* String+AppearancesOfSubstring.swift in Sources */, - 581BFB6B2926431000D251EC /* RecentProjectItem.swift in Sources */, - 587FB99029C1246400B519DD /* EditorTabView.swift in Sources */, - 587B9DA429300ABD00AC7927 /* OverlayPanel.swift in Sources */, - 58D01C95293167DC00C5B6B4 /* Bundle+Info.swift in Sources */, - B6C6A42A297716A500A3D28F /* EditorTabCloseButton.swift in Sources */, - B60718422B17DB93009CDAB4 /* SourceControlNavigatorRepositoryView+outlineGroupData.swift in Sources */, - 58A5DF7D2931787A00D1BD5D /* ShellClient.swift in Sources */, - 5879821A292D92370085B254 /* SearchResultModel.swift in Sources */, - B6F0517729D9E3AD00D72287 /* SourceControlGeneralView.swift in Sources */, - 587B9E8929301D8F00AC7927 /* GitHubGist.swift in Sources */, - 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */, - 6C092EDA2A53A58600489202 /* EditorLayout+StateRestoration.swift in Sources */, - 6C092EE02A53BFCF00489202 /* WorkspaceStateKey.swift in Sources */, - 613899B52B6E700300A5CAF6 /* FuzzySearchModels.swift in Sources */, - 58D01C94293167DC00C5B6B4 /* Color+HEX.swift in Sources */, - 6C578D8729CD345900DC73B2 /* ExtensionSceneView.swift in Sources */, - B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */, - 58798251292E78D80085B254 /* OtherFileView.swift in Sources */, - 587B9E7929301D8F00AC7927 /* GitHubIssueRouter.swift in Sources */, - 587B9E8029301D8F00AC7927 /* GitHubConfiguration.swift in Sources */, - 58822524292C280D00E83CDE /* StatusBarView.swift in Sources */, - 581550D429FBD37D00684881 /* ProjectNavigatorToolbarBottom.swift in Sources */, - 587B9E7E29301D8F00AC7927 /* GitHubGistRouter.swift in Sources */, - B6AB09A52AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift in Sources */, - 04BA7C0B2AE2A2D100584E1C /* GitBranch.swift in Sources */, + 30F67AD62BAF76A200AB0168 /* ServiceTypes.swift in Sources */, 6CAAF69229BCC71C00A1F48A /* (null) in Sources */, - 581BFB682926431000D251EC /* WelcomeView.swift in Sources */, - 6CFF967829BEBCF600182D6F /* MainCommands.swift in Sources */, - 587B9E7129301D8F00AC7927 /* GitURLSession.swift in Sources */, - 610C0FDA2B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift in Sources */, - 5882252C292C280D00E83CDE /* UtilityAreaView.swift in Sources */, - 2847019E27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift in Sources */, - B607184C2B17E037009CDAB4 /* SourceControlStashChangesView.swift in Sources */, - 6C14CEB32877A68F001468FE /* FindNavigatorMatchListCell.swift in Sources */, - 20EBB501280C325D00F3A5DA /* FileInspectorView.swift in Sources */, - 5B698A162B263BCE00DE9392 /* SearchSettingsModel.swift in Sources */, - 58822531292C280D00E83CDE /* View+isHovering.swift in Sources */, - 587B9E9929301D8F00AC7927 /* GitChangedFile.swift in Sources */, - 6C147C4B29A32A7B0089B630 /* Environment+SplitEditor.swift in Sources */, - 2897E1C72979A29200741E32 /* TrackableScrollView.swift in Sources */, - 58F2EB0E292FB2B0004A9BDE /* SoftwareUpdater.swift in Sources */, - 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */, - 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, - 286471AB27ED51FD0039369D /* ProjectNavigatorView.swift in Sources */, - B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */, - B62AEDB52A1FE295009A9F52 /* UtilityAreaDebugView.swift in Sources */, - 6C049A372A49E2DB00D42923 /* DirectoryEventStream.swift in Sources */, - 04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */, - 615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */, + 30F67AE02BAF76A200AB0168 /* RecentProjectItem.swift in Sources */, + 30F67AD72BAF76A200AB0168 /* ServiceWrapper.swift in Sources */, 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */, - 6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */, - 6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */, - B60718462B17DC15009CDAB4 /* RepoOutlineGroupItem.swift in Sources */, - 613899B32B6E6FEE00A5CAF6 /* FuzzySearchable.swift in Sources */, - B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */, - 5B698A0D2B26327800DE9392 /* SearchSettings.swift in Sources */, - B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */, - 587B9DA629300ABD00AC7927 /* ToolbarBranchPicker.swift in Sources */, - 6C6BD6F629CD145F00235D17 /* ExtensionInfo.swift in Sources */, - 04BA7C202AE2D92B00584E1C /* GitClient+Status.swift in Sources */, - 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */, - 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */, - 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */, - B6E41C9429DEAE260088F9F4 /* SourceControlAccount.swift in Sources */, - 2806E9022979588B000040F4 /* Contributor.swift in Sources */, - 58D01C98293167DC00C5B6B4 /* String+RemoveOccurrences.swift in Sources */, - 5878DAA8291AE76700DD95A3 /* QuickOpenItem.swift in Sources */, - 58FD7608291EA1CB0051D6E4 /* CommandPaletteViewModel.swift in Sources */, - B65B11042B09DB1C002852CF /* GitClient+Fetch.swift in Sources */, - 5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */, - B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */, - 6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */, - B6C4F2A92B3CB00100B2B140 /* CommitDetailsHeaderView.swift in Sources */, - B6EA1FFB29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift in Sources */, - 587B9E7029301D8F00AC7927 /* GitLabUser.swift in Sources */, - 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */, - 58798218292D92370085B254 /* String+SafeOffset.swift in Sources */, - 6C6BD70429CD17B600235D17 /* ExtensionsManager.swift in Sources */, - 587B9E6129301D8F00AC7927 /* GitLabOAuthConfiguration.swift in Sources */, - 587B9E6229301D8F00AC7927 /* GitLabConfiguration.swift in Sources */, - 61A53A7E2B4449870093BF8A /* WorkspaceDocument+Find.swift in Sources */, - 6CABB19E29C5591D00340467 /* NSTableViewWrapper.swift in Sources */, - 5879821B292D92370085B254 /* SearchResultMatchModel.swift in Sources */, - 04BA7C1E2AE2D8A000584E1C /* GitClient+Clone.swift in Sources */, - 58F2EB09292FB2B0004A9BDE /* TerminalSettings.swift in Sources */, - 6C578D8429CD343800DC73B2 /* ExtensionDetailView.swift in Sources */, - B607181D2B0C5BE3009CDAB4 /* GitClient+Stash.swift in Sources */, - B65B10F22B07D34F002852CF /* GitRemote.swift in Sources */, - B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */, - B6EA200229DB7F81001BF195 /* View+ConstrainHeightToWindow.swift in Sources */, - B68C7C212A01DEFE004EA6D6 /* GitHubComment.swift in Sources */, - 613899B72B6E702F00A5CAF6 /* String+LengthOfMatchingPrefix.swift in Sources */, - 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */, - 6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */, - 58F2EB07292FB2B0004A9BDE /* GeneralSettings.swift in Sources */, - B6041F4D29D7A4E9000F3454 /* SettingsPageView.swift in Sources */, - 587B9E9A29301D8F00AC7927 /* GitType.swift in Sources */, - B65B10F82B081A34002852CF /* SourceControlNavigatorNoRemotesView.swift in Sources */, - 58D01C97293167DC00C5B6B4 /* String+SHA256.swift in Sources */, - B6EA1FFD29DB792C001BF195 /* ThemeSettingsColorPreview.swift in Sources */, - 2806E904297958B9000040F4 /* ContributorRowView.swift in Sources */, - 6C578D8C29CD372700DC73B2 /* ExtensionCommands.swift in Sources */, - B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */, - B6EA1FF829DB78DB001BF195 /* ThemeSettingThemeRow.swift in Sources */, - 587B9E7629301D8F00AC7927 /* GitTime.swift in Sources */, - 587B9E5D29301D8F00AC7927 /* GitLabUserRouter.swift in Sources */, - 588847692992ABCA00996D95 /* Array+SortURLs.swift in Sources */, - 58822530292C280D00E83CDE /* FilterTextField.swift in Sources */, - 6C82D6B929BFE34900495C54 /* HelpCommands.swift in Sources */, - 6C147C4929A32A080089B630 /* EditorLayoutView.swift in Sources */, - 6C147C4129A328BF0089B630 /* EditorLayout.swift in Sources */, - B6D7EA592971078500301FAC /* InspectorSection.swift in Sources */, - B6AB09A32AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift in Sources */, - B69BFDC72B0686910050D9A6 /* GitClient+Initiate.swift in Sources */, - 58F2EAEF292FB2B0004A9BDE /* ThemeSettingsView.swift in Sources */, - 85745D632A38F8D900089AAB /* String+HighlightOccurrences.swift in Sources */, - B6EE989027E8879A00CDD8AB /* InspectorAreaView.swift in Sources */, - 587B9DA229300ABD00AC7927 /* EffectView.swift in Sources */, - 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */, - 284DC84F2978B7B400BF2770 /* ContributorsView.swift in Sources */, - B62AEDC92A2704F3009A9F52 /* UtilityAreaTabView.swift in Sources */, - 58798250292E78D80085B254 /* CodeFileDocument.swift in Sources */, - 5878DAA5291AE76700DD95A3 /* QuickOpenView.swift in Sources */, - 201169D72837B2E300F92B46 /* SourceControlNavigatorView.swift in Sources */, - B6F0517929D9E3C900D72287 /* SourceControlGitView.swift in Sources */, - 587B9E8329301D8F00AC7927 /* GitHubPullRequest.swift in Sources */, - 5878DA82291863F900DD95A3 /* AcknowledgementsView.swift in Sources */, - 587B9E8529301D8F00AC7927 /* GitHubReview.swift in Sources */, - 58D01C9A293167DC00C5B6B4 /* CodeEditKeychain.swift in Sources */, - B62AEDAA2A1FCBE5009A9F52 /* AreaTabBar.swift in Sources */, - 20D839AB280DEB2900B27357 /* NoSelectionInspectorView.swift in Sources */, - 587B9E5A29301D8F00AC7927 /* GitCloneView.swift in Sources */, - B65B10F52B081A0C002852CF /* SourceControlAddRemoteView.swift in Sources */, - 58D01C99293167DC00C5B6B4 /* String+MD5.swift in Sources */, - 20EBB505280C329800F3A5DA /* CommitListItemView.swift in Sources */, - 5878DAB2291D627C00DD95A3 /* EditorPathBarView.swift in Sources */, - 04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */, - 6C6BD70129CD172700235D17 /* ExtensionsListView.swift in Sources */, - 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */, - 58F2EAEC292FB2B0004A9BDE /* IgnoredFiles.swift in Sources */, - 6CD03B6A29FC773F001BD1D0 /* SettingsInjector.swift in Sources */, - 58798236292E30B90085B254 /* FeedbackType.swift in Sources */, - 587B9E6D29301D8F00AC7927 /* GitLabEventNote.swift in Sources */, - 587B9E9129301D8F00AC7927 /* BitBucketOAuthRouter.swift in Sources */, - B6E41C7429DD40010088F9F4 /* View+HideSidebarToggle.swift in Sources */, - 611191FA2B08CC9000D4459B /* SearchIndexer.swift in Sources */, - 58822532292C280D00E83CDE /* UtilityAreaViewModel.swift in Sources */, - 043BCF03281DA18A000AC47C /* WorkspaceDocument+SearchState.swift in Sources */, - 58822527292C280D00E83CDE /* StatusBarIndentSelector.swift in Sources */, - 587B9E9629301D8F00AC7927 /* BitBucketRepositories.swift in Sources */, - 5878DAA6291AE76700DD95A3 /* QuickOpenPreviewView.swift in Sources */, - 58798286292ED0FB0085B254 /* SwiftTerm+Color+Init.swift in Sources */, - 6CFF967C29BEBD5200182D6F /* WindowCommands.swift in Sources */, - 587B9E7229301D8F00AC7927 /* GitJSONPostRouter.swift in Sources */, - 5878DAB0291D627C00DD95A3 /* EditorPathBarMenu.swift in Sources */, - 04BA7C242AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift in Sources */, - 587B9DA529300ABD00AC7927 /* PressActionsModifier.swift in Sources */, - 6C147C4029A328BC0089B630 /* SplitViewData.swift in Sources */, - 6C1CC9982B1E770B0002349B /* AsyncFileIterator.swift in Sources */, - 587B9E9029301D8F00AC7927 /* BitBucketTokenRouter.swift in Sources */, - B6C6A42E29771A8D00A3D28F /* EditorTabButtonStyle.swift in Sources */, - 58822525292C280D00E83CDE /* StatusBarMenuStyle.swift in Sources */, - 6C147C4229A328C10089B630 /* Editor.swift in Sources */, - B6C4F2A32B3CA74800B2B140 /* CommitDetailsView.swift in Sources */, - 6C2C155829B4F49100EA60A5 /* SplitViewItem.swift in Sources */, - 6CDA84AD284C1BA000C1CC3A /* EditorTabBarContextMenu.swift in Sources */, - 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */, - 613899BC2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift in Sources */, - 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */, - 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, - 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */, - 5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */, - 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */, - 58F2EB0B292FB2B0004A9BDE /* AccountsSettings.swift in Sources */, - 5882252A292C280D00E83CDE /* StatusBarToggleUtilityAreaButton.swift in Sources */, - B6AB09A12AAABAAE0003A3A6 /* EditorTabs.swift in Sources */, - 04C3255B2801F86400C8DA2D /* ProjectNavigatorViewController.swift in Sources */, - 587B9E6029301D8F00AC7927 /* GitLabOAuthRouter.swift in Sources */, - B6AB09B32AB919CF0003A3A6 /* View+actionBar.swift in Sources */, - 6C05A8AF284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift in Sources */, - 588847632992A2A200996D95 /* CEWorkspaceFile.swift in Sources */, - 6C2C155D29B4F4E500EA60A5 /* SplitViewReader.swift in Sources */, - B65B10EF2B07C454002852CF /* GitClient+Remote.swift in Sources */, - 58AFAA2F2933C69E00482B53 /* EditorItemID.swift in Sources */, - 6C4104E3297C87A000F472BA /* BlurButtonStyle.swift in Sources */, + 30F67AD52BAF76A200AB0168 /* ServiceContainer.swift in Sources */, + 30F67ADC2BAF76A200AB0168 /* Bundle+Info.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3639,11 +930,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 2BE487F328245162003F3F64 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 2BE487EB28245162003F3F64 /* OpenWithCodeEdit */; - targetProxy = 2BE487F228245162003F3F64 /* PBXContainerItemProxy */; - }; 6C7B1C762A1D57CE005CBBFC /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 6C7B1C752A1D57CE005CBBFC /* SwiftLint */; @@ -3739,11 +1025,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 28052DFC29730DF600F4F90A /* Alpha.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "${CE_APPICON_NAME}"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -3767,10 +1053,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_DOCUMENTATION_COMPILER = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Alpha; }; @@ -3929,11 +1218,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 28052DFD29730E0300F4F90A /* Beta.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "${CE_APPICON_NAME}"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -3957,10 +1246,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_DOCUMENTATION_COMPILER = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Beta; }; @@ -4186,12 +1478,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 28052DFC29730DF600F4F90A /* Alpha.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "${CE_APPICON_NAME}"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CE_APPICON_NAME = AppIconPre; CODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -4215,10 +1507,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_DOCUMENTATION_COMPILER = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Pre; }; @@ -4445,11 +1740,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 28052DFB29730DE300F4F90A /* Debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "${CE_APPICON_NAME}"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements; CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; @@ -4472,10 +1767,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_DOCUMENTATION_COMPILER = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -4483,11 +1781,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 28052DFE29730E0B00F4F90A /* Release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "${CE_APPICON_NAME}"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = CodeEdit/CodeEdit.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -4511,10 +1809,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_DOCUMENTATION_COMPILER = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; @@ -4695,14 +1996,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference "CodeEditSymbols" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/CodeEditApp/CodeEditSymbols"; - requirement = { - kind = exactVersion; - version = 0.2.2; - }; - }; 287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference "SwiftLintPlugin" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lukepistrol/SwiftLintPlugin"; @@ -4711,162 +2004,14 @@ minimumVersion = 0.2.2; }; }; - 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; - requirement = { - kind = upToNextMinorVersion; - minimumVersion = 1.14.2; - }; - }; - 58798288292ED15F0085B254 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/migueldeicaza/SwiftTerm.git"; - requirement = { - kind = exactVersion; - version = 1.2.0; - }; - }; - 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sparkle-project/Sparkle.git"; - requirement = { - kind = exactVersion; - version = 2.3.0; - }; - }; - 6C0F3A3A2A1D0D5000223D19 /* XCRemoteSwiftPackageReference "CodeEditKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/CodeEditApp/CodeEditKit"; - requirement = { - kind = exactVersion; - version = 0.1.1; - }; - }; - 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-collections.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; - 6C21493F2A1BB9AB00748382 /* XCRemoteSwiftPackageReference "LogStream" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/CodeEditApp/LogStream"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; - 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference "GRDB.swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/groue/GRDB.swift.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.2.0; - }; - }; - 6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference "collectionconcurrencykit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/johnsundell/collectionconcurrencykit"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.2.0; - }; - }; - 6CBE1CFE2B720565003AC32E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.7.2; - }; - }; - 6CDEFC9429E22C2700B7C684 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.2.3; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 2816F593280CF50500DD548B /* CodeEditSymbols */ = { - isa = XCSwiftPackageProductDependency; - package = 2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference "CodeEditSymbols" */; - productName = CodeEditSymbols; - }; - 583E529B29361BAB001AB554 /* SnapshotTesting */ = { - isa = XCSwiftPackageProductDependency; - package = 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; - productName = SnapshotTesting; - }; - 58798289292ED15F0085B254 /* SwiftTerm */ = { - isa = XCSwiftPackageProductDependency; - package = 58798288292ED15F0085B254 /* XCRemoteSwiftPackageReference "SwiftTerm" */; - productName = SwiftTerm; - }; - 58F2EB1D292FB954004A9BDE /* Sparkle */ = { - isa = XCSwiftPackageProductDependency; - package = 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */; - productName = Sparkle; - }; - 6C0F3A3B2A1D0D5000223D19 /* CodeEditKit */ = { - isa = XCSwiftPackageProductDependency; - package = 6C0F3A3A2A1D0D5000223D19 /* XCRemoteSwiftPackageReference "CodeEditKit" */; - productName = CodeEditKit; - }; - 6C147C4429A329350089B630 /* OrderedCollections */ = { - isa = XCSwiftPackageProductDependency; - package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; - productName = OrderedCollections; - }; - 6C2149402A1BB9AB00748382 /* LogStream */ = { - isa = XCSwiftPackageProductDependency; - package = 6C21493F2A1BB9AB00748382 /* XCRemoteSwiftPackageReference "LogStream" */; - productName = LogStream; - }; - 6C66C31229D05CDC00DE9ED2 /* GRDB */ = { - isa = XCSwiftPackageProductDependency; - package = 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference "GRDB.swift" */; - productName = GRDB; - }; - 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */ = { - isa = XCSwiftPackageProductDependency; - package = 6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference "collectionconcurrencykit" */; - productName = CollectionConcurrencyKit; - }; - 6C6BD6F729CD14D100235D17 /* CodeEditKit */ = { - isa = XCSwiftPackageProductDependency; - productName = CodeEditKit; - }; 6C7B1C752A1D57CE005CBBFC /* SwiftLint */ = { isa = XCSwiftPackageProductDependency; package = 287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference "SwiftLintPlugin" */; productName = "plugin:SwiftLint"; }; - 6C81916A29B41DD300B75C92 /* DequeModule */ = { - isa = XCSwiftPackageProductDependency; - package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; - productName = DequeModule; - }; - 6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */ = { - isa = XCSwiftPackageProductDependency; - productName = CodeEditSourceEditor; - }; - 6CBE1CFF2B720565003AC32E /* CodeEditSourceEditor */ = { - isa = XCSwiftPackageProductDependency; - package = 6CBE1CFE2B720565003AC32E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */; - productName = CodeEditSourceEditor; - }; - 6CDEFC9529E22C2700B7C684 /* Introspect */ = { - isa = XCSwiftPackageProductDependency; - package = 6CDEFC9429E22C2700B7C684 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = Introspect; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */; diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 14106a2645..0000000000 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,203 +0,0 @@ -{ - "pins" : [ - { - "identity" : "anycodable", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Flight-School/AnyCodable", - "state" : { - "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05", - "version" : "0.6.7" - } - }, - { - "identity" : "codeeditkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditKit", - "state" : { - "revision" : "bf76c0240589a38b504a703363ab85fcedfbc732", - "version" : "0.1.1" - } - }, - { - "identity" : "codeeditlanguages", - "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditLanguages.git", - "state" : { - "revision" : "620b463c88894741e20d4711c9435b33547de5d2", - "version" : "0.1.18" - } - }, - { - "identity" : "codeeditsourceeditor", - "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditSourceEditor", - "state" : { - "revision" : "7360f00bf7ec8e93b4833357bd254bef7e5c943d", - "version" : "0.7.2" - } - }, - { - "identity" : "codeeditsymbols", - "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditSymbols", - "state" : { - "revision" : "a794528172314f9be5d838f8579c4435895e0988", - "version" : "0.2.2" - } - }, - { - "identity" : "codeedittextview", - "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditTextView.git", - "state" : { - "revision" : "86b980464bcb67693e2053283c7a99bdc6f358bc", - "version" : "0.7.3" - } - }, - { - "identity" : "collectionconcurrencykit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/johnsundell/collectionconcurrencykit", - "state" : { - "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", - "version" : "0.2.0" - } - }, - { - "identity" : "concurrencyplus", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ChimeHQ/ConcurrencyPlus", - "state" : { - "revision" : "8dc56499412a373d617d50d059116bccf44b9874", - "version" : "0.4.2" - } - }, - { - "identity" : "grdb.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/groue/GRDB.swift.git", - "state" : { - "revision" : "dd7e7f39e8e4d7a22d258d9809a882f914690b01", - "version" : "5.26.1" - } - }, - { - "identity" : "logstream", - "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/LogStream", - "state" : { - "revision" : "afd2422c65c12822f26606408b4e39b9549c5e1a", - "version" : "1.2.1" - } - }, - { - "identity" : "mainoffender", - "kind" : "remoteSourceControl", - "location" : "https://github.com/mattmassicotte/MainOffender", - "state" : { - "revision" : "8de872d9256ff7f9913cbc5dd560568ab164be45", - "version" : "0.2.1" - } - }, - { - "identity" : "rearrange", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ChimeHQ/Rearrange", - "state" : { - "revision" : "5ff7f3363f7a08f77e0d761e38e6add31c2136e1", - "version" : "1.8.1" - } - }, - { - "identity" : "sparkle", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparkle-project/Sparkle.git", - "state" : { - "revision" : "2a98381dfe72e24bf593c5c06d2c4fc1763c3f19", - "version" : "2.3.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version" : "1.1.0" - } - }, - { - "identity" : "swift-snapshot-testing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", - "state" : { - "revision" : "bb0ea08db8e73324fe6c3727f755ca41a23ff2f4", - "version" : "1.14.2" - } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", - "state" : { - "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", - "version" : "509.1.1" - } - }, - { - "identity" : "swiftlintplugin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/lukepistrol/SwiftLintPlugin", - "state" : { - "revision" : "ea6d3ca895b49910f790e98e4b4ca658e0fe490e", - "version" : "0.54.0" - } - }, - { - "identity" : "swiftterm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/migueldeicaza/SwiftTerm.git", - "state" : { - "revision" : "55e7cdbeb3f41c80cce7b8a29ce9d17e214b2e77", - "version" : "1.2.0" - } - }, - { - "identity" : "swifttreesitter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ChimeHQ/SwiftTreeSitter.git", - "state" : { - "revision" : "2599e95310b3159641469d8a21baf2d3d200e61f", - "version" : "0.8.0" - } - }, - { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect", - "state" : { - "revision" : "121c146fe591b1320238d054ae35c81ffa45f45a", - "version" : "0.12.0" - } - }, - { - "identity" : "textformation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ChimeHQ/TextFormation", - "state" : { - "revision" : "f6faed6abd768ae95b70d10113d4008a7cac57a7", - "version" : "0.8.2" - } - }, - { - "identity" : "textstory", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ChimeHQ/TextStory", - "state" : { - "revision" : "8883fa739aa213e70e6cb109bfbf0a0b551e4cb5", - "version" : "0.8.0" - } - } - ], - "version" : 2 -} diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift deleted file mode 100644 index f998f74b2b..0000000000 --- a/CodeEdit/AppDelegate.swift +++ /dev/null @@ -1,236 +0,0 @@ -// -// AppDelegate.swift -// CodeEdit -// -// Created by Pavel Kasila on 12.03.22. -// - -import SwiftUI -import CodeEditSourceEditor -import CodeEditSymbols - -final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { - private let updater = SoftwareUpdater() - - @Environment(\.openWindow) - private var openWindow - - func applicationDidFinishLaunching(_ notification: Notification) { - enableWindowSizeSaveOnQuit() - Settings.shared.preferences.general.appAppearance.applyAppearance() - checkForFilesToOpen() - - NSApp.closeWindow(.welcome, .about) - - DispatchQueue.main.async { - var needToHandleOpen = true - - // If no windows were reopened by NSQuitAlwaysKeepsWindows, do default behavior. - // Non-WindowGroup SwiftUI Windows are still in NSApp.windows when they are closed, - // So we need to think about those. - if NSApp.windows.count > NSApp.openSwiftUIWindows { - needToHandleOpen = false - } - - for index in 0.. Bool { - true - } - - func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - if flag { - return false - } - - handleOpen() - - return false - } - - func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { - false - } - - func handleOpen() { - let behavior = Settings.shared.preferences.general.reopenBehavior - switch behavior { - case .welcome: - if !tryFocusWindow(id: .welcome) { - openWindow(sceneID: .welcome) - } - case .openPanel: - CodeEditDocumentController.shared.openDocument(self) - case .newDocument: - CodeEditDocumentController.shared.newDocument(self) - } - } - - /// Handle urls with the form `codeedit://file/{filepath}:{line}:{column}` - func application(_ application: NSApplication, open urls: [URL]) { - for url in urls { - let file = URL(fileURLWithPath: url.path).path.split(separator: ":") - let filePath = URL(fileURLWithPath: String(file[0])) - let line = file.count > 1 ? Int(file[1]) ?? 0 : 0 - let column = file.count > 2 ? Int(file[2]) ?? 1 : 1 - - CodeEditDocumentController.shared - .openDocument(withContentsOf: filePath, display: true) { document, _, error in - if let error { - NSAlert(error: error).runModal() - return - } - if line > 0, let document = document as? CodeFileDocument { - document.openOptions = CodeFileDocument.OpenOptions( - cursorPositions: [CursorPosition(line: line, column: column > 0 ? column : 1)] - ) - } - } - } - } - - func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { - let projects: [String] = CodeEditDocumentController.shared.documents - .map { doc in - (doc as? WorkspaceDocument)?.fileURL?.path - } - .filter { $0 != nil } - .map { $0! } - - UserDefaults.standard.set(projects, forKey: AppDelegate.recoverWorkspacesKey) - - let areAllDocumentsClean = CodeEditDocumentController.shared.documents.allSatisfy { !$0.isDocumentEdited } - guard areAllDocumentsClean else { - CodeEditDocumentController.shared.closeAllDocuments( - withDelegate: self, - didCloseAllSelector: #selector(documentController(_:didCloseAll:contextInfo:)), - contextInfo: nil - ) - return .terminateLater - } - - return .terminateNow - } - - func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - false - } - - // MARK: - Open windows - - @IBAction private func openWelcome(_ sender: Any) { - openWindow(sceneID: .welcome) - } - - @IBAction private func openAbout(_ sender: Any) { - openWindow(sceneID: .about) - } - - @IBAction func openFeedback(_ sender: Any) { - if tryFocusWindow(of: FeedbackView.self) { return } - - FeedbackView().showWindow() - } - - @IBAction private func checkForUpdates(_ sender: Any) { - updater.checkForUpdates() - } - - /// Tries to focus a window with specified view content type. - /// - Parameter type: The type of viewContent which hosted in a window to be focused. - /// - Returns: `true` if window exist and focused, otherwise - `false` - private func tryFocusWindow(of type: T.Type) -> Bool { - guard let window = NSApp.windows.filter({ ($0.contentView as? NSHostingView) != nil }).first - else { return false } - - window.makeKeyAndOrderFront(self) - return true - } - - /// Tries to focus a window with specified sceneId - /// - Parameter type: Id of a window to be focused. - /// - Returns: `true` if window exist and focused, otherwise - `false` - private func tryFocusWindow(id: SceneID) -> Bool { - guard let window = NSApp.windows.filter({ $0.identifier?.rawValue == id.rawValue }).first - else { return false } - - window.makeKeyAndOrderFront(self) - return true - } - - // MARK: - Open With CodeEdit (Extension) functions - private func checkForFilesToOpen() { - guard let defaults = UserDefaults.init( - suiteName: "app.codeedit.CodeEdit.shared" - ) else { - print("Failed to get/init shared defaults") - return - } - - // Register enableOpenInCE (enable Open In CodeEdit - defaults.register(defaults: ["enableOpenInCE": true]) - - if let filesToOpen = defaults.string(forKey: "openInCEFiles") { - let files = filesToOpen.split(separator: ";") - - for filePath in files { - let fileURL = URL(fileURLWithPath: String(filePath)) - CodeEditDocumentController.shared.reopenDocument( - for: fileURL, - withContentsOf: fileURL, - display: true - ) { document, _, _ in - document?.windowControllers.first?.synchronizeWindowTitleWithDocumentName() - } - } - - defaults.removeObject(forKey: "openInCEFiles") - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.checkForFilesToOpen() - } - } - - /// Enable window size restoring on app relaunch after quitting. - private func enableWindowSizeSaveOnQuit() { - // This enables window restoring on normal quit (instead of only on force-quit). - UserDefaults.standard.setValue(true, forKey: "NSQuitAlwaysKeepsWindows") - } - - // MARK: NSDocumentController delegate - - @objc - func documentController(_ docController: NSDocumentController, didCloseAll: Bool, contextInfo: Any) { - NSApplication.shared.reply(toApplicationShouldTerminate: didCloseAll) - } -} - -extension AppDelegate { - static let recoverWorkspacesKey = "recover.workspaces" -} diff --git a/CodeEdit/CodeEditApp.swift b/CodeEdit/CodeEditApp.swift index 9cc4626d47..4c260936ae 100644 --- a/CodeEdit/CodeEditApp.swift +++ b/CodeEdit/CodeEditApp.swift @@ -8,34 +8,21 @@ import SwiftUI @main -struct CodeEditApp: App { - @NSApplicationDelegateAdaptor var appdelegate: AppDelegate - @ObservedObject var settings = Settings.shared - - @Environment(\.openWindow) - var openWindow - - let updater: SoftwareUpdater = SoftwareUpdater() - +struct CodeEdit: App { init() { - _ = CodeEditDocumentController.shared - NSMenuItem.swizzle() - NSSplitViewItem.swizzle() + setupServiceContainer() } var body: some Scene { - Group { - WelcomeWindow() - - ExtensionManagerWindow() - - AboutWindow() + WelcomeScene() + } +} - SettingsWindow() - .commands { - CodeEditCommands() - } - } - .environment(\.settings, settings.preferences) // Add settings to each window environment +private extension CodeEdit { + func setupServiceContainer() { + ServiceContainer.register( + type: PasteboardService.self, + PasteboardService() + ) } } diff --git a/CodeEdit/CodeEditShared/Components/EffectView.swift b/CodeEdit/CodeEditShared/Components/EffectView.swift new file mode 100644 index 0000000000..b70d7759e1 --- /dev/null +++ b/CodeEdit/CodeEditShared/Components/EffectView.swift @@ -0,0 +1,122 @@ +import SwiftUI + +#if os(macOS) +import AppKit +#elseif os(iOS) +import UIKit +#endif + +struct EffectView: View { + private let material: Material + private let blendingMode: BlendingMode + private let emphasized: Bool + + init( + _ material: Material = .headerView, + blendingMode: BlendingMode = .withinWindow, + emphasized: Bool = false + ) { + self.material = material + self.blendingMode = blendingMode + self.emphasized = emphasized + } + + var body: some View { + #if os(macOS) + NSViewEffectWrapper(material: material.nsMaterial, blendingMode: blendingMode.nsBlendingMode, emphasized: emphasized) + #elseif os(iOS) + UIViewEffectWrapper(style: material.uiBlurEffectStyle, emphasized: emphasized) + #endif + } + + // Mapping enum for multiplatform support + enum Material { + case headerView, selection, underWindowBackground + + #if os(macOS) + var nsMaterial: NSVisualEffectView.Material { + switch self { + case .headerView: return .headerView + case .selection: return .selection + case .underWindowBackground: return .underWindowBackground + } + } + #elseif os(iOS) + var uiBlurEffectStyle: UIBlurEffect.Style { + switch self { + case .headerView, .underWindowBackground: return .systemMaterial + case .selection: return .dark // TODO: Example mapping, adjust as needed + } + } + #endif + } + + enum BlendingMode { + case withinWindow, behindWindow + + #if os(macOS) + var nsBlendingMode: NSVisualEffectView.BlendingMode { + // macOS specific mapping + switch self { + case .withinWindow: return .withinWindow + case .behindWindow: return .behindWindow + } + } + #endif + } + + #if os(macOS) + // macOS specific wrapper + struct NSViewEffectWrapper: NSViewRepresentable { + var material: NSVisualEffectView.Material + var blendingMode: NSVisualEffectView.BlendingMode + var emphasized: Bool + + func makeNSView(context: Context) -> NSVisualEffectView { + let view = NSVisualEffectView() + view.material = material + view.blendingMode = blendingMode + view.isEmphasized = emphasized + view.state = .followsWindowActiveState + return view + } + + func updateNSView(_ nsView: NSVisualEffectView, context: Context) { + nsView.material = material + nsView.blendingMode = blendingMode + nsView.isEmphasized = emphasized + } + } + #elseif os(iOS) + // iOS specific wrapper + struct UIViewEffectWrapper: UIViewRepresentable { + var style: UIBlurEffect.Style + var emphasized: Bool // This might be used to adjust the effect based on context + + func makeUIView(context: Context) -> UIVisualEffectView { + let effect = UIBlurEffect(style: style) + return UIVisualEffectView(effect: effect) + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + uiView.effect = UIBlurEffect(style: style) + } + } + #endif +} + +extension EffectView { + /// Returns the system selection style as an ``EffectView`` if the `condition` is met. + /// Otherwise it returns `Color.clear` + /// + /// - Parameter condition: The condition of when to apply the background. Defaults to `true`. + /// - Returns: A View + @ViewBuilder + static func selectionBackground(_ condition: Bool = true) -> some View { + if condition { + EffectView(.selection, blendingMode: .withinWindow, emphasized: true) + } else { + Color.clear + } + } +} diff --git a/CodeEdit/CodeEditShared/Models/RecentProject.swift b/CodeEdit/CodeEditShared/Models/RecentProject.swift new file mode 100644 index 0000000000..57cbfbde45 --- /dev/null +++ b/CodeEdit/CodeEditShared/Models/RecentProject.swift @@ -0,0 +1,17 @@ +// +// RecentProject.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/19/24. +// + +import Foundation + +import Foundation + +struct RecentProject: Identifiable, Codable { + var id: UUID = UUID() + var name: String + var path: String + var lastOpened: Date +} diff --git a/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceContainer.swift b/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceContainer.swift new file mode 100644 index 0000000000..46a51924b7 --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceContainer.swift @@ -0,0 +1,44 @@ +// +// ServiceContainer.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/20/24. +// + +final class ServiceContainer { + private static var factories: [String: () -> Any] = [:] + private static var cache: [String: Any] = [:] + + static func register(type: Service.Type, _ factory: @autoclosure @escaping () -> Service) { + factories[String(describing: type.self)] = factory + } + + static func resolve(_ resolveType: ServiceType = .singleton, _ type: Service.Type) -> Service? { + let serviceName = String(describing: type.self) + + switch resolveType { + case .singleton: + if let service = cache[serviceName] as? Service { + return service + } else { + let service = factories[serviceName]?() as? Service + + if let service = service { + cache[serviceName] = service + } + + return service + } + case .newSingleton: + let service = factories[serviceName]?() as? Service + + if let service = service { + cache[serviceName] = service + } + + return service + case .new: + return factories[serviceName]?() as? Service + } + } +} diff --git a/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceTypes.swift b/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceTypes.swift new file mode 100644 index 0000000000..0d256e5a4f --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceTypes.swift @@ -0,0 +1,15 @@ +// +// ServiceTypes.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/20/24. +// + +enum ServiceType { + /// Returns a new singleton on the first call, then returns a cached one every other time + case singleton + /// Creates a new singleton reference each time and caches it, returning the newer singleton + case newSingleton + /// Creates a new singleton + case new +} diff --git a/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceWrapper.swift b/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceWrapper.swift new file mode 100644 index 0000000000..e942f04b8e --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/DependencyInjection/ServiceWrapper.swift @@ -0,0 +1,26 @@ +// +// ServiceWrapper.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/20/24. +// + +@propertyWrapper +struct Service { + + var service: Service + + init(_ type: ServiceType = .singleton) { + guard let service = ServiceContainer.resolve(type, Service.self) else { + let serviceName = String(describing: Service.self) + fatalError("No service of type \(serviceName) registered!") + } + + self.service = service + } + + var wrappedValue: Service { + get { self.service } + mutating set { service = newValue } + } +} diff --git a/CodeEdit/CodeEditShared/Services/DocumentService/DocumentService.swift b/CodeEdit/CodeEditShared/Services/DocumentService/DocumentService.swift new file mode 100644 index 0000000000..22cca1f5e2 --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/DocumentService/DocumentService.swift @@ -0,0 +1,14 @@ +// +// DocumentService.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/21/24. +// + +import Foundation + +protocol DocumentProvider { + static func newDocument() + static func openDocument() + static func openDocument(completion: @escaping (Bool, Error?) -> Void) +} diff --git a/CodeEdit/CodeEditShared/Services/PasteboardService/NSPasteboardProvider.swift b/CodeEdit/CodeEditShared/Services/PasteboardService/NSPasteboardProvider.swift new file mode 100644 index 0000000000..f0530b0795 --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/PasteboardService/NSPasteboardProvider.swift @@ -0,0 +1,35 @@ +// +// NSPasteboardProvider.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/20/24. +// + +#if os(macOS) +import AppKit + +/// Using an enum to namespace the static functions. +/// If NSPasteboardProvider or other PasteboardProvider implementations might need to +/// hold state in the future, consider using a final class or a struct instead. +enum NSPasteboardProvider: PasteboardProvider { + @inline(__always) + static func clear() { + NSPasteboard.general.clearContents() + } + + @inline(__always) + static func string() -> String? { + return NSPasteboard.general.string(forType: .string) + } + + @inline(__always) + static func setString(_ string: String) { + NSPasteboard.general.setString(string, forType: .string) + } + + @inline(__always) + static func setStrings(_ strings: [String]) { + NSPasteboard.general.writeObjects(strings as [NSString]) + } +} +#endif diff --git a/CodeEdit/CodeEditShared/Services/PasteboardService/PasteboardService.swift b/CodeEdit/CodeEditShared/Services/PasteboardService/PasteboardService.swift new file mode 100644 index 0000000000..6e2dbfff13 --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/PasteboardService/PasteboardService.swift @@ -0,0 +1,49 @@ +// +// PasteboardService.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/20/24. +// + +protocol PasteboardProvider { + static func clear() + static func string() -> String? + static func setString(_ string: String) + static func setStrings(_ strings: [String]) +} + +/// A service for interacting with the pasteboard +final class PasteboardService { + private let pasteboard: PasteboardProvider.Type + + init() { + // Initialize a provider based on platform +#if os(macOS) + self.pasteboard = NSPasteboardProvider.self +#elseif os(iOS) + self.pasteboard = UIPasteboardProvider.self +#endif + } + + /// Clears the pasteboard + func clear() { + self.pasteboard.clear() + } + + /// Copies a string to the pasteboard + /// - Parameter string: The string to copy + func copy(_ string: String) { + self.pasteboard.setString(string) + } + + /// Copies an array of strings to the pasteboard + /// - Parameter strings: The strings to copy + func copy(_ strings: [String]) { + self.pasteboard.setStrings(strings) + } + + /// Pastes a string from the pasteboard + func paste() -> String? { + self.pasteboard.string() + } +} diff --git a/CodeEdit/CodeEditShared/Services/PasteboardService/UIPasteboardProvider.swift b/CodeEdit/CodeEditShared/Services/PasteboardService/UIPasteboardProvider.swift new file mode 100644 index 0000000000..02869e8190 --- /dev/null +++ b/CodeEdit/CodeEditShared/Services/PasteboardService/UIPasteboardProvider.swift @@ -0,0 +1,35 @@ +// +// UIPasteboardProvider.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/20/24. +// + +#if os(iOS) +import UIKit + +/// Using an enum to namespace the static functions. +/// If UIPasteboardProvider or other PasteboardProvider implementations might need to +/// hold state in the future, consider using a final class or a struct instead. +enum UIPasteboardProvider: PasteboardProvider { + @inline(__always) + static func clear() { + UIPasteboard.general.string = nil + } + + @inline(__always) + static func string() -> String? { + return UIPasteboard.general.string + } + + @inline(__always) + static func setString(_ string: String) { + UIPasteboard.general.string = string + } + + @inline(__always) + static func setStrings(_ strings: [String]) { + UIPasteboard.general.strings = strings + } +} +#endif diff --git a/CodeEdit/CodeEditShared/Utilities/Extensions/Bundle/Bundle+Info.swift b/CodeEdit/CodeEditShared/Utilities/Extensions/Bundle/Bundle+Info.swift new file mode 100644 index 0000000000..3252718cbc --- /dev/null +++ b/CodeEdit/CodeEditShared/Utilities/Extensions/Bundle/Bundle+Info.swift @@ -0,0 +1,83 @@ +// +// Bundle+Info.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/21/24. +// + +#if os(iOS) +import UIKit +#elseif os(macOS) +import Foundation +#endif + +extension Bundle { + + /// Returns the version and build of the platform + static var systemVersionBuild: String { + #if os(iOS) + // Example output: "17.0" + return UIDevice.current.systemVersion + #elseif os(macOS) + let url = URL(fileURLWithPath: "/System/Library/CoreServices/SystemVersion.plist") + guard let dict = NSDictionary(contentsOf: url), + let version = dict["ProductUserVisibleVersion"], + let build = dict["ProductBuildVersion"] else { + return ProcessInfo.processInfo.operatingSystemVersionString + } + // Example output: "14.4 (23E214)"" + return "\(version) (\(build))" + #else + // Example output: "Version 14.4 (Build 23E214)"" + return ProcessInfo.processInfo.operatingSystemVersionString + #endif + } + + /// Returns the version of the system, including the patch version (e.g. 14.4.0) + static var systemVersion: String { + let version = ProcessInfo.processInfo.operatingSystemVersion + return "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + } + + /// Returns the name of the operating system (e.g. iOS, macOS, Web) + static var systemName: String { + #if os(iOS) + return UIDevice.current.systemName + #elseif os(macOS) + return "macOS" + #elseif os(WASI) + return "Web" + #endif + } + + /// Returns the platform name (e.g. iPhone, iPad, Mac) + static var deviceName: String { + #if os(iOS) + return UIDevice.current.model + #elseif os(macOS) + return "Mac" + #elseif os(WASI) + return "Web" + #endif + } + + /// Returns the main bundle's name if available (e.g. CodeEdit) + static var copyrightString: String? { + Bundle.main.object(forInfoDictionaryKey: "NSHumanReadableCopyright") as? String + } + + /// Returns the main bundle's version string if available (e.g. 1.0.0) + static var appVersion: String? { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + } + + /// Returns the main bundle's build string if available (e.g. 123) + static var buildString: String? { + Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String + } + + /// Returns the version postfix if available (e.g. pre, dev, etc) + static var versionPostfix: String? { + Bundle.main.object(forInfoDictionaryKey: "CE_VERSION_POSTFIX") as? String + } +} diff --git a/CodeEdit/Utils/Extensions/NSApplication/NSApp+openWindow.swift b/CodeEdit/CodeEditShared/Utilities/Extensions/NSApplication/NSApp+openWindow.swift similarity index 91% rename from CodeEdit/Utils/Extensions/NSApplication/NSApp+openWindow.swift rename to CodeEdit/CodeEditShared/Utilities/Extensions/NSApplication/NSApp+openWindow.swift index a348111363..f7fc4d3141 100644 --- a/CodeEdit/Utils/Extensions/NSApplication/NSApp+openWindow.swift +++ b/CodeEdit/CodeEditShared/Utilities/Extensions/NSApplication/NSApp+openWindow.swift @@ -1,10 +1,11 @@ // // NSApp+openWindow.swift -// CodeEdit +// CodeEditV2 // -// Created by Wouter Hennen on 14/03/2023. +// Created by Abe Malla on 3/21/24. // +#if os(macOS) import AppKit import SwiftUI @@ -37,3 +38,4 @@ extension NSApplication { .count } } +#endif diff --git a/CodeEdit/CodeEditShared/Utilities/ProjectManager.swift b/CodeEdit/CodeEditShared/Utilities/ProjectManager.swift new file mode 100644 index 0000000000..b5d2ee6b46 --- /dev/null +++ b/CodeEdit/CodeEditShared/Utilities/ProjectManager.swift @@ -0,0 +1,20 @@ +// +// ProjectManager.swift +// CodeEditV2 +// +// Created by Abe Malla on 3/19/24. +// + +import Foundation + +class ProjectManager { + static let shared = ProjectManager() + + func getRecentProjects() -> [RecentProject] { + // TODO: Placeholder implementation + return [ + RecentProject(name: "Project 1", path: "/path/to/project1", lastOpened: Date()), + RecentProject(name: "Project 2", path: "/path/to/project2", lastOpened: Date().addingTimeInterval(-86400)) + ] + } +} diff --git a/CodeEdit/SceneID.swift b/CodeEdit/CodeEditShared/Utilities/SceneID.swift similarity index 73% rename from CodeEdit/SceneID.swift rename to CodeEdit/CodeEditShared/Utilities/SceneID.swift index 3e213ab628..6e24b6d341 100644 --- a/CodeEdit/SceneID.swift +++ b/CodeEdit/CodeEditShared/Utilities/SceneID.swift @@ -1,8 +1,8 @@ // // SceneID.swift -// CodeEdit +// CodeEditV2 // -// Created by Wouter Hennen on 15/03/2023. +// Created by Abe Malla on 3/19/24. // import Foundation diff --git a/CodeEdit/Features/Welcome/Views/RecentProjectItem.swift b/CodeEdit/CodeEditShared/Views/Welcome/Components/RecentProjectItem.swift similarity index 82% rename from CodeEdit/Features/Welcome/Views/RecentProjectItem.swift rename to CodeEdit/CodeEditShared/Views/Welcome/Components/RecentProjectItem.swift index 73b6508fa2..72fa9c1621 100644 --- a/CodeEdit/Features/Welcome/Views/RecentProjectItem.swift +++ b/CodeEdit/CodeEditShared/Views/Welcome/Components/RecentProjectItem.swift @@ -22,10 +22,10 @@ struct RecentProjectItem: View { var body: some View { HStack(spacing: 8) { - Image(nsImage: NSWorkspace.shared.icon(forFile: projectPath.path(percentEncoded: false))) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 32, height: 32) +// Image(nsImage: NSWorkspace.shared.icon(forFile: projectPath.path(percentEncoded: false))) +// .resizable() +// .aspectRatio(contentMode: .fit) +// .frame(width: 32, height: 32) VStack(alignment: .leading) { Text(projectPath.lastPathComponent) .foregroundColor(.primary) diff --git a/CodeEdit/Features/Welcome/Views/RecentProjectsListView.swift b/CodeEdit/CodeEditShared/Views/Welcome/Components/RecentProjectsListView.swift similarity index 94% rename from CodeEdit/Features/Welcome/Views/RecentProjectsListView.swift rename to CodeEdit/CodeEditShared/Views/Welcome/Components/RecentProjectsListView.swift index 084b1231a3..94ae438b9c 100644 --- a/CodeEdit/Features/Welcome/Views/RecentProjectsListView.swift +++ b/CodeEdit/CodeEditShared/Views/Welcome/Components/RecentProjectsListView.swift @@ -9,6 +9,8 @@ import SwiftUI struct RecentProjectsListView: View { + @Service private var pasteboardService: PasteboardService + @State private var selection: Set @State var recentProjects: [URL] @@ -47,14 +49,14 @@ struct RecentProjectsListView: View { case 0: EmptyView() default: +#if os(macOS) Button("Show in Finder") { NSWorkspace.shared.activateFileViewerSelecting(Array(items)) } - +#endif Button("Copy path\(items.count > 1 ? "s" : "")") { - let pasteBoard = NSPasteboard.general - pasteBoard.clearContents() - pasteBoard.writeObjects(selection.map(\.relativePath) as [NSString]) + pasteboardService.clear() + pasteboardService.copy(selection.map(\.relativePath)) } Button("Remove from Recents") { @@ -66,6 +68,7 @@ struct RecentProjectsListView: View { openDocument($0, dismissWindow) } } +#if os(macOS) .onCopyCommand { selection.map { NSItemProvider(object: $0.path(percentEncoded: false) as NSString) @@ -80,6 +83,7 @@ struct RecentProjectsListView: View { // Ideally, this should be 'whenever a doc opens/closes'. updateRecentProjects() } +#endif .background { Button("") { selection.forEach { diff --git a/CodeEdit/Features/Welcome/Views/WelcomeActionView.swift b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeActionView.swift similarity index 77% rename from CodeEdit/Features/Welcome/Views/WelcomeActionView.swift rename to CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeActionView.swift index 900feb6b3c..e9121fa977 100644 --- a/CodeEdit/Features/Welcome/Views/WelcomeActionView.swift +++ b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeActionView.swift @@ -39,9 +39,16 @@ struct WelcomeActionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .contentShape(Rectangle()) + #if os(iOS) + .padding(14) + .frame(height: 48) + .frame(maxWidth: 348) + .background(Color(UIColor.label).opacity(configuration.isPressed ? 0.1 : 0.05)) + #elseif os(macOS) .padding(7) .frame(height: 36) - .background(Color(.labelColor).opacity(configuration.isPressed ? 0.1 : 0.05)) + .background(Color(NSColor.labelColor).opacity(configuration.isPressed ? 0.1 : 0.05)) + #endif .cornerRadius(8) } } diff --git a/CodeEdit/Features/Welcome/Views/WelcomeWindow.swift b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeScene.swift similarity index 60% rename from CodeEdit/Features/Welcome/Views/WelcomeWindow.swift rename to CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeScene.swift index e80014e48d..01d277b482 100644 --- a/CodeEdit/Features/Welcome/Views/WelcomeWindow.swift +++ b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeScene.swift @@ -7,11 +7,13 @@ import SwiftUI -struct WelcomeWindow: Scene { +struct WelcomeScene: Scene { - @ObservedObject var settings = Settings.shared + // TODO: RE-ENABLE FOR WHEN SETTINGS IMPLEMENTED FOR IOS +// @ObservedObject var settings = Settings.shared var body: some Scene { + #if os(macOS) Window("Welcome To CodeEdit", id: SceneID.welcome.rawValue) { ContentView() .frame(width: 740, height: 432) @@ -26,6 +28,11 @@ struct WelcomeWindow: Scene { } .windowStyle(.hiddenTitleBar) .windowResizability(.contentSize) + #elseif os(iOS) + WindowGroup { + ContentView() + } + #endif } struct ContentView: View { @@ -37,20 +44,20 @@ struct WelcomeWindow: Scene { var body: some View { WelcomeWindowView { url, opened in if let url { - CodeEditDocumentController.shared.openDocument(withContentsOf: url, display: true) { doc, _, _ in - if doc != nil { - opened() - } - } +// CodeEditDocumentController.shared.openDocument(withContentsOf: url, display: true) { doc, _, _ in +// if doc != nil { +// opened() +// } +// } } else { dismiss() - CodeEditDocumentController.shared.openDocument( - onCompletion: { _, _ in opened() }, - onCancel: { openWindow(sceneID: .welcome) } - ) +// CodeEditDocumentController.shared.openDocument( +// onCompletion: { _, _ in opened() }, +// onCancel: { openWindow(sceneID: .welcome) } +// ) } } newDocument: { - CodeEditDocumentController.shared.newDocument(nil) +// CodeEditDocumentController.shared.newDocument(nil) } dismissWindow: { dismiss() } diff --git a/CodeEdit/Features/Welcome/Views/WelcomeView.swift b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeView.swift similarity index 76% rename from CodeEdit/Features/Welcome/Views/WelcomeView.swift rename to CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeView.swift index ca5357284d..8151d6ad00 100644 --- a/CodeEdit/Features/Welcome/Views/WelcomeView.swift +++ b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeView.swift @@ -6,18 +6,25 @@ // import SwiftUI -import AppKit import Foundation +#if os(macOS) +import AppKit +#endif struct WelcomeView: View { + + @Service private var pasteboardService: PasteboardService + @Environment(\.colorScheme) var colorScheme + #if os(macOS) @Environment(\.controlActiveState) var controlActiveState + #endif - @AppSettings(\.general.reopenBehavior) - var reopenBehavior +// @AppSettings(\.general.reopenBehavior) +// var reopenBehavior @State var showGitClone = false @@ -41,16 +48,8 @@ struct WelcomeView: View { self.dismissWindow = dismissWindow } - private var showWhenLaunchedBinding: Binding { - Binding { - reopenBehavior == .welcome - } set: { new in - reopenBehavior = new ? .welcome : .openPanel - } - } - private var appVersion: String { - Bundle.versionString ?? "" + Bundle.appVersion ?? "" } private var appBuild: String { @@ -61,21 +60,9 @@ struct WelcomeView: View { Bundle.versionPostfix ?? "" } - /// Get the macOS version & build - private var macOSVersion: String { - let url = URL(fileURLWithPath: "/System/Library/CoreServices/SystemVersion.plist") - guard let dict = NSDictionary(contentsOf: url), - let version = dict["ProductUserVisibleVersion"], - let build = dict["ProductBuildVersion"] - else { - return ProcessInfo.processInfo.operatingSystemVersionString - } - - return "\(version) (\(build))" - } - /// Return the Xcode version and build (if installed) private var xcodeVersion: String? { + #if os(macOS) guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.dt.Xcode"), let bundle = Bundle(url: url), let infoDict = bundle.infoDictionary, @@ -88,21 +75,22 @@ struct WelcomeView: View { } return "\(version) (\(build))" + #else + return nil + #endif } /// Get program and operating system information private func copyInformation() { var copyString = "CodeEdit: \(appVersion)\(appVersionPostfix) (\(appBuild))\n" - - copyString.append("macOS: \(macOSVersion)\n") + copyString.append("\(Bundle.systemName): \(Bundle.systemVersionBuild)\n") if let xcodeVersion { copyString.append("Xcode: \(xcodeVersion)") } - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(copyString, forType: .string) + pasteboardService.clear() + pasteboardService.copy(copyString) } var body: some View { @@ -113,24 +101,24 @@ struct WelcomeView: View { .onHover { isHovering in self.isHovering = isHovering } - .sheet(isPresented: $showGitClone) { - GitCloneView( - openBranchView: { url in - showCheckoutBranchItem = url - }, - openDocument: { url in - openDocument(url, dismissWindow) - } - ) - } - .sheet(item: $showCheckoutBranchItem, content: { repoPath in - GitCheckoutBranchView( - repoLocalPath: repoPath, - openDocument: { url in - openDocument(url, dismissWindow) - } - ) - }) +// .sheet(isPresented: $showGitClone) { +// GitCloneView( +// openBranchView: { url in +// showCheckoutBranchItem = url +// }, +// openDocument: { url in +// openDocument(url, dismissWindow) +// } +// ) +// } +// .sheet(item: $showCheckoutBranchItem, content: { repoPath in +// GitCheckoutBranchView( +// repoLocalPath: repoPath, +// openDocument: { url in +// openDocument(url, dismissWindow) +// } +// ) +// }) } private var mainContent: some View { @@ -145,9 +133,15 @@ struct WelcomeView: View { .blur(radius: 64) .opacity(0.5) } + #if os(macOS) Image(nsImage: NSApp.applicationIconImage) .resizable() .frame(width: 128, height: 128) + #else + Image("AppIcon") + .resizable() + .frame(width: 128, height: 128) + #endif } Text(NSLocalizedString("CodeEdit", comment: "")) .font(.system(size: 36, weight: .bold)) @@ -162,6 +156,7 @@ struct WelcomeView: View { .textSelection(.enabled) .foregroundColor(.secondary) .font(.system(size: 13.5)) + #if os(macOS) .onHover { hover in if hover { NSCursor.pointingHand.push() @@ -169,7 +164,9 @@ struct WelcomeView: View { NSCursor.pop() } } + #endif .onTapGesture { + // TODO: DOESNT WORK copyInformation() } .help("Copy System Information to Clipboard") @@ -177,6 +174,10 @@ struct WelcomeView: View { Spacer().frame(height: 40) HStack { VStack(alignment: .leading, spacing: 8) { + // TODO: IOS OPTIONS: + // 1. CREATE LOCAL PROJECT + // 2. CLONE GIT REPOSITORY + // 3. OPEN FILE OR FOLDER WelcomeActionView( iconName: "plus.square", title: NSLocalizedString("Create New File...", comment: ""), @@ -200,22 +201,32 @@ struct WelcomeView: View { } ) } + .frame(maxWidth: .infinity) } Spacer() } .padding(.top, 20) .padding(.horizontal, 56) .padding(.bottom, 16) + #if os(macOS) .frame(width: 460) .background( colorScheme == .dark ? Color(.black).opacity(0.2) : Color(.white).opacity(controlActiveState == .inactive ? 1.0 : 0.5) ) + #else + .background( + colorScheme == .dark + ? Color(.black).opacity(0.2) + : Color(.white).opacity(0.5) + ) + #endif .background(EffectView(.underWindowBackground, blendingMode: .behindWindow)) } private var dismissButton: some View { + #if os(macOS) Button( action: dismissWindow, label: { @@ -232,5 +243,8 @@ struct WelcomeView: View { } .padding(10) .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.25))) + #elseif os(iOS) + return EmptyView() + #endif } } diff --git a/CodeEdit/Features/Welcome/Views/WelcomeWindowView.swift b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeWindowView.swift similarity index 58% rename from CodeEdit/Features/Welcome/Views/WelcomeWindowView.swift rename to CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeWindowView.swift index 378f930849..51557ce241 100644 --- a/CodeEdit/Features/Welcome/Views/WelcomeWindowView.swift +++ b/CodeEdit/CodeEditShared/Views/Welcome/Components/WelcomeWindowView.swift @@ -33,21 +33,22 @@ struct WelcomeWindowView: View { .frame(width: 280) } .edgesIgnoringSafeArea(.top) - .onDrop(of: [.fileURL], isTargeted: .constant(true)) { providers in - NSApp.activate(ignoringOtherApps: true) - providers.forEach { - _ = $0.loadDataRepresentation(for: .fileURL) { data, _ in - if let data, let url = URL(dataRepresentation: data, relativeTo: nil) { - Task { - try? await CodeEditDocumentController - .shared - .openDocument(withContentsOf: url, display: true) - } - } - } - } - dismissWindow() - return true - } +// TODO: ENABLE +// .onDrop(of: [.fileURL], isTargeted: .constant(true)) { providers in +// NSApp.activate(ignoringOtherApps: true) +// providers.forEach { +// _ = $0.loadDataRepresentation(for: .fileURL) { data, _ in +// if let data, let url = URL(dataRepresentation: data, relativeTo: nil) { +// Task { +// try? await CodeEditDocumentController +// .shared +// .openDocument(withContentsOf: url, display: true) +// } +// } +// } +// } +// dismissWindow() +// return true +// } } } diff --git a/CodeEdit/Features/About/Views/AboutDefaultView.swift b/CodeEdit/Features/About/Views/AboutDefaultView.swift deleted file mode 100644 index 62312c51f0..0000000000 --- a/CodeEdit/Features/About/Views/AboutDefaultView.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// AboutDefaultView.swift -// CodeEdit -// -// Created by Wouter Hennen on 21/01/2023. -// - -import SwiftUI - -struct AboutDefaultView: View { - private var appVersion: String { - Bundle.versionString ?? "No Version" - } - - private var appBuild: String { - Bundle.buildString ?? "No Build" - } - - private var appVersionPostfix: String { - Bundle.versionPostfix ?? "" - } - - @Binding var aboutMode: AboutMode - var namespace: Namespace.ID - - @Environment(\.colorScheme) - var colorScheme - - private static var licenseURL = URL(string: "https://github.com/CodeEditApp/CodeEdit/blob/main/LICENSE.md")! - - let smallTitlebarHeight: CGFloat = 28 - let mediumTitlebarHeight: CGFloat = 113 - let largeTitlebarHeight: CGFloat = 231 - - var body: some View { - VStack(spacing: 0) { - Image(nsImage: NSApp.applicationIconImage) - .resizable() - .matchedGeometryEffect(id: "AppIcon", in: namespace) - .frame(width: 128, height: 128) - .padding(.top, 16) - .padding(.bottom, 8) - - VStack(spacing: 0) { - Text("CodeEdit") - .matchedGeometryEffect(id: "Title", in: namespace, properties: .position, anchor: .center) - .foregroundColor(.primary) - .font(.system( - size: 26, - weight: .bold - )) - Text("Version \(appVersion)\(appVersionPostfix) (\(appBuild))") - .textSelection(.enabled) - .foregroundColor(Color(.tertiaryLabelColor)) - .font(.body) - .blendMode(colorScheme == .dark ? .plusLighter : .plusDarker) - .padding(.top, 4) - .matchedGeometryEffect( - id: "Title", - in: namespace, - properties: .position, - anchor: UnitPoint(x: 0.5, y: -0.75) - ) - } - .padding(.horizontal) - } - .padding(24) - - VStack { - Spacer() - VStack { - Button { - aboutMode = .contributors - } label: { - Text("Contributors") - .foregroundColor(.primary) - .frame(maxWidth: .infinity) - } - .controlSize(.large) - .buttonStyle(.blur) - - Button { - aboutMode = .acknowledgements - } label: { - Text("Acknowledgements") - .foregroundColor(.primary) - .frame(maxWidth: .infinity) - } - .controlSize(.large) - .buttonStyle(.blur) - - VStack(spacing: 2) { - Link(destination: Self.licenseURL) { - Text("MIT License") - .underline() - - } - Text(Bundle.copyrightString ?? "") - } - .textSelection(.disabled) - .font(.system(size: 11, weight: .regular)) - .foregroundColor(Color(.tertiaryLabelColor)) - .blendMode(colorScheme == .dark ? .plusLighter : .plusDarker) - .padding(.top, 12) - .padding(.bottom, 24) - } - .matchedGeometryEffect(id: "Titlebar", in: namespace, properties: .position, anchor: .top) - .matchedGeometryEffect(id: "ScrollView", in: namespace, properties: .position, anchor: .top) - } - .padding(.horizontal) - } -} diff --git a/CodeEdit/Features/About/Views/AboutDetailView.swift b/CodeEdit/Features/About/Views/AboutDetailView.swift deleted file mode 100644 index 0d44d7f547..0000000000 --- a/CodeEdit/Features/About/Views/AboutDetailView.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// AboutDetailView.swift -// CodeEdit -// -// Created by Wouter Hennen on 21/01/2023. -// - -import SwiftUI - -struct AboutDetailView: View { - var title: String - - @Binding var aboutMode: AboutMode - - var namespace: Namespace.ID - - @ViewBuilder var content: Content - - let smallTitlebarHeight: CGFloat = 28 - let mediumTitlebarHeight: CGFloat = 113 - let largeTitlebarHeight: CGFloat = 231 - - var maxScrollOffset: CGFloat { - smallTitlebarHeight - mediumTitlebarHeight - } - - var currentOffset: CGFloat { - getScrollAdjustedValue( - minValue: 22, - maxValue: 14, - minOffset: 0, - maxOffset: maxScrollOffset - ) - } - - @State private var scrollOffset: CGFloat = 0 - - var body: some View { - VStack { - Spacer(minLength: smallTitlebarHeight + 1) - TrackableScrollView(showIndicators: false, contentOffset: $scrollOffset) { - Spacer(minLength: mediumTitlebarHeight - smallTitlebarHeight - 1 + 8) - content - .padding(.horizontal) - .padding(.bottom, 8) - } - .frame(maxWidth: .infinity) - .matchedGeometryEffect(id: "ScrollView", in: namespace, properties: .position, anchor: .top) - .clipShape(Rectangle()) - } - - VStack(spacing: 0) { - Image(nsImage: NSApp.applicationIconImage) - .resizable() - .matchedGeometryEffect(id: "AppIcon", in: namespace) - .frame( - width: getScrollAdjustedValue( - minValue: 48, - maxValue: 0, - minOffset: 0, - maxOffset: maxScrollOffset - ), - height: getScrollAdjustedValue( - minValue: 48, - maxValue: 0, - minOffset: 0, - maxOffset: maxScrollOffset - ) - ) - .opacity( - getScrollAdjustedValue( - minValue: 1, - maxValue: 0, - minOffset: 0, - maxOffset: maxScrollOffset - ) - ) - .padding(.top, getScrollAdjustedValue( - minValue: smallTitlebarHeight, - maxValue: 0, - minOffset: 0, - maxOffset: maxScrollOffset - )) - .padding(.bottom, getScrollAdjustedValue( - minValue: 5, - maxValue: 0, - minOffset: 0, - maxOffset: maxScrollOffset - )) - - Button { - aboutMode = .about - } label: { - Text(title) - .foregroundColor(.primary) - .font(.system( - size: getScrollAdjustedValue( - minValue: 20, - maxValue: 14, - minOffset: 0, - maxOffset: maxScrollOffset - ), - weight: .bold - )) - - .fixedSize(horizontal: true, vertical: false) - .frame(minHeight: smallTitlebarHeight) - .padding(.horizontal, 13) - .overlay(alignment: .leading) { - Image(systemName: "chevron.left") - .foregroundColor(.secondary) - .padding(.trailing) - } - .contentShape(Rectangle()) - .matchedGeometryEffect(id: "Title", in: namespace, properties: .position, anchor: .center) - } - .buttonStyle(.plain) - - Divider() - .opacity(getScrollAdjustedValue( - minValue: 0, - maxValue: 1, - minOffset: 0, - maxOffset: maxScrollOffset - )) - } - .padding(0) - .frame(maxWidth: .infinity) - .matchedGeometryEffect(id: "Titlebar", in: namespace, properties: .position, anchor: .bottom) - } - - func getScrollAdjustedValue( - minValue: CGFloat, - maxValue: CGFloat, - minOffset: CGFloat, - maxOffset: CGFloat - ) -> CGFloat { - let valueRange = maxValue - minValue - let offsetRange = maxOffset - minOffset - let currentOffset = scrollOffset - let percentage = (currentOffset - minOffset) / offsetRange - let value = minValue + (valueRange * percentage) - - if currentOffset <= maxOffset { - return maxValue - } - if value < 0 { - return 0 - } - return value - } -} diff --git a/CodeEdit/Features/About/Views/AboutView.swift b/CodeEdit/Features/About/Views/AboutView.swift deleted file mode 100644 index 900177edb9..0000000000 --- a/CodeEdit/Features/About/Views/AboutView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// AboutView.swift -// CodeEditModules/About -// -// Created by Andrei Vidrasco on 02.04.2022 -// - -import SwiftUI - -enum AboutMode: String, CaseIterable { - case about - case acknowledgements - case contributors -} - -public struct AboutView: View { - @Environment(\.openURL) - private var openURL - @Environment(\.colorScheme) - private var colorScheme - @Environment(\.dismiss) - private var dismiss - - @State var aboutMode: AboutMode = .about - - @Namespace var animator - - public var body: some View { - ZStack(alignment: .top) { - switch aboutMode { - case .about: - AboutDefaultView(aboutMode: $aboutMode, namespace: animator) - case .acknowledgements: - AcknowledgementsView(aboutMode: $aboutMode, namespace: animator) - case .contributors: - ContributorsView(aboutMode: $aboutMode, namespace: animator) - } - } - .animation(.spring(), value: aboutMode) - .ignoresSafeArea() - .frame(width: 280, height: 400 - 28) - .fixedSize() - // hack required to get buttons appearing correctly in light appearance - // if anyone knows of a better way to do this feel free to refactor - .background(.regularMaterial.opacity(0)) - .background(EffectView(.popover, blendingMode: .behindWindow).ignoresSafeArea()) - .background { - Button("") { - dismiss() - } - .keyboardShortcut(.escape, modifiers: []) - .hidden() - } - .task { - if let window = NSApp.findWindow(.about) { - window.styleMask = [.closable, .fullSizeContentView, .titled, .nonactivatingPanel] - window.standardWindowButton(.miniaturizeButton)?.isHidden = true - window.standardWindowButton(.zoomButton)?.isHidden = true - window.backgroundColor = .gray.withAlphaComponent(0.15) - window.isMovableByWindowBackground = true - } - } - } -} diff --git a/CodeEdit/Features/About/Views/AboutWindow.swift b/CodeEdit/Features/About/Views/AboutWindow.swift deleted file mode 100644 index 8faae428d1..0000000000 --- a/CodeEdit/Features/About/Views/AboutWindow.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// AboutWindow.swift -// CodeEdit -// -// Created by Wouter Hennen on 14/03/2023. -// - -import SwiftUI - -struct AboutWindow: Scene { - var body: some Scene { - Window("", id: SceneID.about.rawValue) { - AboutView() - } - .defaultSize(width: 530, height: 220) - .windowResizability(.contentSize) - .windowStyle(.hiddenTitleBar) - } -} diff --git a/CodeEdit/Features/About/Views/BlurButtonStyle.swift b/CodeEdit/Features/About/Views/BlurButtonStyle.swift deleted file mode 100644 index 1ec9cc5ca6..0000000000 --- a/CodeEdit/Features/About/Views/BlurButtonStyle.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// BlurButtonStyle.swift -// CodeEdit -// -// Created by Wouter Hennen on 21/01/2023. -// - -import SwiftUI - -extension ButtonStyle where Self == BlurButtonStyle { - static var blur: BlurButtonStyle { BlurButtonStyle() } -} - -struct BlurButtonStyle: ButtonStyle { - @Environment(\.controlSize) - var controlSize - - var height: CGFloat { - switch controlSize { - case .large: - return 28 - default: - return 20 - } - } - - @Environment(\.colorScheme) - var colorScheme - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .frame(height: height) - .buttonStyle(.bordered) - .background { - switch colorScheme { - case .dark: - Color - .gray - .opacity(0.001) - .overlay(.regularMaterial.blendMode(.plusLighter)) - .overlay(Color.gray.opacity(0.30)) - .overlay(Color.white.opacity(configuration.isPressed ? 0.20 : 0.00)) - case .light: - Color - .gray - .opacity(0.001) - .overlay(.regularMaterial.blendMode(.darken)) - .overlay(Color.gray.opacity(0.15).blendMode(.plusDarker)) - @unknown default: - Color.black - } - } - .cornerRadius(6) - } -} diff --git a/CodeEdit/Features/Acknowledgements/ViewModels/AcknowledgementsViewModel.swift b/CodeEdit/Features/Acknowledgements/ViewModels/AcknowledgementsViewModel.swift deleted file mode 100644 index ad2660055f..0000000000 --- a/CodeEdit/Features/Acknowledgements/ViewModels/AcknowledgementsViewModel.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// AcknowledgementsModel.swift -// CodeEditModules/Acknowledgements -// -// Created by Lukas Pistrol on 01.05.22. -// - -import SwiftUI - -final class AcknowledgementsViewModel: ObservableObject { - - @Published private (set) var acknowledgements: [AcknowledgementDependency] - - var indexedAcknowledgements: [(index: Int, acknowledgement: AcknowledgementDependency)] { - return Array(zip(acknowledgements.indices, acknowledgements)) - } - - init(_ dependencies: [AcknowledgementDependency] = []) { - self.acknowledgements = dependencies - - if acknowledgements.isEmpty { - fetchDependencies() - } - } - - func fetchDependencies() { - self.acknowledgements.removeAll() - do { - if let bundlePath = Bundle.main.path(forResource: "Package", ofType: "resolved") { - let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) - let parsedJSON = try JSONDecoder().decode(AcknowledgementObject.self, from: jsonData!) - for dependency in parsedJSON.pins.sorted(by: { $0.identity < $1.identity }) - where dependency.identity.range( - of: "[Cc]ode[Ee]dit", - options: .regularExpression, - range: nil, - locale: nil - ) == nil { - self.acknowledgements.append( - AcknowledgementDependency( - name: dependency.name, - repositoryLink: dependency.location, - version: dependency.state.version ?? "-" - ) - ) - } - } - } catch { - print(error) - } - } -} diff --git a/CodeEdit/Features/Acknowledgements/Views/AcknowledgementRowView.swift b/CodeEdit/Features/Acknowledgements/Views/AcknowledgementRowView.swift deleted file mode 100644 index bbb9338260..0000000000 --- a/CodeEdit/Features/Acknowledgements/Views/AcknowledgementRowView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// AcknowledgementsRowView.swift -// CodeEdit -// -// Created by Austin Condiff on 1/19/23. -// - -import SwiftUI - -struct AcknowledgementRowView: View { - @Environment(\.openURL) - private var openURL - - let acknowledgement: AcknowledgementDependency - - var body: some View { - HStack { - Text(acknowledgement.name) - .font(.body) - - Spacer() - - Button { - openURL(acknowledgement.repositoryURL) - } label: { - Image(systemName: "arrow.right.circle.fill") - .foregroundColor(Color(nsColor: .tertiaryLabelColor)) - } - .buttonStyle(.plain) - } - .padding(.vertical, 12) - .frame(maxWidth: .infinity) - } -} - -struct AcknowledgementsRowView_Previews: PreviewProvider { - static var previews: some View { - AcknowledgementRowView(acknowledgement: AcknowledgementDependency( - name: "Test", - repositoryLink: "https://www.test.com/", - version: "-" - )) - } -} diff --git a/CodeEdit/Features/Acknowledgements/Views/AcknowledgementsView.swift b/CodeEdit/Features/Acknowledgements/Views/AcknowledgementsView.swift deleted file mode 100644 index 43fe9767b5..0000000000 --- a/CodeEdit/Features/Acknowledgements/Views/AcknowledgementsView.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// AcknowledgementsView.swift -// CodeEditModules/Acknowledgements -// -// Created by Shivesh M M on 4/4/22. -// - -import SwiftUI - -struct AcknowledgementsView: View { - @StateObject var model = AcknowledgementsViewModel() - @Binding var aboutMode: AboutMode - var namespace: Namespace.ID - - var body: some View { - AboutDetailView(title: "Acknowledgements", aboutMode: $aboutMode, namespace: namespace) { - VStack(spacing: 0) { - ForEach( - model.indexedAcknowledgements, - id: \.acknowledgement.name - ) { (index, acknowledgement) in - if index != 0 { - Divider() - .frame(height: 0.5) - .opacity(0.5) - } - AcknowledgementRowView(acknowledgement: acknowledgement) - } - } - } - } -} diff --git a/CodeEdit/Features/Acknowledgements/Views/AcknowledgementsWindowController.swift b/CodeEdit/Features/Acknowledgements/Views/AcknowledgementsWindowController.swift deleted file mode 100644 index 26263aa26b..0000000000 --- a/CodeEdit/Features/Acknowledgements/Views/AcknowledgementsWindowController.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// AcknowledgementsWindowController.swift -// CodeEdit -// -// Created by Wouter Hennen on 18/01/2023. -// - -import SwiftUI - -final class AcknowledgementsViewWindowController: NSWindowController { - convenience init(view: T, size: NSSize) { - let hostingController = NSHostingController(rootView: view) - // New window holding our SwiftUI view - let window = NSWindow(contentViewController: hostingController) - self.init(window: window) - window.setContentSize(size) - window.styleMask = [.closable, .fullSizeContentView, .titled, .nonactivatingPanel] - window.standardWindowButton(.miniaturizeButton)?.isHidden = true - window.standardWindowButton(.zoomButton)?.isHidden = true - window.titlebarAppearsTransparent = true - window.titleVisibility = .hidden - window.backgroundColor = .gray.withAlphaComponent(0.15) - } - - override func showWindow(_ sender: Any?) { - window?.center() - window?.alphaValue = 0.0 - - super.showWindow(sender) - - window?.animator().alphaValue = 1.0 - - // close the window when the escape key is pressed - NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in - guard event.keyCode == 53 else { return event } - - self.closeAnimated() - - return nil - } - - window?.collectionBehavior = [.transient, .ignoresCycle] - window?.isMovableByWindowBackground = true - window?.title = "Acknowledgements" - } - - func closeAnimated() { - NSAnimationContext.beginGrouping() - NSAnimationContext.current.duration = 0.4 - NSAnimationContext.current.completionHandler = { - self.close() - } - window?.animator().alphaValue = 0.0 - NSAnimationContext.endGrouping() - } -} diff --git a/CodeEdit/Features/Acknowledgements/Views/ParsePackagesResolved.swift b/CodeEdit/Features/Acknowledgements/Views/ParsePackagesResolved.swift deleted file mode 100644 index 243a100199..0000000000 --- a/CodeEdit/Features/Acknowledgements/Views/ParsePackagesResolved.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// ParsePackagesResolved.swift -// CodeEditModules/Acknowledgements -// -// Created by Shivesh M M on 4/4/22. -// - -import Foundation - -struct AcknowledgementDependency: Decodable { - var name: String - var repositoryLink: String - var version: String - var repositoryURL: URL { - URL(string: repositoryLink)! - } -} - -// MARK: - Object -struct AcknowledgementObject: Codable { - let pins: [AcknowledgementPin] -} - -// MARK: - Pin -struct AcknowledgementPin: Codable { - let identity: String - let location: String - let state: AcknowledgementPackageState - - var name: String { - location.split(separator: "/").last?.replacingOccurrences(of: ".git", with: "") ?? identity - } -} - -// MARK: - State -struct AcknowledgementPackageState: Codable { - let revision: String - let version: String? -} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile+Recursion.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile+Recursion.swift deleted file mode 100644 index 1647514a4e..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile+Recursion.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// CEWorkspaceFile+Recursion.swift -// CodeEdit -// -// Created by Matthijs Eikelenboom on 30/04/2023. -// - -import Foundation - -extension CEWorkspaceFile { - /// Flattens the children of `self` recursively with depth. - /// - Parameters: - /// - depth: An int that indicates the how deep the tree files need to be flattened - /// - ignoringFolders: A boolean on whether to ignore files that are Folders - /// - fileManager: The workspace's file manager to use. - /// - Returns: An array of flattened `CEWorkspaceFiles` - func flattenedChildren( - withDepth depth: Int, - ignoringFolders: Bool, - using fileManager: CEWorkspaceFileManager - ) -> [CEWorkspaceFile] { - guard depth > 0 else { return [] } - guard self.isFolder else { return [self] } - var childItems: [CEWorkspaceFile] = ignoringFolders ? [] : [self] - fileManager.childrenOfFile(self)?.forEach { child in - childItems.append(contentsOf: child.flattenedChildren( - withDepth: depth - 1, - ignoringFolders: ignoringFolders, - using: fileManager - )) - } - return childItems - } - - /// Returns a list of `CEWorkspaceFiles` that are sibilings of `self`. - /// The `height` parameter lets the function navigate up the folder hierarchy to - /// select a starting point from which it should start flettening the items. - /// - Parameters: - /// - height: `Int` that tells where to start in the hierarchy - /// - ignoringFolders: Wether the sibling folders should be flattened - /// - fileManager: The workspace's file manager to use. - /// - Returns: A list of `FileSystemItems` - func flattenedSiblings( - withHeight height: Int, - ignoringFolders: Bool, - using fileManager: CEWorkspaceFileManager - ) -> [CEWorkspaceFile] { - let topMostParent = self.getParent(withHeight: height) - return topMostParent.flattenedChildren(withDepth: height, ignoringFolders: ignoringFolders, using: fileManager) - } - - /// Using the current instance of `FileSystemItem` it will walk back up the Workspace file hiarchy - /// the amount of times specified with the `withHeight` parameter. - /// - Parameter height: The amount of times you want to up a folder. - /// - Returns: The found `FileSystemItem` object, This should always be a folder. - private func getParent(withHeight height: Int) -> CEWorkspaceFile { - var topmostParent = self - for _ in 0.. String { - var myDetails = "\(String(repeating: "| ", count: max(tabCount - 1, 0)))\(tabCount != 0 ? "╰--" : "")" - myDetails += "\(url.path(percentEncoded: false))" - if !self.isFolder { // if im a file, just return the url - return myDetails - } else { // if im a folder, return the url and its children's details - var childDetails = "\(myDetails)" - if fileManager.hasLoadedChildrenFor(file: self) { - for child in fileManager.childrenOfFile(self) ?? [] { - childDetails += "\n\(child.childrenDescription(tabCount: tabCount + 1, using: fileManager))" - } - } else { - // Disabling for debug line. - // swiftlint:disable:next line_length - childDetails += "\n\(String(repeating: "| ", count: max(tabCount - 1, 0)))\(tabCount != 0 ? "╰--" : "") Children Not Loaded" - } - return childDetails - } - } -#endif -} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift deleted file mode 100644 index 2264d777ba..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift +++ /dev/null @@ -1,267 +0,0 @@ -// -// FileItem.swift -// CodeEdit -// -// Created by Matthijs Eikelenboom on 07/02/2023. -// - -import Foundation -import SwiftUI -import UniformTypeIdentifiers -import Combine - -/// An object containing all necessary information and actions for a specific file in the workspace -/// -/// The ``CEWorkspaceFile`` represents every type of file that can exist on the file system. Directories, files, -/// symlinks, etc. This class does not assume anything about what it is representing, but it can be interrogated to find -/// out what it represents. All information about the file is derived from the `URL` passed to the initializer of the -/// object. -/// -/// This object works to provide a consistent API for any component that needs to work with files, and is as small as -/// possible. -/// -/// These objects should be fetched from the ``CEWorkspaceFileManager`` whenever possible. Objects fetched from there -/// will be connected in CodeEdit's file tree, and structural properties like ``CEWorkspaceFile/parent`` will exist. -/// They can, however, be created standalone when necessary. Creating a standalone ``CEWorkspaceFile`` is useful if -/// loading all intermediate subdirectories (from the nearest cached parent to the file) has not been done yet and doing -/// so would be unnecessary. -/// -/// An example of this is in the ``QuickOpenView``. This view finds a file URL via a search bar, and needs to display a -/// quick preview of the file. There's a good chance the file is deep in some subdirectory of the workspace, so fetching -/// it from the ``CEWorkspaceFileManager`` may require loading and caching multiple directories. Instead, it just -/// makes a disconnected object and uses it for the preview. Then, when opening the file in the workspace it forces the -/// file to be loaded and cached. -final class CEWorkspaceFile: Codable, Comparable, Hashable, Identifiable, EditorTabRepresentable { - - /// The id of the ``CEWorkspaceFile``. - /// - /// This is equal to `url.relativePath` - var id: String { url.relativePath } - - /// Returns the file name (e.g.: `Package.swift`) - var name: String { url.lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines) } - - /// Returns the extension of the file or an empty string if no extension is present. - var type: FileIcon.FileType { .init(rawValue: url.pathExtension) ?? .txt } - - /// Returns the URL of the ``CEWorkspaceFile`` - let url: URL - - /// Return the icon of the file as `Image` - var icon: Image { - if let customImage = NSImage.symbol(named: systemImage) { - return Image(nsImage: customImage) - } else { - return Image(systemName: systemImage) - } - } - - /// Return the icon of the file as `NSImage` - var nsIcon: NSImage { - if let customImage = NSImage.symbol(named: systemImage) { - return customImage - } else { - return NSImage(systemSymbolName: systemImage, accessibilityDescription: systemImage) - ?? NSImage(systemSymbolName: "doc", accessibilityDescription: "doc")! - } - } - - /// Returns a parent ``CEWorkspaceFile``. - /// - /// If the item already is the top-level ``CEWorkspaceFile`` this returns `nil`. - var parent: CEWorkspaceFile? - - private let fileDocumentSubject = PassthroughSubject() - - var fileDocument: CodeFileDocument? { - didSet { - fileDocumentSubject.send(fileDocument) - } - } - - /// Publisher for fileDocument property - var fileDocumentPublisher: AnyPublisher { - fileDocumentSubject.eraseToAnyPublisher() - } - - var fileIdentifier = UUID().uuidString - - /// Returns the Git status of a file as ``GitType`` - var gitStatus: GitType? - - /// Returns a boolean that is true if the file is staged for commit - var staged: Bool? - - /// Returns the `id` in ``EditorTabID`` enum form - var tabID: EditorTabID { .codeEditor(id) } - - /// Returns a boolean that is true if the resource represented by this object is a directory. - lazy var isFolder: Bool = { - (try? url.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true - }() - - /// Returns a boolean that is true if the contents of the directory at this path are - /// - /// Does not indicate if this is a folder, see ``isFolder`` to first check if this object is also a directory. - var isEmptyFolder: Bool { - (try? CEWorkspaceFile.fileManager.contentsOfDirectory( - at: url, - includingPropertiesForKeys: nil, - options: .skipsSubdirectoryDescendants - ).isEmpty) ?? true - } - - /// Returns a boolean that is true if the file item is the root folder of the workspace. - var isRoot: Bool { parent == nil } - - /// Returns a boolean that is true if the file item actually exists in the file system - var doesExist: Bool { CEWorkspaceFile.fileManager.fileExists(atPath: self.url.path) } - - /// Returns a string describing a SFSymbol for the current ``CEWorkspaceFile`` - /// - /// Use it like this - /// ```swift - /// Image(systemName: item.systemImage) - /// ``` - var systemImage: String { - if isFolder { - // item is a folder - return folderIcon() - } else { - // item is a file - return FileIcon.fileIcon(fileType: type) - } - } - - /// Return the file's UTType - var contentType: UTType? { - try? url.resourceValues(forKeys: [.contentTypeKey]).contentType - } - - /// Returns a `Color` for a specific `fileType` - /// - /// If not specified otherwise this will return `Color.accentColor` - var iconColor: Color { - FileIcon.iconColor(fileType: type) - } - - init( - url: URL, - changeType: GitType? = nil, - staged: Bool? = false - ) { - self.url = url - self.gitStatus = changeType - self.staged = staged - } - - enum CodingKeys: String, CodingKey { - case url - case changeType - case staged - } - - required init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - url = try values.decode(URL.self, forKey: .url) - gitStatus = try values.decode(GitType.self, forKey: .changeType) - staged = try values.decode(Bool.self, forKey: .staged) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(url, forKey: .url) - try container.encode(gitStatus, forKey: .changeType) - try container.encode(staged, forKey: .staged) - } - - /// Returns a string describing a SFSymbol for folders - /// - /// If it is the top-level folder this will return `"square.dashed.inset.filled"`. - /// If it is a `.codeedit` folder this will return `"folder.fill.badge.gearshape"`. - /// If it has children this will return `"folder.fill"` otherwise `"folder"`. - private func folderIcon() -> String { - if self.parent == nil { - return "folder.fill.badge.gearshape" - } - if self.name == ".codeedit" { - return "folder.fill.badge.gearshape" - } - return isEmptyFolder ? "folder" : "folder.fill" - } - - /// Returns the file name with optional extension (e.g.: `Package.swift`) - func fileName(typeHidden: Bool = false) -> String { - typeHidden ? url.deletingPathExtension() - .lastPathComponent - .trimmingCharacters(in: .whitespacesAndNewlines) : name - } - - /// Generates a string based on user's file name preferences. - /// - Returns: A `String` suitable for display. - func labelFileName() -> String { - let prefs = Settings.shared.preferences.general - switch prefs.fileExtensionsVisibility { - case .hideAll: - return self.fileName(typeHidden: true) - case .showAll: - return self.fileName(typeHidden: false) - case .showOnly: - return self.fileName(typeHidden: !prefs.shownFileExtensions.extensions.contains(self.type.rawValue)) - case .hideOnly: - return self.fileName(typeHidden: prefs.hiddenFileExtensions.extensions.contains(self.type.rawValue)) - } - } - - func validateFileName(for newName: String) -> Bool { - guard newName != labelFileName() else { return true } - - guard !newName.isEmpty && newName.isValidFilename && - !FileManager.default.fileExists( - atPath: self.url.deletingLastPathComponent().appendingPathComponent(newName).path - ) - else { return false } - - return true - } - - // MARK: Statics - /// The default `FileManager` instance - static let fileManager = FileManager.default - - // MARK: Intents - /// Allows the user to view the file or folder in the finder application - func showInFinder() { - NSWorkspace.shared.activateFileViewerSelecting([url]) - } - - /// Allows the user to launch the file or folder as it would be in finder - func openWithExternalEditor() { - NSWorkspace.shared.open(url) - } - - /// Nearest folder refers to the parent directory if this is a non-folder item, or itself if the item is a folder. - var nearestFolder: URL { - (self.isFolder ? - self.url : - self.url.deletingLastPathComponent()) - } - - // MARK: Comparable - - static func == (lhs: CEWorkspaceFile, rhs: CEWorkspaceFile) -> Bool { - lhs.id == rhs.id - } - - static func < (lhs: CEWorkspaceFile, rhs: CEWorkspaceFile) -> Bool { - lhs.url.lastPathComponent < rhs.url.lastPathComponent - } - - // MARK: Hashable - - func hash(into hasher: inout Hasher) { - hasher.combine(url) - hasher.combine(id) - } - -} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileIcon.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileIcon.swift deleted file mode 100644 index fbaa0893df..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileIcon.swift +++ /dev/null @@ -1,210 +0,0 @@ -// -// FileIcon.swift -// -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI - -// TODO: DOCS (Nanashi Li) -enum FileIcon { - - // swiftlint:disable identifier_name - enum FileType: String { - case adb - case aif - case avi - case bash - case c - case cetheme - case clj - case cls - case cs - case css - case d - case dart - case elm - case entitlements - case env - case ex - case example - case f95 - case fs - case gitignore - case go - case gs - case h - case hs - case html - case ico - case java - case jl - case jpeg - case jpg - case js - case json - case jsx - case kt - case l - case LICENSE - case lock - case lsp - case lua - case m - case Makefile - case md - case mid - case mjs - case mk - case mod - case mov - case mp3 - case mp4 - case pas - case pdf - case pl - case plist - case png - case py - case resolved - case rb - case rs - case rtf - case scm - case scpt - case sh - case ss - case strings - case sum - case svg - case swift - case ts - case tsx - case txt = "text" - case vue - case wav - case xcconfig - case yml - case zsh - } - - // swiftlint:enable identifier_name - - /// Returns a string describing a SFSymbol for files - /// If not specified otherwise this will return `"doc"` - static func fileIcon(fileType: FileType) -> String { // swiftlint:disable:this cyclomatic_complexity function_body_length line_length - switch fileType { - case .json, .yml, .resolved: - return "doc.json" - case .lock: - return "lock.doc" - case .css: - return "curlybraces" - case .js, .mjs: - return "doc.javascript" - case .jsx, .tsx: - return "atom" - case .swift: - return "swift" - case .env, .example: - return "gearshape.fill" - case .gitignore: - return "arrow.triangle.branch" - case .pdf, .png, .jpg, .jpeg, .ico: - return "photo" - case .svg: - return "square.fill.on.circle.fill" - case .entitlements: - return "checkmark.seal" - case .plist: - return "tablecells" - case .md, .txt: - return "doc.plaintext" - case .rtf: - return "doc.richtext" - case .html: - return "chevron.left.forwardslash.chevron.right" - case .LICENSE: - return "key.fill" - case .java: - return "cup.and.saucer" - case .py: - return "doc.python" - case .rb: - return "doc.ruby" - case .strings: - return "text.quote" - case .h: - return "h.square" - case .m: - return "m.square" - case .vue: - return "v.square" - case .go: - return "g.square" - case .sum: - return "s.square" - case .mod: - return "m.square" - case .bash, .sh, .Makefile, .zsh: - return "terminal" - case .rs: - return "r.square" - case .wav, .mp3, .aif, .mid: - return "speaker.wave.2" - case .avi, .mp4, .mov: - return "film" - case .scpt: - return "applescript" - case .xcconfig: - return "gearshape.2" - case .cetheme: - return "paintbrush" - case .adb, .clj, .cls, .cs, .d, .dart, .elm, .ex, .f95, .fs, .gs, .hs, - .jl, .kt, .l, .lsp, .lua, .mk, .pas, .pl, .scm, .ss: - return "doc.plaintext" - default: - return "doc" - } - } - - /// Returns a `Color` for a specific `fileType` - /// If not specified otherwise this will return `Color.accentColor` - static func iconColor(fileType: FileType) -> Color { // swiftlint:disable:this cyclomatic_complexity - switch fileType { - case .swift, .html: - return .orange - case .java, .jpg, .png, .svg, .ts: - return .blue - case .css: - return .teal - case .js, .mjs, .py, .entitlements, .LICENSE: - return Color("Amber") - case .json, .resolved, .rb, .strings, .yml: - return Color("Scarlet") - case .jsx, .tsx: - return .cyan - case .plist, .xcconfig, .sh: - return Color("Steel") - case .c, .cetheme: - return .purple - case .vue: - return Color(red: 0.255, green: 0.722, blue: 0.514, opacity: 1.0) - case .h: - return Color(red: 0.667, green: 0.031, blue: 0.133, opacity: 1.0) - case .m: - return Color(red: 0.271, green: 0.106, blue: 0.525, opacity: 1.0) - case .go: - return Color(red: 0.02, green: 0.675, blue: 0.757, opacity: 1.0) - case .sum, .mod: - return Color(red: 0.925, green: 0.251, blue: 0.478, opacity: 1.0) - case .Makefile: - return Color(red: 0.937, green: 0.325, blue: 0.314, opacity: 1.0) - case .rs: - return .orange - default: - return Color("Steel") - } - } -} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift deleted file mode 100644 index 89bfed5815..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ /dev/null @@ -1,197 +0,0 @@ -// -// CEWorkspaceFileManager+FileSystem.swift -// CodeEdit -// -// Created by Khan Winter on 9/30/23. -// - -import Foundation -import AppKit - -extension CEWorkspaceFileManager { - /// This function allows creation of folders in the main directory or sub-folders - /// - Parameters: - /// - folderName: The name of the new folder - /// - file: The file to add the new folder to. - /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e* - func addFolder(folderName: String, toFile file: CEWorkspaceFile) { - // Check if folder, if it is create folder under self, else create on same level. - var folderUrl = ( - file.isFolder ? file.url.appendingPathComponent(folderName) - : file.url.deletingLastPathComponent().appendingPathComponent(folderName) - ) - - // If a file/folder with the same name exists, add a number to the end. - var fileNumber = 0 - while fileManager.fileExists(atPath: folderUrl.path) { - fileNumber += 1 - folderUrl = folderUrl.deletingLastPathComponent().appendingPathComponent("\(folderName)\(fileNumber)") - } - - // Create the folder - do { - try fileManager.createDirectory( - at: folderUrl, - withIntermediateDirectories: true, - attributes: [:] - ) - } catch { - fatalError(error.localizedDescription) - } - } - - /// This function allows creating files in the selected folder or project main directory - /// - Parameters: - /// - fileName: The name of the new file - /// - file: The file to add the new file to. - /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e* - func addFile(fileName: String, toFile file: CEWorkspaceFile) { - // check the folder for other files, and see what the most common file extension is - var fileExtensions: [String: Int] = ["": 0] - - for child in ( - file.isFolder ? file.flattenedSiblings(withHeight: 2, ignoringFolders: true, using: self) - : file.parent?.flattenedSiblings(withHeight: 2, ignoringFolders: true, using: self) - ) ?? [] - where !child.isFolder { - // if the file extension was present before, add it now - let childFileName = child.fileName(typeHidden: false) - if let index = childFileName.lastIndex(of: ".") { - let childFileExtension = ".\(childFileName.suffix(from: index).dropFirst())" - fileExtensions[childFileExtension] = (fileExtensions[childFileExtension] ?? 0) + 1 - } else { - fileExtensions[""] = (fileExtensions[""] ?? 0) + 1 - } - } - - var largestValue = 0 - var idealExtension = "" - for (extName, count) in fileExtensions where count > largestValue { - idealExtension = extName - largestValue = count - } - - var fileUrl = file.nearestFolder.appendingPathComponent("\(fileName)\(idealExtension)") - // If a file/folder with the same name exists, add a number to the end. - var fileNumber = 0 - while fileManager.fileExists(atPath: fileUrl.path) { - fileNumber += 1 - fileUrl = fileUrl.deletingLastPathComponent() - .appendingPathComponent("\(fileName)\(fileNumber)\(idealExtension)") - } - - // Create the file - fileManager.createFile( - atPath: fileUrl.path, - contents: nil, - attributes: [FileAttributeKey.creationDate: Date()] - ) - } - - /// This function deletes the item or folder from the current project - /// - Parameters: - /// - file: The file to delete - /// - confirmDelete: True to present an alert to confirm the delete. - /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e* - public func delete(file: CEWorkspaceFile, confirmDelete: Bool = true) { - // This function also has to account for how the - // - file system can change outside of the editor - let deleteConfirmation = NSAlert() - let message: String - if !file.isFolder || file.isEmptyFolder { // if its a file or an empty folder, call it by its name - message = file.name - } else { - let childrenCount = try? fileManager.contentsOfDirectory( - at: file.url, - includingPropertiesForKeys: nil - ).count - message = "the \((childrenCount ?? 0) + 1) selected items" - } - deleteConfirmation.messageText = "Do you want to move \(message) to the Trash?" - deleteConfirmation.informativeText = "This operation cannot be undone" - deleteConfirmation.alertStyle = .critical - deleteConfirmation.addButton(withTitle: "Move to Trash") - deleteConfirmation.buttons.last?.hasDestructiveAction = true - deleteConfirmation.addButton(withTitle: "Cancel") - if !confirmDelete || deleteConfirmation.runModal() == .alertFirstButtonReturn { // "Delete" button - if fileManager.fileExists(atPath: file.url.path) { - do { - try fileManager.removeItem(at: file.url) - } catch { - fatalError(error.localizedDescription) - } - } - } - } - - /// This function duplicates the item or folder - /// - Parameter file: The file to duplicate - /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e* - public func duplicate(file: CEWorkspaceFile) { - // If a file/folder with the same name exists, add "copy" to the end - var fileUrl = file.url - while fileManager.fileExists(atPath: fileUrl.path) { - let previousName = fileUrl.lastPathComponent - let fileExtension = fileUrl.pathExtension.isEmpty ? "" : ".\(fileUrl.pathExtension)" - let fileName = fileExtension.isEmpty ? previousName : - previousName.replacingOccurrences(of: fileExtension, with: "") - fileUrl = fileUrl.deletingLastPathComponent().appendingPathComponent("\(fileName) copy\(fileExtension)") - } - - if fileManager.fileExists(atPath: file.url.path) { - do { - try fileManager.copyItem(at: file.url, to: fileUrl) - } catch { - fatalError(error.localizedDescription) - } - } - } - - /// This function moves the item or folder if possible - /// - Parameters: - /// - file: The file to move. - /// - newLocation: The destination to move the file to. - /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e* - public func move(file: CEWorkspaceFile, to newLocation: URL) { - guard !fileManager.fileExists(atPath: newLocation.path) else { return } - createMissingParentDirectory(for: newLocation.deletingLastPathComponent()) - - do { - try fileManager.moveItem(at: file.url, to: newLocation) - } catch { fatalError(error.localizedDescription) } - - // This function recursively creates missing directories if the file is moved to a directory that does not exist - func createMissingParentDirectory(for url: URL, createSelf: Bool = true) { - // if the folder's parent folder doesn't exist, create it. - if !fileManager.fileExists(atPath: url.deletingLastPathComponent().path) { - createMissingParentDirectory(for: url.deletingLastPathComponent()) - } - // if the folder doesn't exist and the function was ordered to create it, create it. - if createSelf && !fileManager.fileExists(atPath: url.path) { - // Create the folder - do { - try fileManager.createDirectory( - at: url, - withIntermediateDirectories: true, - attributes: [:] - ) - } catch { - fatalError(error.localizedDescription) - } - } - } - } - - /// Copy a file's contents to a new location. - /// - Parameters: - /// - file: The file to copy. - /// - newLocation: The location to copy to. - public func copy(file: CEWorkspaceFile, to newLocation: URL) { - guard file.url != newLocation && !fileManager.fileExists(atPath: newLocation.absoluteString) else { return } - do { - try fileManager.copyItem(at: file.url, to: newLocation) - } catch { - fatalError(error.localizedDescription) - } - } -} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift deleted file mode 100644 index 56453a0923..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift +++ /dev/null @@ -1,392 +0,0 @@ -// -// FileSystemClient.swift -// CodeEdit -// -// Created by Matthijs Eikelenboom on 04/02/2023. -// - -import Combine -import Foundation -import AppKit - -protocol CEWorkspaceFileManagerObserver: AnyObject { - func fileManagerUpdated(updatedItems: Set) -} - -/// This class is used to load, modify, and listen to files on a user's machine. -/// -/// The workspace file manager provides an API for: -/// - Navigating and loading file items. -/// - Moving and modifying files. -/// - Listening for file system updates and notifying observers. -/// -/// File caching in CodeEdit is done lazily. the ``CEWorkspaceFileManager`` will only create ``CEWorkspaceFile``s for -/// from files that are needed for some UI component, and ignores all other files. This is done primarily to prevent -/// CodeEdit from wasting resources finding and caching a potentially large file tree (eg, a user's home directory). -/// -/// When the workspace is first loaded, the file manager will only load the contents of the workspace directory. To find -/// files after this, calls to ``CEWorkspaceFileManager/getFile(_:createIfNotFound:)`` or -/// ``CEWorkspaceFileManager/childrenOfFile(_:)`` can cause the children for a file to be loaded into CodeEdit's cache -/// of files. -/// -/// Moving and modifying files is done via the methods: ``CEWorkspaceFileManager/addFile(fileName:toFile:)``, -/// ``CEWorkspaceFileManager/addFolder(folderName:toFile:)``, ``CEWorkspaceFileManager/delete(file:)``, -/// ``CEWorkspaceFileManager/copy(file:to:)``, and ``CEWorkspaceFileManager/duplicate(file:)``. -/// -/// To listen for updates, the ``CEWorkspaceFileManager`` uses a ``DirectoryEventStream`` to listen to updates for any -/// files under the ``CEWorkspaceFileManager/folderUrl`` url. Those can be passed on to listeners that conform to the -/// ``CEWorkspaceFileManagerObserver`` protocol. Use the ``CEWorkspaceFileManager/addObserver(_:)`` -/// and ``CEWorkspaceFileManager/removeObserver(_:)`` to add or remove observers. Observers are kept as weak references. -final class CEWorkspaceFileManager { - private(set) var fileManager: FileManager - private(set) var ignoredFilesAndFolders: Set - private(set) var flattenedFileItems: [String: CEWorkspaceFile] - /// Maps all directories to it's children's paths. - private var childrenMap: [String: [String]] = [:] - private var fsEventStream: DirectoryEventStream? - private var observers: NSHashTable = .weakObjects() - - let folderUrl: URL - let workspaceItem: CEWorkspaceFile - weak var sourceControlManager: SourceControlManager? - - /// Create a file manager object with a root and a set of files to ignore. - /// - Parameters: - /// - folderUrl: The folder to use as the root of the file manager. - /// - ignoredFilesAndFolders: A set of files to ignore. These should not be paths, but rather file names - /// like `.DS_Store` - init( - folderUrl: URL, - ignoredFilesAndFolders: Set, - fileManager: FileManager = FileManager.default, - sourceControlManager: SourceControlManager? - ) { - self.folderUrl = folderUrl - self.ignoredFilesAndFolders = ignoredFilesAndFolders - - self.workspaceItem = CEWorkspaceFile(url: folderUrl) - self.flattenedFileItems = [workspaceItem.id: workspaceItem] - self.sourceControlManager = sourceControlManager - self.fileManager = fileManager - - self.loadChildrenForFile(self.workspaceItem) - - fsEventStream = DirectoryEventStream(directory: self.folderUrl.path) { [weak self] events in - self?.fileSystemEventReceived(events: events) - } - - Task { - try await self.sourceControlManager?.validate() - } - } - - // MARK: - Public API - - /// A function that, given a file's path, returns a `FileItem` if it exists - /// within the scope of the `FileSystemClient`. - /// - Parameters: - /// - path: The file's relative path. - /// - createIfNotFound: Set to true if the function should index any intermediate directories to find the file, - /// as well as index the file if it is not already. - /// - Returns: The file item corresponding to the file - func getFile( - _ path: String, - createIfNotFound: Bool = false - ) -> CEWorkspaceFile? { - if let file = flattenedFileItems[path] { - return file - } else if createIfNotFound { - guard let url = URL(string: path, relativeTo: folderUrl) else { - return nil - } - - // Drill down towards the file, indexing any directories needed. If file is not in the `folderURL` or - // subdirectories, exit. - guard url.absoluteString.starts(with: folderUrl.absoluteString), - url.pathComponents.count > folderUrl.pathComponents.count else { - return nil - } - let pathComponents = url.pathComponents.dropFirst(folderUrl.pathComponents.count) - var currentURL = folderUrl - - for component in pathComponents { - currentURL.append(component: component) - - if let file = flattenedFileItems[currentURL.relativePath], childrenMap[file.id] == nil { - loadChildrenForFile(file) - } - } - - return flattenedFileItems[url.relativePath] - } - - return nil - } - - /// Returns all children for the given file. - /// - Note: Will find and cache new children if they have not been already, see - /// ``CEWorkspaceFileManager/getFile(_:createIfNotFound:)`` to force a file to be loaded. - /// - Parameter file: The file to find children for. - /// - Returns: An array of children for the file, or `nil` if the file was not a directory. - func childrenOfFile(_ file: CEWorkspaceFile) -> [CEWorkspaceFile]? { - if file.isFolder { - if childrenMap[file.id] == nil { - // Load the children - loadChildrenForFile(file) - } - - return childrenMap[file.id]?.compactMap { flattenedFileItems[$0] } - } - return nil - } - - /// Loads and caches all children for the given file item. - /// - /// After calling this method, you can expect `childrenMap` to contain some value - /// for the file object, even an empty array. - /// - /// - Parameter file: The file item to load children for. - private func loadChildrenForFile(_ file: CEWorkspaceFile) { - guard let children = urlsForDirectory(file) else { - return - } - for child in children { - let newFileItem = CEWorkspaceFile(url: child) - newFileItem.parent = file - flattenedFileItems[newFileItem.id] = newFileItem - } - childrenMap[file.id] = children.map { $0.relativePath } - Task { - await sourceControlManager?.refreshAllChangedFiles() - } - } - - /// Creates an ordered array of all files and directories at the given file object. - /// - Parameter file: The file to use. - /// - Returns: An ordered array of URLs sorted alphabetically with directories first. - private func urlsForDirectory(_ file: CEWorkspaceFile) -> [URL]? { - try? fileManager.contentsOfDirectory( - at: file.url, - includingPropertiesForKeys: [.isDirectoryKey], - options: [.includesDirectoriesPostOrder, .skipsSubdirectoryDescendants] - ) - .compactMap { - ignoredFilesAndFolders.contains($0.lastPathComponent) && (try? $0.checkResourceIsReachable()) ?? false - ? nil - : URL(filePath: $0.path(percentEncoded: false), relativeTo: folderUrl) - } - .sortItems(foldersOnTop: true) - } - -#if DEBUG - /// Determines if the file has had it's children loaded from disk. - /// - Parameter file: The file to check. - /// - Returns: True if the file's children have been cached. - func hasLoadedChildrenFor(file: CEWorkspaceFile) -> Bool { - childrenMap[file.id] != nil - } -#endif - - // MARK: - Directory Events - - /// Run when the owner of the ``CEWorkspaceFileManager`` doesn't need it anymore. - /// This de-inits most functions in the ``CEWorkspaceFileManager``, so that in case it isn't de-init'd it does not - /// use up significant amounts of RAM, and clears any file system event watchers. - func cleanUp() { - fsEventStream?.cancel() - flattenedFileItems = [workspaceItem.id: workspaceItem] - } - - /// Called by `fsEventStream` when an event occurs. - /// - /// This method may be called on a background thread, but all work done by this function will be queued on the main - /// thread. - /// - Parameter events: An array of events that occurred. - private func fileSystemEventReceived(events: [DirectoryEventStream.Event]) { - DispatchQueue.main.async { - var files: Set = [] - for event in events { - // Event returns file/folder that was changed, but in tree we need to update it's parent - let parent = "/" + event.path.split(separator: "/").dropLast().joined(separator: "/") - guard let parentItem = self.getFile(parent) else { - continue - } - - switch event.eventType { - case .changeInDirectory, .itemChangedOwner, .itemModified: - // Can be ignored for now, these I think not related to tree changes - continue - case .rootChanged: - // TODO: Handle workspace root changing. - continue - case .itemCreated, .itemCloned, .itemRemoved, .itemRenamed: - try? self.rebuildFiles(fromItem: parentItem) - files.insert(parentItem) - } - } - if !files.isEmpty { - self.notifyObservers(updatedItems: files) - } - - self.handleGitEvents(events: events) - } - } - - func handleGitEvents(events: [DirectoryEventStream.Event]) { - // Changes excluding .git folder - let notGitChanges = events.filter({ !$0.path.contains(".git/") }) - - // .git folder was changed - let gitFolderChange = events.first(where: { - $0.path == "\(self.folderUrl.relativePath)/.git" - }) - - // Change made to git index file, staged/unstaged files - let gitIndexChange = events.first(where: { - $0.path == "\(self.folderUrl.relativePath)/.git/index" - }) - - // Change made to git stash - let gitStashChange = events.first(where: { - $0.path == "\(self.folderUrl.relativePath)/.git/refs/stash" - }) - - // Changes made to git branches - let gitBranchChange = events.first(where: { - $0.path.contains("\(self.folderUrl.relativePath)/.git/refs/heads") - }) - - // Changes made to git HEAD - current branch changed - let gitHeadChange = events.first(where: { - $0.path.contains("\(self.folderUrl.relativePath)/.git/HEAD") - }) - - // Change made to remotes by looking at .git/config - let gitConfigChange = events.first(where: { - $0.path == "\(self.folderUrl.relativePath)/.git/config" - }) - - // If changes were made to project OR files were staged, refresh changes - if !notGitChanges.isEmpty || gitIndexChange != nil { - Task { - await self.sourceControlManager?.refreshAllChangedFiles() - } - } - - // If changes were stashed, refresh stashed entries - if gitStashChange != nil { - Task { - try await self.sourceControlManager?.refreshStashEntries() - } - } - - // If branches were added or removed, refresh branches - if gitBranchChange != nil { - Task { - await self.sourceControlManager?.refreshBranches() - } - } - - // If HEAD was changed, refresh the current branch - if gitHeadChange != nil { - Task { - await self.sourceControlManager?.refreshCurrentBranch() - } - } - - // If git config changed, refresh remotes - if gitConfigChange != nil { - Task { - try await self.sourceControlManager?.refreshRemotes() - } - } - - // If .git folder was added or removed, check if repository is valid - if gitFolderChange != nil { - Task { - try await self.sourceControlManager?.validate() - } - } - } - - /// Creates or deletes children of the ``CEWorkspaceFile`` so that they are accurate with the file system, - /// instead of creating an entirely new ``CEWorkspaceFile``. Can optionally run a deep rebuild. - /// - /// This method will return immediately if the given file item is not a directory. - /// This will also only rebuild *already cached* directories. - /// - Parameters: - /// - fileItem: The ``CEWorkspaceFile`` to correct the children of - /// - deep: Set to `true` if this should perform the rebuild recursively. - func rebuildFiles(fromItem fileItem: CEWorkspaceFile, deep: Bool = false) throws { - // Do not index directories that are not already loaded. - guard childrenMap[fileItem.id] != nil else { return } - - // get the actual directory children - let directoryContentsUrls = try fileManager.contentsOfDirectory( - at: fileItem.url.resolvingSymlinksInPath(), - includingPropertiesForKeys: nil - ) - - // test for deleted children, and remove them from the index - // Folders may or may not have slash at the end, this will normalize check - let directoryContentsUrlsRelativePaths = directoryContentsUrls.map({ $0.relativePath }) - for (idx, oldURL) in (childrenMap[fileItem.id] ?? []).map({ URL(filePath: $0) }).enumerated().reversed() - where !directoryContentsUrlsRelativePaths.contains(oldURL.relativePath) { - flattenedFileItems.removeValue(forKey: oldURL.relativePath) - childrenMap[fileItem.id]?.remove(at: idx) - } - - // test for new children, and index them - for newContent in directoryContentsUrls { - // if the child has already been indexed, continue to the next item. - guard !ignoredFilesAndFolders.contains(newContent.lastPathComponent) && - !(childrenMap[fileItem.id]?.contains(newContent.relativePath) ?? true) else { continue } - - if fileManager.fileExists(atPath: newContent.path) { - let newFileItem = CEWorkspaceFile(url: newContent) - - newFileItem.parent = fileItem - flattenedFileItems[newFileItem.id] = newFileItem - childrenMap[fileItem.id]?.append(newFileItem.id) - } - } - - childrenMap[fileItem.id] = childrenMap[fileItem.id]? - .map { URL(filePath: $0) } - .sortItems(foldersOnTop: true) - .map { $0.relativePath } - - if deep && childrenMap[fileItem.id] != nil { - for child in (childrenMap[fileItem.id] ?? []).compactMap({ flattenedFileItems[$0] }) { - try rebuildFiles(fromItem: child) - } - } - } - - /// Notify observers that an update occurred in the watched files. - func notifyObservers(updatedItems: Set) { - observers.allObjects.reversed().forEach { delegate in - guard let delegate = delegate as? CEWorkspaceFileManagerObserver else { - observers.remove(delegate) - return - } - delegate.fileManagerUpdated(updatedItems: updatedItems) - } - } - - /// Add an observer for file system events. - /// - Parameter observer: The observer to add. - func addObserver(_ observer: CEWorkspaceFileManagerObserver) { - observers.add(observer as AnyObject) - } - - /// Remove an observer for file system events. - /// - Parameter observer: The observer to remove. - func removeObserver(_ observer: CEWorkspaceFileManagerObserver) { - observers.remove(observer as AnyObject) - } - - deinit { - observers.removeAllObjects() - } -} diff --git a/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift b/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift deleted file mode 100644 index 753f3c79b3..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// DirectoryEventStream.swift -// CodeEdit -// -// Created by Khan Winter on 6/26/23. -// - -import Foundation - -enum FSEvent { - case changeInDirectory - case rootChanged - case itemChangedOwner - case itemCreated - case itemCloned - case itemModified - case itemRemoved - case itemRenamed -} - -/// Creates a stream of events using the File System Events API. -/// -/// The stream of events is started immediately upon initialization, and will only be stopped when either `cancel` -/// is called, or the object is deallocated. The stream is also configured to debounce notifications to happen -/// according to the `debounceDuration` parameter. This directly corresponds with the `latency` parameter in -/// `FSEventStreamCreate`, which will delay notifications until `latency` has passed at which point it will send all -/// the notifications built up during that period of time. -/// -/// Use the `callback` parameter to listen for notifications. -/// Notifications are automatically filtered to include certain events, but the FS event API doesn't always correctly -/// flag events so use caution when handling events as they can come frequently. -/// -/// The `callback` function will be called with all events that happened since the last event notification, -/// effectively batching all notifications every `debounceDuration`. This callback may not be called on a -/// predictable dispatch queue. -class DirectoryEventStream { - typealias EventCallback = ([Event]) -> Void - - private var streamRef: FSEventStreamRef? - private var callback: EventCallback - private let debounceDuration: TimeInterval - - struct Event { - let path: String - let eventType: FSEvent - } - - /// Initialize the event stream and begin listening for events. - /// - Parameters: - /// - directory: The directory to monitor. The listener may receive a ``FSEvent/rootChanged`` event if this - /// directory is modified or moved. - /// - debounceDuration: The duration to delay notifications for to let the FS events API accumulates events. - /// defaults to 0.1s. - /// - callback: A callback provided that the ``DirectoryEventStream`` will send events to. See - /// ``DirectoryEventStream``'s documentation for detailed information. - init(directory: String, debounceDuration: TimeInterval = 0.1, callback: @escaping EventCallback) { - self.debounceDuration = debounceDuration - self.callback = callback - let selfPtr = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) - - var context = FSEventStreamContext( - version: 0, - info: selfPtr, - retain: nil, - release: nil, - copyDescription: nil - ) - let contextPtr = withUnsafeMutablePointer(to: &context) { ptr in UnsafeMutablePointer(ptr) } - - let cfDirectory = directory as CFString - let pathsToWatch = [cfDirectory] as CFArray - - if let ref = FSEventStreamCreate( - kCFAllocatorDefault, - // swiflint:ignore:next opening_brace - { streamRef, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIds in - guard let clientCallBackInfo else { return } - Unmanaged - .fromOpaque(clientCallBackInfo) - .takeUnretainedValue() - .eventStreamHandler(streamRef, numEvents, eventPaths, eventFlags, eventIds) - }, - contextPtr, - pathsToWatch, - UInt64(kFSEventStreamEventIdSinceNow), - debounceDuration, - FSEventStreamCreateFlags( - kFSEventStreamCreateFlagUseCFTypes - // This will listen for file changes - | kFSEventStreamCreateFlagFileEvents - // This provides additional information, like fileId, - // it is useful when file renamed, because it's firing to separate events with old and new path, - // but they can be linked by file id - | kFSEventStreamCreateFlagUseExtendedData - ) - ) { - self.streamRef = ref - FSEventStreamSetDispatchQueue(ref, DispatchQueue.global(qos: .default)) - FSEventStreamStart(ref) - } - } - - deinit { - if let streamRef { - FSEventStreamStop(streamRef) - FSEventStreamInvalidate(streamRef) - FSEventStreamRelease(streamRef) - } - streamRef = nil - } - - /// Cancels the events watcher. - /// This class will have to be re-initialized to begin streaming events again. - public func cancel() { - if let streamRef { - FSEventStreamStop(streamRef) - FSEventStreamInvalidate(streamRef) - FSEventStreamRelease(streamRef) - } - streamRef = nil - } - - /// Handler for the fs event stream. - private func eventStreamHandler( - _ streamRef: ConstFSEventStreamRef, - _ numEvents: Int, - _ eventPaths: UnsafeMutableRawPointer, - _ eventFlags: UnsafePointer, - _ eventIds: UnsafePointer - ) { - guard let eventDictionaries = unsafeBitCast(eventPaths, to: NSArray.self) as? [NSDictionary] else { - return - } - - var events: [Event] = [] - - for (index, dictionary) in eventDictionaries.enumerated() { - // Get get file id use dictionary[kFSEventStreamEventExtendedFileIDKey] as? UInt64 - guard let path = dictionary[kFSEventStreamEventExtendedDataPathKey] as? String, - let event = getEventFromFlags(eventFlags[index]) - else { - continue - } - - events.append(.init(path: path, eventType: event)) - } - - callback(events) - } - - /// Parses an ``FSEvent`` from the raw flag value. - /// - /// Often returns ``FSEvent/changeInDirectory`` as `FSEventStream` returns - /// `kFSEventStreamEventFlagNone (0x00000000)` frequently without more information. - /// - Parameter raw: The int value received from the FSEventStream - /// - Returns: An ``FSEvent`` if a valid one was found, or `nil` otherwise. - func getEventFromFlags(_ raw: FSEventStreamEventFlags) -> FSEvent? { - if raw == 0 { - return .changeInDirectory - } else if raw & UInt32(kFSEventStreamEventFlagRootChanged) > 0 { - return .rootChanged - } else if raw & UInt32(kFSEventStreamEventFlagItemChangeOwner) > 0 { - return .itemChangedOwner - } else if raw & UInt32(kFSEventStreamEventFlagItemCreated) > 0 { - return .itemCreated - } else if raw & UInt32(kFSEventStreamEventFlagItemCloned) > 0 { - return .itemCloned - } else if raw & UInt32(kFSEventStreamEventFlagItemModified) > 0 { - return .itemModified - } else if raw & UInt32(kFSEventStreamEventFlagItemRemoved) > 0 { - return .itemRemoved - } else if raw & UInt32(kFSEventStreamEventFlagItemRenamed) > 0 { - return .itemRenamed - } else { - return nil - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/AreaTabBar.swift b/CodeEdit/Features/CodeEditUI/Views/AreaTabBar.swift deleted file mode 100644 index 5b7796b93b..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/AreaTabBar.swift +++ /dev/null @@ -1,302 +0,0 @@ -// -// AreaTabBar.swift -// CodeEdit -// -// Created by Austin Condiff on 5/25/23. -// - -import SwiftUI - -protocol AreaTab: View, Identifiable, Hashable { - var title: String { get } - var systemImage: String { get } -} - -struct AreaTabBar: View { - @Environment(\.controlActiveState) - private var activeState - - @Binding var items: [Tab] - @Binding var selection: Tab? - - var position: SettingsData.SidebarTabBarPosition - - @State private var tabLocations: [Tab: CGRect] = [:] - @State private var tabWidth: [Tab: CGFloat] = [:] - @State private var tabOffsets: [Tab: CGFloat] = [:] - - /// The tab currently being dragged. - /// - /// It will be `nil` when there is no tab dragged currently. - @State private var draggingTab: Tab? - - /// The start location of dragging. - /// - /// When there is no tab being dragged, it will be `nil`. - @State private var draggingStartLocation: CGFloat? - - /// The last location of dragging. - /// - /// This is used to determine the dragging direction. - /// - TODO: Check if I can use `value.translation` instead. - @State private var draggingLastLocation: CGFloat? - - var body: some View { - if position == .top { - topBody - } else { - sideBody - } - } - - var topBody: some View { - GeometryReader { proxy in - iconsView(size: proxy.size) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .animation(.default, value: items) - } - .clipped() - .frame(maxWidth: .infinity, idealHeight: 27) - .fixedSize(horizontal: false, vertical: true) - } - - var sideBody: some View { - GeometryReader { proxy in - iconsView(size: proxy.size) - .padding(.vertical, 5) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .animation(.default, value: items) - } - .clipped() - .frame(idealWidth: 40, maxHeight: .infinity) - .fixedSize(horizontal: true, vertical: false) - } - - @ViewBuilder - func iconsView(size: CGSize) -> some View { - let layout = position == .top - ? AnyLayout(HStackLayout(spacing: 0)) - : AnyLayout(VStackLayout(spacing: 0)) - layout { - ForEach(items) { icon in - makeIcon(tab: icon, size: size) - .offset( - x: (position == .top) ? (tabOffsets[icon] ?? 0) : 0, - y: (position == .side) ? (tabOffsets[icon] ?? 0) : 0 - ) - .background(makeTabItemGeometryReader(tab: icon)) - .simultaneousGesture(makeAreaTabDragGesture(tab: icon)) - } - if position == .side { - Spacer() - } - } - } - - private func makeIcon( - tab: Tab, - scale: Image.Scale = .medium, - size: CGSize - ) -> some View { - Button { - selection = tab - } label: { - getSafeImage(named: tab.systemImage, accessibilityDescription: tab.title) - .font(.system(size: 12.5)) - .symbolVariant(tab == selection ? .fill : .none) - .help(tab.title) - } - .buttonStyle( - .icon( - isActive: tab == selection, - size: CGSize( - width: position == .side ? 40 : 24, - height: position == .side ? 28 : size.height - ) - ) - ) - } - - private func makeAreaTabDragGesture(tab: Tab) -> some Gesture { - DragGesture(minimumDistance: 2, coordinateSpace: .global) - .onChanged({ value in - if draggingTab != tab { - initializeDragGesture(value: value, for: tab) - } - - // Get the current cursor location - let currentLocation = (position == .top) ? value.location.x : value.location.y - guard let startLocation = draggingStartLocation, - let currentIndex = items.firstIndex(of: tab), - let currentTabWidth = tabWidth[tab], - let lastLocation = draggingLastLocation - else { return } - - let dragDifference = currentLocation - lastLocation - tabOffsets[tab] = currentLocation - startLocation - - // Check for swaps between adjacent tabs - // Left tab - swapTab( - tab: tab, - currentIndex: currentIndex, - currentLocation: currentLocation, - dragDifference: dragDifference, - currentTabWidth: currentTabWidth, - direction: .previous - ) - // Right tab - swapTab( - tab: tab, - currentIndex: currentIndex, - currentLocation: currentLocation, - dragDifference: dragDifference, - currentTabWidth: currentTabWidth, - direction: .next - ) - - // Update the last dragging location if there's enough offset - let currentLocationOnAxis = ((position == .top) ? value.location.x : value.location.y) - if draggingLastLocation == nil || abs(currentLocationOnAxis - draggingLastLocation!) >= 10 { - draggingLastLocation = (position == .top) ? value.location.x : value.location.y - } - }) - .onEnded({ _ in - draggingStartLocation = nil - draggingLastLocation = nil - withAnimation(.easeInOut(duration: 0.25)) { - tabOffsets = [:] - } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { - draggingTab = nil - } - }) - } - - private func initializeDragGesture(value: DragGesture.Value, for tab: Tab) { - draggingTab = tab - let initialLocation = position == .top ? value.startLocation.x : value.startLocation.y - draggingStartLocation = initialLocation - draggingLastLocation = initialLocation - } - - enum SwapDirection { - case previous - case next - } - - // swiftlint: disable function_parameter_count - private func swapTab( - tab: Tab, - currentIndex: Int, - currentLocation: CGFloat, - dragDifference: CGFloat, - currentTabWidth: CGFloat, - direction: SwapDirection - ) { - // Determine the index to swap with based on direction - var swapIndex: Int? - if direction == .previous { - if currentIndex > 0 { - swapIndex = currentIndex - 1 - } - } else { - if currentIndex < items.count - 1 { - swapIndex = currentIndex + 1 - } - } - - // Validate the drag direction - let isValidDragDir = (direction == .previous && dragDifference < 0) || - (direction == .next && dragDifference > 0) - guard let swapIndex = swapIndex, isValidDragDir else { return } - - // Get info about the tab to swap with - let swapTab = items[swapIndex] - guard let swapTabLocation = tabLocations[swapTab], - let swapTabWidth = tabWidth[swapTab] - else { return } - - let isWithinBounds: Bool - if position == .top { - isWithinBounds = direction == .previous ? - isWithinPrevTopBounds(currentLocation, swapTabLocation, swapTabWidth) : - isWithinNextTopBounds(currentLocation, swapTabLocation, swapTabWidth, currentTabWidth) - } else { - isWithinBounds = direction == .previous ? - isWithinPrevBottomBounds(currentLocation, swapTabLocation, swapTabWidth) : - isWithinNextBottomBounds(currentLocation, swapTabLocation, swapTabWidth, currentTabWidth) - } - - // Swap tab positions - if isWithinBounds { - let changing = swapTabWidth - 1 - draggingStartLocation! += direction == .previous ? -changing : changing - tabOffsets[tab]! += direction == .previous ? changing : -changing - items.swapAt(currentIndex, swapIndex) - } - } - // swiftlint: enable function_parameter_count - - private func isWithinPrevTopBounds( - _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat - ) -> Bool { - return curLocation < max( - swapLocation.maxX - swapWidth * 0.1, - swapLocation.minX + swapWidth * 0.9 - ) - } - - private func isWithinNextTopBounds( - _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat - ) -> Bool { - return curLocation > min( - swapLocation.minX + swapWidth * 0.1, - swapLocation.maxX - curWidth * 0.9 - ) - } - - private func isWithinPrevBottomBounds( - _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat - ) -> Bool { - return curLocation < max( - swapLocation.maxY - swapWidth * 0.1, - swapLocation.minY + swapWidth * 0.9 - ) - } - - private func isWithinNextBottomBounds( - _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat - ) -> Bool { - return curLocation > min( - swapLocation.minY + swapWidth * 0.1, - swapLocation.maxY - curWidth * 0.9 - ) - } - - private func makeTabItemGeometryReader(tab: Tab) -> some View { - GeometryReader { geometry in - Rectangle() - .foregroundColor(.clear) - .onAppear { - self.tabWidth[tab] = (position == .top) ? geometry.size.width : geometry.size.height - self.tabLocations[tab] = geometry.frame(in: .global) - } - .onChange(of: geometry.frame(in: .global)) { newFrame in - self.tabLocations[tab] = newFrame - } - .onChange(of: geometry.size.width) { newWidth in - self.tabWidth[tab] = newWidth - } - } - } - - private func getSafeImage(named: String, accessibilityDescription: String?) -> Image { - // We still use the NSImage init to check if a symbol with the name exists. - if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil { - return Image(systemName: named) - } else { - return Image(symbol: named) - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/CEContentUnavailableView.swift b/CodeEdit/Features/CodeEditUI/Views/CEContentUnavailableView.swift deleted file mode 100644 index 0e06271737..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/CEContentUnavailableView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// CEContentUnavailableView.swift -// CodeEdit -// -// Created by Austin Condiff on 11/17/23. -// - -import SwiftUI - -struct CEContentUnavailableView: View { - var label: String - var description: String? - var systemImage: String? - var actions: Actions? - - init( - _ label: String, - description: String? = nil, - systemImage: String? = nil, - @ViewBuilder actions: () -> Actions? = { EmptyView() } - ) { - self.label = label - self.description = description - self.systemImage = systemImage - self.actions = actions() - } - - var contentUnavailableView: some View { - VStack(spacing: 14) { - VStack(spacing: 5) { - if systemImage != nil { - Image(systemName: systemImage ?? "questionmark.app.dashed") - .font(.system(size: 28)) - .foregroundStyle(.tertiary) - .padding(.bottom, 8) - } - Text(label) - .font(.system(size: 16.5, weight: systemImage != nil ? .bold : .regular)) - if description != nil { - Text(description ?? "") - .font(.system(size: 10)) - } - } - if let actionsView = actions { - HStack { actionsView } - } - } - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .contentShape(Rectangle()) - .controlSize(.small) - } - - var body: some View { - if #available(macOS 14, *) { - contentUnavailableView - .buttonStyle(.accessoryBarAction) - } else { - contentUnavailableView - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/CEOutlineGroup.swift b/CodeEdit/Features/CodeEditUI/Views/CEOutlineGroup.swift deleted file mode 100644 index 9715ffb4a5..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/CEOutlineGroup.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// CEOutlineGroup.swift -// CodeEdit -// -// Created by Austin Condiff on 11/27/23. -// - -import SwiftUI - -// This view replaces OutlineGroup, which lacks support for controlling the expanded state. - -struct CEOutlineGroup: View where DataElement: Identifiable, ID: Hashable, Leaf: View { - let root: DataElement - var expandedIds: Binding<[ID: Bool]>? - @State var expanded: Bool = false - let defaultExpanded: Bool? - let childrenKeyPath: KeyPath - let idKeyPath: KeyPath - let content: (DataElement) -> Leaf - - public init( - _ root: DataElement, - id: KeyPath, - defaultExpanded: Bool? = false, - expandedIds: Binding<[ID: Bool]>? = nil, - children: KeyPath, - @ViewBuilder content: @escaping (DataElement) -> Leaf - ) { - self.root = root - self.expandedIds = expandedIds - self.childrenKeyPath = children - self.idKeyPath = id - self.defaultExpanded = defaultExpanded - self.content = content - let rootId = root[keyPath: id] - _expanded = State( - initialValue: expandedIds?.wrappedValue[rootId] ?? defaultExpanded ?? false - ) - } - - var itemView: some View { - content(root) - .id(root[keyPath: idKeyPath]) - .tag(root[keyPath: idKeyPath]) - } - - var body: some View { - switch root[keyPath: childrenKeyPath] { - case .none: - itemView - case .some(let children): - DisclosureGroup(isExpanded: Binding( - get: { - self.expanded - }, - set: { isExpanded in - self.expanded = isExpanded - let id = root[keyPath: idKeyPath] - expandedIds?.wrappedValue[id] = isExpanded - } - )) { - ForEach(children, id: idKeyPath) { - CEOutlineGroup( - $0, - id: idKeyPath, - defaultExpanded: defaultExpanded, - expandedIds: expandedIds, - children: childrenKeyPath, - content: content - ) - } - } label: { - itemView - } - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/Divided.swift b/CodeEdit/Features/CodeEditUI/Views/Divided.swift deleted file mode 100644 index 7a64f5a33c..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/Divided.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Divided.swift -// CodeEdit -// -// Created by Austin Condiff on 11/18/23. -// - -import SwiftUI - -struct Divided: View { - var content: Content - - init(@ViewBuilder content: () -> Content) { - self.content = content() - } - - var body: some View { - _VariadicView.Tree(DividedLayout()) { - content - } - } - - struct DividedLayout: _VariadicView_MultiViewRoot { - @ViewBuilder - func body(children: _VariadicView.Children) -> some View { - let last = children.last?.id - - ForEach(children) { child in - child - - if child.id != last { - Divider() - } - } - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/EffectView.swift b/CodeEdit/Features/CodeEditUI/Views/EffectView.swift deleted file mode 100644 index 0c60b1384f..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/EffectView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// BlurView.swift -// CodeEditModules/CodeEditUI -// -// Created by Rehatbir Singh on 15/03/2022. -// - -import SwiftUI - -/// A SwiftUI Wrapper for `NSVisualEffectView` -/// -/// ## Usage -/// ```swift -/// EffectView(material: .headerView, blendingMode: .withinWindow) -/// ``` -struct EffectView: NSViewRepresentable { - private let material: NSVisualEffectView.Material - private let blendingMode: NSVisualEffectView.BlendingMode - private let emphasized: Bool - - /// Initializes the - /// [`NSVisualEffectView`](https://developer.apple.com/documentation/appkit/nsvisualeffectview) - /// with a - /// [`Material`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/material) and - /// [`BlendingMode`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/blendingmode) - /// - /// By setting the - /// [`emphasized`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/1644721-isemphasized) - /// flag the emphasized state of the material will be used if available. - /// - /// - Parameters: - /// - material: The material to use. Defaults to `.headerView`. - /// - blendingMode: The blending mode to use. Defaults to `.withinWindow`. - /// - emphasized:A Boolean value indicating whether to emphasize the look of the material. Defaults to `false`. - init( - _ material: NSVisualEffectView.Material = .headerView, - blendingMode: NSVisualEffectView.BlendingMode = .withinWindow, - emphasized: Bool = false - ) { - self.material = material - self.blendingMode = blendingMode - self.emphasized = emphasized - } - - func makeNSView(context: Context) -> NSVisualEffectView { - let view = NSVisualEffectView() - view.material = material - view.blendingMode = blendingMode - view.isEmphasized = emphasized - view.state = .followsWindowActiveState - return view - } - - func updateNSView(_ nsView: NSVisualEffectView, context: Context) { - nsView.material = material - nsView.blendingMode = blendingMode - } - - /// Returns the system selection style as an ``EffectView`` if the `condition` is met. - /// Otherwise it returns `Color.clear` - /// - /// - Parameter condition: The condition of when to apply the background. Defaults to `true`. - /// - Returns: A View - @ViewBuilder - static func selectionBackground(_ condition: Bool = true) -> some View { - if condition { - EffectView(.selection, blendingMode: .withinWindow, emphasized: true) - } else { - Color.clear - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/HelpButton.swift b/CodeEdit/Features/CodeEditUI/Views/HelpButton.swift deleted file mode 100644 index f46dea8fed..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/HelpButton.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// HelpButton.swift -// CodeEditModules/CodeEditUI -// -// Created by Lukas Pistrol on 30.03.22. -// - -import SwiftUI - -/// A Button representing a system Help button displaying a question mark symbol. -struct HelpButton: View { - - private var action: () -> Void - - /// Initializes the ``HelpButton`` with an action closure - /// - Parameter action: A closure that gets called once the button is pressed. - init(action: @escaping () -> Void) { - self.action = action - } - - var body: some View { - Button(action: action, label: { - ZStack { - Circle() - .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5) - .background(Circle().foregroundColor(Color(NSColor.controlColor))) - .shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 0.5) - .shadow(color: Color(NSColor.shadowColor).opacity(0.3), radius: 1, y: 0.5) - .frame(width: 20, height: 20) - Image(systemName: "questionmark") - .font(.system(size: 12.5, weight: .medium)) - } - }) - .buttonStyle(PlainButtonStyle()) - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/IconButtonStyle.swift b/CodeEdit/Features/CodeEditUI/Views/IconButtonStyle.swift deleted file mode 100644 index dfc6ff7852..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/IconButtonStyle.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// IconButtonStyle.swift -// CodeEdit -// -// Created by Austin Condiff on 11/9/23. -// - -import SwiftUI - -struct IconButtonStyle: ButtonStyle { - var isActive: Bool? - var font: Font? - var size: CGSize? - - init(isActive: Bool? = nil, font: Font? = nil, size: CGFloat? = nil) { - self.isActive = isActive - self.font = font - self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0) - } - - init(isActive: Bool? = nil, font: Font? = nil, size: CGSize? = nil) { - self.isActive = isActive - self.font = font - self.size = size - } - - init(isActive: Bool? = nil, font: Font? = nil) { - self.isActive = isActive - self.font = font - self.size = nil - } - - func makeBody(configuration: ButtonStyle.Configuration) -> some View { - IconButton( - configuration: configuration, - isActive: isActive, - font: font, - size: size - ) - } - - struct IconButton: View { - let configuration: ButtonStyle.Configuration - var isActive: Bool - var font: Font - var size: CGSize? - @Environment(\.controlActiveState) - private var controlActiveState - @Environment(\.isEnabled) - private var isEnabled: Bool - @Environment(\.colorScheme) - private var colorScheme - - init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGFloat?) { - self.configuration = configuration - self.isActive = isActive ?? false - self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) - self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0) - } - - init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGSize?) { - self.configuration = configuration - self.isActive = isActive ?? false - self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) - self.size = size ?? nil - } - - init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?) { - self.configuration = configuration - self.isActive = isActive ?? false - self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) - self.size = nil - } - - var body: some View { - configuration.label - .font(font) - .foregroundColor( - isActive - ? Color(.controlAccentColor) - : Color(.secondaryLabelColor) - ) - .frame(width: size?.width, height: size?.height, alignment: .center) - .contentShape(Rectangle()) - .brightness( - configuration.isPressed - ? colorScheme == .dark - ? 0.5 - : isActive ? -0.25 : -0.75 - : 0 - ) - .opacity(controlActiveState == .inactive ? 0.5 : 1) - .symbolVariant(isActive ? .fill : .none) - } - } -} - -extension ButtonStyle where Self == IconButtonStyle { - static func icon( - isActive: Bool? = false, - font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), - size: CGFloat? = 24 - ) -> IconButtonStyle { - return IconButtonStyle(isActive: isActive, font: font, size: size) - } - static func icon( - isActive: Bool? = false, - font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), - size: CGSize? = CGSize(width: 24, height: 24) - ) -> IconButtonStyle { - return IconButtonStyle(isActive: isActive, font: font, size: size) - } - static func icon( - isActive: Bool? = false, - font: Font? = Font.system(size: 14.5, weight: .regular, design: .default) - ) -> IconButtonStyle { - return IconButtonStyle(isActive: isActive, font: font) - } - static var icon: IconButtonStyle { .init() } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/IconToggleStyle.swift b/CodeEdit/Features/CodeEditUI/Views/IconToggleStyle.swift deleted file mode 100644 index 2382dfc346..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/IconToggleStyle.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// IconToggleStyle.swift -// CodeEdit -// -// Created by Austin Condiff on 11/9/23. -// - -import SwiftUI - -struct IconToggleStyle: ToggleStyle { - var font: Font? - var size: CGSize? - - @State var isPressing = false - - init(font: Font? = nil, size: CGFloat? = nil) { - self.font = font - self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0) - } - - init(font: Font? = nil, size: CGSize? = nil) { - self.font = font - self.size = size - } - - init(font: Font? = nil) { - self.font = font - self.size = nil - } - - func makeBody(configuration: ToggleStyle.Configuration) -> some View { - Button( - action: { configuration.isOn.toggle() }, - label: { configuration.label } - ) - .buttonStyle(.icon(isActive: configuration.isOn, font: font, size: size)) - } -} - -extension ToggleStyle where Self == IconToggleStyle { - static func icon( - font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), - size: CGFloat? = 24 - ) -> IconToggleStyle { - return IconToggleStyle(font: font, size: size) - } - static func icon( - font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), - size: CGSize? = CGSize(width: 24, height: 24) - ) -> IconToggleStyle { - return IconToggleStyle(font: font, size: size) - } - static func icon( - font: Font? = Font.system(size: 14.5, weight: .regular, design: .default) - ) -> IconToggleStyle { - return IconToggleStyle(font: font) - } - static var icon: IconToggleStyle { .init() } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/OverlayPanel.swift b/CodeEdit/Features/CodeEditUI/Views/OverlayPanel.swift deleted file mode 100644 index d8b456179c..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/OverlayPanel.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// OverlayPanel.swift -// CodeEditModules/CodeEditUI -// -// Created by Pavel Kasila on 20.03.22. -// - -import Cocoa - -final class OverlayPanel: NSPanel, NSWindowDelegate { - init() { - super.init( - contentRect: NSRect(x: 0, y: 0, width: 500, height: 48), - styleMask: [.fullSizeContentView, .titled, .resizable], - backing: .buffered, defer: false - ) - self.delegate = self - self.center() - self.titlebarAppearsTransparent = true - self.isMovableByWindowBackground = true - } - - override func standardWindowButton(_ button: NSWindow.ButtonType) -> NSButton? { - let button = super.standardWindowButton(button) - button?.isHidden = true - return button - } - - func windowDidResignKey(_ notification: Notification) { - if let panel = notification.object as? OverlayPanel { - panel.close() - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/OverlayView.swift b/CodeEdit/Features/CodeEditUI/Views/OverlayView.swift deleted file mode 100644 index a997e980de..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/OverlayView.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// OverlayWindow.swift -// CodeEdit -// -// Created by Khan Winter on 3/17/23. -// - -import Foundation -import SwiftUI - -struct OverlayView: View { - @ViewBuilder let rowViewBuilder: ((Option) -> RowView) - @ViewBuilder let previewViewBuilder: ((Option) -> PreviewView)? - - @Binding var options: [Option] - @Binding var text: String - - @State var selection: Option? - @State var previewVisible: Bool = true - - let title: String - let image: Image - let hasPreview: Bool - let onRowClick: ((Option) -> Void) - let onClose: (() -> Void) - let alwaysShowOptions: Bool - let optionRowHeight: CGFloat - - init( - title: String, - image: Image, - options: Binding<[Option]>, - text: Binding, - alwaysShowOptions: Bool = false, - optionRowHeight: CGFloat = 30, - content: @escaping ((Option) -> RowView), - preview: ((Option) -> PreviewView)? = nil, - onRowClick: @escaping ((Option) -> Void), - onClose: @escaping () -> Void - ) { - self.title = title - self.image = image - self._options = options - self._text = text - self.rowViewBuilder = content - self.previewViewBuilder = preview - self.onRowClick = onRowClick - self.onClose = onClose - self.hasPreview = preview != nil - self.alwaysShowOptions = alwaysShowOptions - self.optionRowHeight = optionRowHeight - } - - var body: some View { - VStack(spacing: 0) { - VStack { - HStack(alignment: .center, spacing: 0) { - image - .font(.system(size: 18)) - .foregroundColor(.secondary) - .padding(.leading, 1) - .padding(.trailing, 10) - TextField(title, text: $text) - .font(.system(size: 20, weight: .light, design: .default)) - .textFieldStyle(.plain) - .onSubmit { - if let selection { - onRowClick(selection) - } else { - NSSound.beep() - } - } - .task(id: options) { - if options.isEmpty { - selection = nil - } else { - if !options.isEmpty { - selection = options.first - } - } - } - if hasPreview { - PreviewToggle(previewVisible: $previewVisible) - .onTapGesture { - withAnimation { - previewVisible.toggle() - } - } - } - } - .padding(.vertical, 12) - .padding(.horizontal, 12) - .foregroundColor(.primary.opacity(0.85)) - .background(EffectView(.sidebar, blendingMode: .behindWindow)) - } - if !text.isEmpty || alwaysShowOptions == true { - Divider() - .padding(0) - HStack(spacing: 0) { - if options.isEmpty { - Text("No matching options") - .font(.system(size: 17)) - .foregroundColor(.secondary) - .frame(maxWidth: hasPreview ? 272 : .infinity, maxHeight: .infinity) - } else { - NSTableViewWrapper( - data: options, - rowHeight: optionRowHeight, - selection: $selection, - itemView: rowViewBuilder - ) - .frame(maxWidth: hasPreview && previewVisible ? 272 : .infinity) - } - if hasPreview && previewVisible { - Divider() - if options.isEmpty { - Spacer() - .frame(maxWidth: .infinity) - } else { - if let selection, let previewViewBuilder { - previewViewBuilder(selection) - .clipped() - .frame(maxWidth: .infinity) - .transition(.move(edge: .trailing)) - } else { - Text("Select an option to preview") - .frame(maxWidth: .infinity) - } - } - } - } - } - } - .overlay { - keyHandlers - } - .background(EffectView(.sidebar, blendingMode: .behindWindow)) - .edgesIgnoringSafeArea(.vertical) - .frame( - minWidth: 680, - minHeight: text.isEmpty && !alwaysShowOptions ? 19 : 400, - maxHeight: text.isEmpty && !alwaysShowOptions ? 19 : .infinity - ) - } - - @ViewBuilder var keyHandlers: some View { - Button { - onClose() - } label: { EmptyView() } - .opacity(0) - .keyboardShortcut(.escape, modifiers: []) - .accessibilityLabel("Close Overlay") - Button { - guard selection != options.first else { - return - } - if let selection, let index = options.firstIndex(of: selection) { - self.selection = options[index-1] - } else { - selection = options.first - } - } label: { EmptyView() } - .opacity(0) - .keyboardShortcut(.upArrow, modifiers: []) - .accessibilityLabel("Select Up") - Button { - guard selection != options.last else { - return - } - if let selection, let index = options.firstIndex(of: selection) { - - self.selection = options[index+1] - } else { - selection = options.first - } - } label: { EmptyView() } - .opacity(0) - .keyboardShortcut(.downArrow, modifiers: []) - .accessibilityLabel("Select Down") - } -} - -struct PreviewToggle: View { - @Binding var previewVisible: Bool - - var body: some View { - ZStack { - Rectangle() - .fill(Color(NSColor.secondaryLabelColor)) - .frame(width: previewVisible ? 12 : 14, height: 1) - .offset(CGSize(width: 0, height: -2.5)) - if !previewVisible { - Rectangle() - .fill(Color(NSColor.secondaryLabelColor)) - .frame(width: 1, height: 8) - .offset(CGSize(width: -2.5, height: 2)) - } - RoundedRectangle(cornerRadius: 2, style: .continuous) - .strokeBorder(Color(NSColor.secondaryLabelColor), lineWidth: 1) - .frame(width: previewVisible ? 14 : 16, height: 14) - } - .frame(width: 16, height: 16) - .padding(4) - .contentShape(Rectangle()) - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift b/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift deleted file mode 100644 index 560a451526..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// PaneTextField.swift -// CodeEdit -// -// Created by Austin Condiff on 11/2/23. -// - -import SwiftUI -import Combine -import Introspect - -struct PaneTextField: View { - @Environment(\.colorScheme) - var colorScheme - - @Environment(\.controlActiveState) - private var controlActive - - @FocusState private var isFocused: Bool - - var label: String - - @Binding private var text: String - - let axis: Axis - - let leadingAccessories: LeadingAccessories? - - let trailingAccessories: TrailingAccessories? - - var clearable: Bool - - var onClear: (() -> Void) - - var hasValue: Bool - - init( - _ label: String, - text: Binding, - axis: Axis? = .horizontal, - @ViewBuilder leadingAccessories: () -> LeadingAccessories? = { EmptyView() }, - @ViewBuilder trailingAccessories: () -> TrailingAccessories? = { EmptyView() }, - clearable: Bool? = false, - onClear: (() -> Void)? = {}, - hasValue: Bool? = false - ) { - self.label = label - _text = text - self.axis = axis ?? .horizontal - self.leadingAccessories = leadingAccessories() - self.trailingAccessories = trailingAccessories() - self.clearable = clearable ?? false - self.onClear = onClear ?? {} - self.hasValue = hasValue ?? false - } - - @ViewBuilder - public func selectionBackground( - _ isFocused: Bool = false - ) -> some View { - if self.controlActive != .inactive || !text.isEmpty || hasValue { - if isFocused || !text.isEmpty || hasValue { - Color(.textBackgroundColor) - } else { - if colorScheme == .light { - Color.black.opacity(0.06) - } else { - Color.white.opacity(0.24) - } - } - } else { - if colorScheme == .light { - Color.clear - } else { - Color.white.opacity(0.14) - } - } - } - - var body: some View { - HStack(alignment: .top, spacing: 0) { - if let leading = leadingAccessories { - leading - .frame(height: 20) - } - VStack { - TextField(label, text: $text, axis: axis) - .textFieldStyle(.plain) - .focused($isFocused) - .controlSize(.small) - .padding(.horizontal, 8) - .padding(.vertical, 3.5) - .foregroundStyle(.primary) - } - if clearable == true { - Button { - self.text = "" - onClear() - } label: { - Image(systemName: "xmark.circle.fill") - } - .buttonStyle(.icon(font: .system(size: 11, weight: .semibold), size: CGSize(width: 20, height: 20))) - .opacity(text.isEmpty ? 0 : 1) - .disabled(text.isEmpty) - } - if let trailing = trailingAccessories { - trailing - } - } - .fixedSize(horizontal: false, vertical: true) - .buttonStyle(.icon(font: .system(size: 11, weight: .semibold), size: CGSize(width: 28, height: 20))) - .toggleStyle(.icon(font: .system(size: 11, weight: .semibold), size: CGSize(width: 28, height: 20))) - .frame(minHeight: 22) - .background( - selectionBackground(isFocused) - .clipShape(RoundedRectangle(cornerRadius: 6)) - .edgesIgnoringSafeArea(.all) - ) - .overlay( - RoundedRectangle(cornerRadius: 6) - .stroke(isFocused || !text.isEmpty || hasValue ? .tertiary : .quaternary, lineWidth: 1.25) - .cornerRadius(6) - .disabled(true) - .edgesIgnoringSafeArea(.all) - ) - - .onTapGesture { - isFocused = true - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/PanelDivider.swift b/CodeEdit/Features/CodeEditUI/Views/PanelDivider.swift deleted file mode 100644 index abca2627c5..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/PanelDivider.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// PanelDivider.swift -// -// -// Created by Austin Condiff on 5/10/22. -// - -import SwiftUI - -struct PanelDivider: View { - @Environment(\.colorScheme) - private var colorScheme - - var body: some View { - Divider() - .opacity(0) - .overlay( - Color(.black) - .opacity(colorScheme == .dark ? 0.65 : 0.13) - ) - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/PressActionsModifier.swift b/CodeEdit/Features/CodeEditUI/Views/PressActionsModifier.swift deleted file mode 100644 index 9fb32ae3c5..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/PressActionsModifier.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// PressActionsModifier.swift -// CodeEditModules/CodeEditUI -// -// Created by Gabriel Theodoropoulos on 1/11/20. -// - -import SwiftUI - -struct PressActions: ViewModifier { - var onPress: () -> Void - var onRelease: (() -> Void)? - - init(onPress: @escaping () -> Void, onRelease: (() -> Void)? = nil) { - self.onPress = onPress - self.onRelease = onRelease - } - - func body(content: Content) -> some View { - content - .simultaneousGesture( - DragGesture(minimumDistance: 0) - .onChanged({ _ in onPress() }) - .onEnded({ _ in onRelease?() }) - ) - } -} - -extension View { - - /// A custom view modifier for press actions with callbacks for `onPress` and `onRelease`. - /// - Parameters: - /// - onPress: Action to perform once the view is pressed. - /// - onRelease: Action to perform once the view press is released. - /// - Returns: some View - func pressAction(onPress: @escaping (() -> Void), onRelease: (() -> Void)? = nil) -> some View { - modifier(PressActions(onPress: onPress, onRelease: onRelease)) - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift b/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift deleted file mode 100644 index 0b142e54e1..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// SegmentedControl.swift -// CodeEditModules/CodeEditUI -// -// Created by Lukas Pistrol on 31.03.22. -// - -import SwiftUI - -/// A view that creates a segmented control from an array of text labels. -struct SegmentedControl: View { - private var options: [String] - private var prominent: Bool - - @Binding private var preselectedIndex: Int - - /// A view that creates a segmented control from an array of text labels. - /// - Parameters: - /// - selection: The index of the current selected item. - /// - options: the options to display as an array of strings. - /// - prominent: A Bool indicating whether to use a prominent appearance instead - /// of the muted selection color. Defaults to `false`. - init( - _ selection: Binding, - options: [String], - prominent: Bool = false - ) { - self._preselectedIndex = selection - self.options = options - self.prominent = prominent - } - - var body: some View { - HStack(spacing: 4) { - ForEach(options.indices, id: \.self) { index in - SegmentedControlItem( - label: options[index], - active: preselectedIndex == index, - action: { - preselectedIndex = index - }, - prominent: prominent - ) - - } - } - .frame(height: 20) - } -} - -struct SegmentedControlItem: View { - private let color: Color = Color(nsColor: .selectedControlColor) - let label: String - let active: Bool - let action: () -> Void - let prominent: Bool - - @Environment(\.colorScheme) - private var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - @State var isHovering: Bool = false - - @State var isPressing: Bool = false - - var body: some View { - Text(label) - .font(.subheadline) - .foregroundColor(textColor) - .opacity(textOpacity) - .frame(height: 20) - .padding(.horizontal, 7.5) - .background( - background - ) - .cornerRadius(5) - .onTapGesture { - action() - } - .onHover { hover in - isHovering = hover - } - .pressAction { - isPressing = true - } onRelease: { - isPressing = false - } - - } - - private var textColor: Color { - if prominent { - return active - ? .white - : .primary - } else { - return active - ? colorScheme == .dark ? .white : .accentColor - : .primary - } - } - - private var textOpacity: Double { - if prominent { - return activeState != .inactive ? 1 : active ? 1 : 0.3 - } else { - return activeState != .inactive ? 1 : active ? 0.5 : 0.3 - } - } - - @ViewBuilder private var background: some View { - if prominent { - if active { - Color.accentColor.opacity(activeState != .inactive ? 1 : 0.5) - } else { - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(isPressing ? 0.10 : isHovering ? 0.05 : 0) - } - } else { - if active { - color.opacity(isPressing ? 1 : activeState != .inactive ? 0.75 : 0.5) - } else { - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(isPressing ? 0.10 : isHovering ? 0.05 : 0) - } - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/SegmentedControlImproved.swift b/CodeEdit/Features/CodeEditUI/Views/SegmentedControlImproved.swift deleted file mode 100644 index 640b5fe220..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/SegmentedControlImproved.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// SegmentedControlImproved.swift -// CodeEdit -// -// Created by Wouter Hennen on 22/05/2023. -// - -import SwiftUI - -extension ButtonStyle where Self == XcodeButtonStyle { - static func xcodeButton( - isActive: Bool, - prominent: Bool, - isHovering: Bool, - namespace: Namespace.ID = Namespace().wrappedValue - ) -> XcodeButtonStyle { - XcodeButtonStyle(isActive: isActive, prominent: prominent, isHovering: isHovering, namespace: namespace) - } -} - -struct XcodeButtonStyle: ButtonStyle { - var isActive: Bool - var prominent: Bool - var isHovering: Bool - var namespace: Namespace.ID - - @Environment(\.controlSize) - var controlSize - - @Environment(\.colorScheme) - var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(.horizontal, controlSizePadding.horizontal) - .padding(.vertical, controlSizePadding.vertical) - .font(fontSize) - .foregroundColor(isActive ? .white : .primary) - .opacity(textOpacity) - .background { - if isActive { - RoundedRectangle(cornerRadius: 5) - .foregroundColor(.accentColor) - .opacity(configuration.isPressed ? (prominent ? 0.75 : 0.5) : (prominent ? 1 : 0.75)) - .matchedGeometryEffect(id: "xcodebuttonbackground", in: namespace) - - } else if isHovering { - RoundedRectangle(cornerRadius: 5) - .foregroundColor(.gray) - .opacity(0.2) - .transition(.opacity) - .animation(.easeInOut, value: isHovering) - } - } - .opacity(activeState == .inactive ? 0.6 : 1) - .animation(.interpolatingSpring(stiffness: 600, damping: 50), value: isActive) - } - - var fontSize: Font { - switch controlSize { - case .mini: - return .footnote - case .small, .regular: - return .subheadline - default: - return .callout - } - } - - var controlSizePadding: (vertical: CGFloat, horizontal: CGFloat) { - switch controlSize { - case .mini: - return (1, 2) - case .small: - return (2, 4) - case .regular: - return (3, 8) - case .large: - return (6, 12) - @unknown default: - return (4, 8) - } - } - - private var textOpacity: Double { - if prominent { - return activeState != .inactive ? 1 : isActive ? 1 : 0.3 - } else { - return activeState != .inactive ? 1 : isActive ? 0.5 : 0.3 - } - } -} - -private struct MyTag: _ViewTraitKey { - static var defaultValue: AnyHashable? = Optional.none -} - -extension View { - func segmentedTag(_ value: Value) -> some View { - _trait(MyTag.self, value) - } -} - -struct SegmentedControlV2: View { - @Binding var selection: Selection - var prominent: Bool - @ViewBuilder var content: Content - - @State private var hoveringOver: Selection? - - @Namespace var namespace - - var body: some View { - content.variadic { children in - HStack(spacing: 8) { - ForEach(children, id: \.id) { option in - let tag: Selection? = option[MyTag.self].flatMap { $0 as? Selection } - Button { - hoveringOver = nil - if let tag { - selection = tag - } - } label: { - option - } - .buttonStyle( - .xcodeButton( - isActive: tag == selection, - prominent: prominent, - isHovering: tag == hoveringOver, - namespace: namespace - ) - ) - .onHover { hover in - hoveringOver = hover ? tag : nil - } - .animation(.interpolatingSpring(stiffness: 600, damping: 50), value: selection) - } - } - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/SettingsTextEditor.swift b/CodeEdit/Features/CodeEditUI/Views/SettingsTextEditor.swift deleted file mode 100644 index 54328c2c82..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/SettingsTextEditor.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// SettingsTextEditor.swift -// -// -// Created by Andrey Plotnikov on 07.05.2022. -// - -import Foundation -import SwiftUI - -struct SettingsTextEditor: View { - @State private var isFocus: Bool = false - - @Binding var text: String - - init(text: Binding) { - self._text = text - } - - var body: some View { - Representable(text: $text, isFocused: $isFocus) - .overlay(focusOverlay) - } - - private var focusOverlay: some View { - Rectangle().stroke(Color.accentColor.opacity(isFocus ? 0.4 : 0), lineWidth: 2) - } -} - -private extension SettingsTextEditor { - struct Representable: NSViewRepresentable { - - @Binding var text: String - @Binding var isFocused: Bool - - func makeNSView(context: Context) -> NSScrollView { - let scrollView = NSTextView.scrollableTextView() - scrollView.verticalScroller?.alphaValue = 0 - let textView = scrollView.documentView as? NSTextView - textView?.backgroundColor = .windowBackgroundColor - textView?.isEditable = true - textView?.delegate = context.coordinator - textView?.string = text - return scrollView - } - - func updateNSView(_ nsView: NSScrollView, context: Context) { - - } - - func makeCoordinator() -> Coordinator { - Coordinator(parent: self) - } - - class Coordinator: NSObject, NSTextViewDelegate { - var parent: Representable - - init(parent: Representable) { - self.parent = parent - } - - func textDidBeginEditing(_ notification: Notification) { - parent.isFocused = true - } - - func textDidEndEditing(_ notification: Notification) { - parent.isFocused = false - } - - func textDidChange(_ notification: Notification) { - guard let textView = notification.object as? NSTextView else { - return - } - // Update text - self.parent.text = textView.string - } - } - } - -} diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift deleted file mode 100644 index af69745b09..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ /dev/null @@ -1,221 +0,0 @@ -// -// ToolbarBranchPicker.swift -// CodeEditModules/CodeEditUI -// -// Created by Lukas Pistrol on 21.04.22. -// - -import SwiftUI -import CodeEditSymbols -import Combine - -/// A view that pops up a branch picker. -struct ToolbarBranchPicker: View { - private var workspaceFileManager: CEWorkspaceFileManager? - private var sourceControlManager: SourceControlManager? - - @Environment(\.controlActiveState) - private var controlActive - - @State private var isHovering: Bool = false - @State private var displayPopover: Bool = false - @State private var currentBranch: GitBranch? - - /// Initializes the ``ToolbarBranchPicker`` with an instance of a `WorkspaceClient` - /// - Parameter workspace: An instance of the current `WorkspaceClient` - init( - workspaceFileManager: CEWorkspaceFileManager? - ) { - self.workspaceFileManager = workspaceFileManager - self.sourceControlManager = workspaceFileManager?.sourceControlManager - } - - var body: some View { - HStack(alignment: .center, spacing: 7) { - Group { - if currentBranch != nil { - Image(symbol: "branch") - } else { - Image(systemName: "folder.fill.badge.gearshape") - } - } - .foregroundColor(controlActive == .inactive ? inactiveColor : .secondary) - .font(.system(size: 14)) - .imageScale(.medium) - .frame(width: 17, height: 17) - VStack(alignment: .leading, spacing: 0) { - Text(title) - .font(.headline) - .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) - .frame(height: 16) - .help(title) - if let currentBranch { - Menu(content: { - if let sourceControlManager = workspaceFileManager?.sourceControlManager { - PopoverView(sourceControlManager: sourceControlManager) - } - }, label: { - Text(currentBranch.name) - .font(.subheadline) - .foregroundColor(controlActive == .inactive ? inactiveColor : .gray) - .frame(height: 11) - }) - .menuIndicator(isHovering ? .visible : .hidden) - .buttonStyle(.borderless) - .padding(.leading, -3) - .padding(.bottom, 2) - } - } - } - .onHover { active in - isHovering = active - } - .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { (_) in - Task { - await sourceControlManager?.refreshCurrentBranch() - } - } - .onReceive( - self.sourceControlManager?.$currentBranch.eraseToAnyPublisher() ?? - Empty().eraseToAnyPublisher() - ) { branch in - self.currentBranch = branch - } - .task { - await self.sourceControlManager?.refreshCurrentBranch() - await self.sourceControlManager?.refreshBranches() - } - } - - private var inactiveColor: Color { - Color(nsColor: .disabledControlTextColor) - } - - private var title: String { - workspaceFileManager?.folderUrl.lastPathComponent ?? "Empty" - } - - // MARK: Popover View - - /// A popover view that appears once the branch picker is tapped. - /// - /// It displays the currently checked-out branch and all other local branches. - private struct PopoverView: View { - @ObservedObject var sourceControlManager: SourceControlManager - - var body: some View { - VStack(alignment: .leading) { - if let currentBranch = sourceControlManager.currentBranch { - Section { - headerLabel("Current Branch") - BranchCell(sourceControlManager: sourceControlManager, branch: currentBranch, active: true) - } - } - - let branches = sourceControlManager.branches - .filter({ $0.isLocal && $0 != sourceControlManager.currentBranch }) - let branchesGroups = branches.reduce(into: [String: GitBranchesGroup]()) { result, branch in - guard let branchPrefix = branch.name.components(separatedBy: "/").first else { - return - } - - result[ - branchPrefix, - default: GitBranchesGroup(name: branchPrefix, branches: []) - ].branches.append(branch) - } - - if !branches.isEmpty { - Section { - headerLabel("Branches") - ForEach(branchesGroups.keys.sorted(), id: \.self) { branchGroupPrefix in - if let group = branchesGroups[branchGroupPrefix] { - if !group.shouldNest { - BranchCell( - sourceControlManager: sourceControlManager, - branch: group.branches.first! - ) - } else { - Menu(content: { - ForEach(group.branches, id: \.self) { branch in - BranchCell( - sourceControlManager: sourceControlManager, - branch: branch, - title: String( - branch.name.suffix(branch.name.count - branchGroupPrefix.count - 1) - ) - ) - } - }, label: { - HStack { - Image(systemName: "folder") - Text(group.name) - } - }) - } - } - } - } - } - } - .padding(.top, 10) - .padding(5) - .frame(width: 340) - } - - func headerLabel(_ title: String) -> some View { - Text(title) - .font(.subheadline.bold()) - .foregroundColor(.secondary) - .padding(.horizontal) - .padding(.vertical, 5) - } - - // MARK: Branch Cell - - /// A Button Cell that represents a branch in the branch picker - struct BranchCell: View { - let sourceControlManager: SourceControlManager - let branch: GitBranch - let active: Bool - let title: String? - - init( - sourceControlManager: SourceControlManager, - branch: GitBranch, - active: Bool = false, - title: String? = nil - ) { - self.sourceControlManager = sourceControlManager - self.branch = branch - self.active = active - self.title = title - } - - var body: some View { - Button { - switchBranch() - } label: { - HStack { - if active { - Image(systemName: "checkmark.circle.fill") - } else { - Image.branch - } - Text(self.title ?? branch.name) - } - } - } - - func switchBranch() { - Task { - do { - try await sourceControlManager.checkoutBranch(branch: branch) - } catch { - await sourceControlManager.showAlertForError(title: "Failed to checkout", error: error) - } - } - } - } - } -} diff --git a/CodeEdit/Features/CodeEditUI/Views/TrackableScrollView.swift b/CodeEdit/Features/CodeEditUI/Views/TrackableScrollView.swift deleted file mode 100644 index 32a1a7acbb..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/TrackableScrollView.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// TrackableScrollView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 19.01.23. -// - -// Inspired by SwiftUITrackableScrollView by Natchanon A. -// https://github.com/maxnatchanon/trackable-scroll-view - -import SwiftUI - -private struct ScrollViewOffsetPreferenceKey: PreferenceKey { - typealias Value = [CGFloat] - - static var defaultValue: [CGFloat] = [0] - - static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) { - value.append(contentsOf: nextValue()) - } -} - -struct TrackableScrollView: View where Content: View { - let axes: Axis.Set - let showIndicators: Bool - @Binding var contentOffset: CGFloat - @Binding var contentTrailingOffset: CGFloat? - let content: Content - - init( - _ axes: Axis.Set = .vertical, - showIndicators: Bool = true, - contentOffset: Binding, - @ViewBuilder content: () -> Content - ) { - self.axes = axes - self.showIndicators = showIndicators - self._contentOffset = contentOffset - self._contentTrailingOffset = Binding.constant(nil) - self.content = content() - } - - init( - _ axes: Axis.Set = .vertical, - showIndicators: Bool = true, - contentOffset: Binding, - contentTrailingOffset: Binding?, - @ViewBuilder content: () -> Content - ) { - self.axes = axes - self.showIndicators = showIndicators - self._contentOffset = contentOffset - self._contentTrailingOffset = contentTrailingOffset ?? Binding.constant(nil) - self.content = content() - } - - var body: some View { - GeometryReader { outsideProxy in - ScrollView(self.axes, showsIndicators: self.showIndicators) { - ZStack(alignment: self.axes == .vertical ? .top : .leading) { - GeometryReader { insideProxy in - Color.clear - .preference( - key: ScrollViewOffsetPreferenceKey.self, - value: [ - self.calculateContentOffset( - fromOutsideProxy: outsideProxy, - insideProxy: insideProxy - ), - self.calculateContentTrailingOffset( - fromOutsideProxy: outsideProxy, - insideProxy: insideProxy - ) - ] - ) - } - VStack { - self.content - } - } - } - .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in - self.contentOffset = value[0] - if self.contentTrailingOffset != nil { - self.contentTrailingOffset = value[1] - } - } - } - } - - private func calculateContentOffset( - fromOutsideProxy outsideProxy: GeometryProxy, - insideProxy: GeometryProxy - ) -> CGFloat { - if axes == .vertical { - return insideProxy.frame(in: .global).minY - outsideProxy.frame(in: .global).minY - } else { - return insideProxy.frame(in: .global).minX - outsideProxy.frame(in: .global).minX - } - } - - private func calculateContentTrailingOffset( - fromOutsideProxy outsideProxy: GeometryProxy, - insideProxy: GeometryProxy - ) -> CGFloat { - if axes == .vertical { - return insideProxy.frame(in: .global).maxY - outsideProxy.frame(in: .global).maxY - } else { - return insideProxy.frame(in: .global).maxX - outsideProxy.frame(in: .global).maxX - } - } -} diff --git a/CodeEdit/Features/CodeFile/CodeFileDocument.swift b/CodeEdit/Features/CodeFile/CodeFileDocument.swift deleted file mode 100644 index 9cdde5b239..0000000000 --- a/CodeEdit/Features/CodeFile/CodeFileDocument.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// CodeFile.swift -// CodeEditModules/CodeFile -// -// Created by Rehatbir Singh on 12/03/2022. -// - -import AppKit -import Foundation -import SwiftUI -import UniformTypeIdentifiers -import QuickLookUI -import CodeEditSourceEditor -import CodeEditTextView -import CodeEditLanguages -import Combine - -enum CodeFileError: Error { - case failedToDecode - case failedToEncode - case fileTypeError -} - -@objc(CodeFileDocument) -final class CodeFileDocument: NSDocument, ObservableObject, QLPreviewItem { - struct OpenOptions { - let cursorPositions: [CursorPosition] - } - - @Published var content = "" - - /// Used to override detected languages. - @Published var language: CodeLanguage? - - /// Document-specific overridden indent option. - @Published var indentOption: SettingsData.TextEditingSettings.IndentOption? - - /// Document-specific overridden tab width. - @Published var defaultTabWidth: Int? - - /// Document-specific overridden line wrap preference. - @Published var wrapLines: Bool? - - /* - This is the main type of the document. - For example, if the file is end with '.png', it will be an image, - if the file is end with '.py', it will be a text file. - If text content is not empty, return text - If its neither image or text, this could be nil. - */ - var typeOfFile: UTType? { - if !self.content.isEmpty { - return UTType.text - } - guard let fileType, let type = UTType(fileType) else { - return nil - } - if type.conforms(to: UTType.image) { - return UTType.image - } - if type.conforms(to: UTType.text) { - return UTType.text - } - if type.conforms(to: .data) { - return .data - } - return nil - } - - /* - This is the QLPreviewItemURL - */ - var previewItemURL: URL? { - fileURL - } - - /// Specify options for opening the file such as the initial cursor positions. - /// Nulled by ``CodeFileView`` on first load. - var openOptions: OpenOptions? - - private let isDocumentEditedSubject = PassthroughSubject() - - /// Publisher for isDocumentEdited property - var isDocumentEditedPublisher: AnyPublisher { - isDocumentEditedSubject.eraseToAnyPublisher() - } - - // MARK: - NSDocument - - override class var autosavesInPlace: Bool { - Settings.shared.preferences.general.isAutoSaveOn - } - - override var autosavingFileType: String? { - Settings.shared.preferences.general.isAutoSaveOn - ? fileType - : nil - } - - override func makeWindowControllers() { - let window = NSWindow( - contentRect: NSRect(x: 0, y: 0, width: 750, height: 800), - styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], - backing: .buffered, defer: false - ) - let windowController = NSWindowController(window: window) - if let fileURL { - windowController.shouldCascadeWindows = false - windowController.windowFrameAutosaveName = fileURL.path - } - addWindowController(windowController) - - window.contentView = NSHostingView(rootView: SettingsInjector { - WindowCodeFileView(codeFile: self) - }) - - window.makeKeyAndOrderFront(nil) - - if let fileURL, UserDefaults.standard.object(forKey: "NSWindow Frame \(fileURL.path)") == nil { - window.center() - } - } - - override func data(ofType _: String) throws -> Data { - guard let data = content.data(using: .utf8) else { throw CodeFileError.failedToEncode } - return data - } - - /// This function is used for decoding files. - /// It should not throw error as unsupported files can still be opened by QLPreviewView. - override func read(from data: Data, ofType _: String) throws { - guard let content = String(data: data, encoding: .utf8) else { return } - self.content = content - } - - /// Triggered when change occurred - override func updateChangeCount(_ change: NSDocument.ChangeType) { - super.updateChangeCount(change) - - if CodeFileDocument.autosavesInPlace { - return - } - - self.isDocumentEditedSubject.send(self.isDocumentEdited) - } - - /// Triggered when changes saved - override func updateChangeCount(withToken changeCountToken: Any, for saveOperation: NSDocument.SaveOperationType) { - super.updateChangeCount(withToken: changeCountToken, for: saveOperation) - - if CodeFileDocument.autosavesInPlace { - return - } - - self.isDocumentEditedSubject.send(self.isDocumentEdited) - } -} diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift deleted file mode 100644 index adcffbde98..0000000000 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ /dev/null @@ -1,197 +0,0 @@ -// -// CodeFileView.swift -// CodeEditModules/CodeFile -// -// Created by Marco Carnevali on 17/03/22. -// - -import Foundation -import SwiftUI -import CodeEditSourceEditor -import CodeEditTextView -import CodeEditLanguages -import Combine - -/// CodeFileView is just a wrapper of the `CodeEditor` dependency -struct CodeFileView: View { - @ObservedObject private var codeFile: CodeFileDocument - - /// The current cursor positions in the view - @State private var cursorPositions: [CursorPosition] = [] - - /// Any coordinators passed to the view. - private var textViewCoordinators: [TextViewCoordinator] - - @AppSettings(\.textEditing.defaultTabWidth) - var defaultTabWidth - @AppSettings(\.textEditing.indentOption) - var indentOption - @AppSettings(\.textEditing.lineHeightMultiple) - var lineHeightMultiple - @AppSettings(\.textEditing.wrapLinesToEditorWidth) - var wrapLinesToEditorWidth - @AppSettings(\.textEditing.font) - var settingsFont - @AppSettings(\.theme.useThemeBackground) - var useThemeBackground - @AppSettings(\.theme.matchAppearance) - var matchAppearance - @AppSettings(\.textEditing.letterSpacing) - var letterSpacing - @AppSettings(\.textEditing.bracketHighlight) - var bracketHighlight - - @Environment(\.colorScheme) - private var colorScheme - - @EnvironmentObject private var editorManager: EditorManager - - @StateObject private var themeModel: ThemeModel = .shared - - private var cancellables = [AnyCancellable]() - - private let isEditable: Bool - - private let undoManager = CEUndoManager() - - init(codeFile: CodeFileDocument, textViewCoordinators: [TextViewCoordinator] = [], isEditable: Bool = true) { - self.codeFile = codeFile - self.textViewCoordinators = textViewCoordinators - self.isEditable = isEditable - - if let openOptions = codeFile.openOptions { - codeFile.openOptions = nil - self.cursorPositions = openOptions.cursorPositions - } - - codeFile - .$content - .dropFirst() - .debounce( - for: 0.25, - scheduler: DispatchQueue.main - ) - .sink { _ in - codeFile.updateChangeCount(.changeDone) - codeFile.autosave(withImplicitCancellability: false) { _ in - } - } - .store(in: &cancellables) - - codeFile.undoManager = self.undoManager.manager - } - - @State private var selectedTheme = ThemeModel.shared.selectedTheme ?? ThemeModel.shared.themes.first! - - @State private var font: NSFont = Settings[\.textEditing].font.current - - @State private var bracketPairHighlight: BracketPairHighlight? = { - let theme = ThemeModel.shared.selectedTheme ?? ThemeModel.shared.themes.first! - let color = Settings[\.textEditing].bracketHighlight.useCustomColor - ? Settings[\.textEditing].bracketHighlight.color.nsColor - : theme.editor.text.nsColor.withAlphaComponent(0.8) - switch Settings[\.textEditing].bracketHighlight.highlightType { - case .disabled: - return nil - case .flash: - return .flash - case .bordered: - return .bordered(color: color) - case .underline: - return .underline(color: color) - } - }() - - @Environment(\.edgeInsets) - private var edgeInsets - - @EnvironmentObject private var editor: Editor - - var body: some View { - CodeEditSourceEditor( - $codeFile.content, - language: getLanguage(), - theme: selectedTheme.editor.editorTheme, - font: font, - tabWidth: codeFile.defaultTabWidth ?? defaultTabWidth, - indentOption: (codeFile.indentOption ?? indentOption).textViewOption(), - lineHeight: lineHeightMultiple, - wrapLines: codeFile.wrapLines ?? wrapLinesToEditorWidth, - cursorPositions: $cursorPositions, - useThemeBackground: useThemeBackground, - contentInsets: edgeInsets.nsEdgeInsets, - isEditable: isEditable, - letterSpacing: letterSpacing, - bracketPairHighlight: bracketPairHighlight, - undoManager: undoManager, - coordinators: textViewCoordinators - ) - - .id(codeFile.fileURL) - .background { - if colorScheme == .dark { - EffectView(.underPageBackground) - } else { - EffectView(.contentBackground) - } - } - .colorScheme( - selectedTheme.appearance == .dark - ? .dark - : .light - ) - // minHeight zero fixes a bug where the app would freeze if the contents of the file are empty. - .frame(minHeight: .zero, maxHeight: .infinity) - .onChange(of: themeModel.selectedTheme) { newValue in - guard let theme = newValue else { return } - self.selectedTheme = theme - } - .onChange(of: settingsFont) { newFontSetting in - font = newFontSetting.current - } - .onChange(of: bracketHighlight) { _ in - bracketPairHighlight = getBracketPairHighlight() - } - } - - private func getLanguage() -> CodeLanguage { - guard let url = codeFile.fileURL else { - return .default - } - return codeFile.language ?? CodeLanguage.detectLanguageFrom( - url: url, - prefixBuffer: codeFile.content.getFirstLines(5), - suffixBuffer: codeFile.content.getLastLines(5) - ) - } - - private func getBracketPairHighlight() -> BracketPairHighlight? { - let theme = ThemeModel.shared.selectedTheme ?? ThemeModel.shared.themes.first! - let color = Settings[\.textEditing].bracketHighlight.useCustomColor - ? Settings[\.textEditing].bracketHighlight.color.nsColor - : theme.editor.text.nsColor.withAlphaComponent(0.8) - switch Settings[\.textEditing].bracketHighlight.highlightType { - case .disabled: - return nil - case .flash: - return .flash - case .bordered: - return .bordered(color: color) - case .underline: - return .underline(color: color) - } - } -} - -// This extension is kept here because it should not be used elsewhere in the app and may cause confusion -// due to the similar type name from the CETV module. -private extension SettingsData.TextEditingSettings.IndentOption { - func textViewOption() -> IndentOption { - switch self.indentType { - case .spaces: - return IndentOption.spaces(count: spaceCount) - case .tab: - return IndentOption.tab - } - } -} diff --git a/CodeEdit/Features/CodeFile/Image/ImageFileView.swift b/CodeEdit/Features/CodeFile/Image/ImageFileView.swift deleted file mode 100644 index 8e599ecf50..0000000000 --- a/CodeEdit/Features/CodeFile/Image/ImageFileView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// ImageFileView.swift -// CodeEditModules/CodeFile -// -// Created by Nanashi Li on 2022/04/16. -// - -import SwiftUI - -struct ImageFileView: View { - - private let image: NSImage? - - init(image: NSImage?) { - self.image = image - } - - var body: some View { - GeometryReader { proxy in - if let image { - if image.size.width > proxy.size.width || image.size.height > proxy.size.height { - Image(nsImage: image) - .resizable() - .scaledToFit() - } else { - Image(nsImage: image) - .frame(width: proxy.size.width, height: proxy.size.height) - } - } else { - EmptyView() - } - } - } -} diff --git a/CodeEdit/Features/CodeFile/Other/OtherFileView.swift b/CodeEdit/Features/CodeFile/Other/OtherFileView.swift deleted file mode 100644 index f501dae720..0000000000 --- a/CodeEdit/Features/CodeFile/Other/OtherFileView.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// OtherFileView.swift -// -// -// Created by Shibo Tong on 10/7/2022. -// - -import SwiftUI -import QuickLookUI - -/// A SwiftUI Wrapper for `QLPreviewView` -/// Mainly used for other unsupported files -/// ## Usage -/// ```swift -/// OtherFileView(otherFile) -/// ``` -struct OtherFileView: NSViewRepresentable { - - private var otherFile: CodeFileDocument - - /// Initialize the OtherFileView - /// - Parameter otherFile: a file which contains URL to show preview - init( - _ otherFile: CodeFileDocument - ) { - self.otherFile = otherFile - } - - func makeNSView(context: Context) -> QLPreviewView { - let qlPreviewView = QLPreviewView() - if let previewItemURL = otherFile.previewItemURL { - qlPreviewView.previewItem = previewItemURL as QLPreviewItem - } - return qlPreviewView - } - - /// Update preview file when file changed - func updateNSView(_ nsView: QLPreviewView, context: Context) { - guard let currentPreviewItem = nsView.previewItem else { - return - } - if let previewItemURL = otherFile.previewItemURL, previewItemURL != currentPreviewItem.previewItemURL { - nsView.previewItem = previewItemURL as QLPreviewItem - } - } -} diff --git a/CodeEdit/Features/CodeFile/WindowCodeFileView.swift b/CodeEdit/Features/CodeFile/WindowCodeFileView.swift deleted file mode 100644 index a3edb056f1..0000000000 --- a/CodeEdit/Features/CodeFile/WindowCodeFileView.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// WindowCodeFileView.swift -// CodeEdit -// -// Created by Khan Winter on 3/19/23. -// - -import Foundation -import SwiftUI - -/// View that fixes [#1158](https://github.com/CodeEditApp/CodeEdit/issues/1158) -/// # Should **not** be used other than in a single file window. -struct WindowCodeFileView: View { - var codeFile: CodeFileDocument - - @State var hasAppeared = false - @FocusState var focused: Bool - - var body: some View { - Group { - if !hasAppeared { - Color.clear.onAppear { - hasAppeared = true - focused = true - } - } else { - CodeFileView(codeFile: codeFile) - .focused($focused) - } - } - } -} diff --git a/CodeEdit/Features/Commands/ViewModels/CommandPaletteViewModel.swift b/CodeEdit/Features/Commands/ViewModels/CommandPaletteViewModel.swift deleted file mode 100644 index 1e11ba961e..0000000000 --- a/CodeEdit/Features/Commands/ViewModels/CommandPaletteViewModel.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// CommandPaletteViewModel.swift -// CodeEdit -// -// Created by Alex on 25.05.2022. -// - -import Foundation - -/// Simple state class for command palette view. Contains currently selected command, -/// query text and list of filtered commands -final class CommandPaletteViewModel: ObservableObject { - - @Published var commandQuery: String = "" - - @Published var selected: Command? - - @Published var isShowingCommandsList: Bool = true - - @Published var filteredCommands: [Command] = [] - - init() {} - - func reset() { - commandQuery = "" - selected = nil - filteredCommands = CommandManager.shared.commands - } - - func fetchMatchingCommands(val: String) { - if val == "" { - self.filteredCommands = CommandManager.shared.commands - return - } - self.filteredCommands = CommandManager.shared.commands.filter { $0.title.localizedCaseInsensitiveContains(val) } - self.selected = self.filteredCommands.first - - } -} diff --git a/CodeEdit/Features/Commands/Views/CommandPaletteView.swift b/CodeEdit/Features/Commands/Views/CommandPaletteView.swift deleted file mode 100644 index 4a2478bd3c..0000000000 --- a/CodeEdit/Features/Commands/Views/CommandPaletteView.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// CommandPaletteView.swift -// CodeEdit -// -// Created by Alex Sinelnikov on 24.05.2022. -// - -import SwiftUI - -/// Command palette view -struct CommandPaletteView: View { - - @Environment(\.colorScheme) - private var colorScheme: ColorScheme - - @ObservedObject private var state: CommandPaletteViewModel - - @ObservedObject private var commandManager: CommandManager = .shared - - @State private var monitor: Any? - - @State private var selectedItem: Command? - - private let closePalette: () -> Void - - init(state: CommandPaletteViewModel, closePalette: @escaping () -> Void) { - self.state = state - self.closePalette = closePalette - state.filteredCommands = commandManager.commands - } - - func callHandler(command: Command) { - closePalette() - command.closureWrapper.call() - selectedItem = nil - state.commandQuery = "" - state.filteredCommands = [] - } - - func onQueryChange(text: String) { - state.commandQuery = text - state.fetchMatchingCommands(val: text) - } - - var body: some View { - OverlayView( - title: "Commands", - image: Image(systemName: "magnifyingglass"), - options: $state.filteredCommands, - text: $state.commandQuery, - alwaysShowOptions: true, - optionRowHeight: 30 - ) { command in - SearchResultLabel(labelName: command.title, textToMatch: state.commandQuery) - } onRowClick: { command in - callHandler(command: command) - } onClose: { - closePalette() - } - .onReceive(state.$commandQuery.debounce(for: 0.2, scheduler: DispatchQueue.main)) { _ in - state.fetchMatchingCommands(val: state.commandQuery) - } - } -} - -/// Implementation of command palette entity. While swiftui does not allow to use NSMutableAttributeStrings, -/// the only way to fallback to UIKit and have NSViewRepresentable to be a bridge between UIKit and SwiftUI. -/// Highlights currently entered text query - -struct SearchResultLabel: NSViewRepresentable { - - var labelName: String - var textToMatch: String - - public func makeNSView(context: Context) -> some NSTextField { - let label = NSTextField(wrappingLabelWithString: labelName) - label.translatesAutoresizingMaskIntoConstraints = false - label.drawsBackground = false - label.textColor = .labelColor - label.isEditable = false - label.isSelectable = false - label.font = .labelFont(ofSize: 13) - label.allowsDefaultTighteningForTruncation = false - label.cell?.truncatesLastVisibleLine = true - label.cell?.wraps = true - label.maximumNumberOfLines = 1 - label.attributedStringValue = highlight() - return label - } - - func highlight() -> NSAttributedString { - let attribText = NSMutableAttributedString(string: self.labelName) - let range: NSRange = attribText.mutableString.range( - of: self.textToMatch, - options: NSString.CompareOptions.caseInsensitive - ) - attribText.addAttribute(.foregroundColor, value: NSColor(Color(.labelColor)), range: range) - attribText.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), range: range) - - return attribText - } - - func updateNSView(_ nsView: NSViewType, context: Context) { - nsView.textColor = textToMatch.isEmpty ? .labelColor : .secondaryLabelColor - nsView.attributedStringValue = highlight() - } - -} diff --git a/CodeEdit/Features/Contributors/ContributorRowView.swift b/CodeEdit/Features/Contributors/ContributorRowView.swift deleted file mode 100644 index 85c9894396..0000000000 --- a/CodeEdit/Features/Contributors/ContributorRowView.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// ContributorRowView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 19.01.23. -// - -import SwiftUI - -struct ContributorRowView: View { - - let contributor: Contributor - - var body: some View { - HStack { - userImage - VStack(alignment: .leading, spacing: 2) { - HStack { - Text(contributor.name) - .font(.headline) - } - HStack(spacing: 3) { - ForEach(contributor.contributions, id: \.self) { item in - tag(item) - } - } - } - Spacer() - HStack(alignment: .top) { - if let profileURL = contributor.profileURL, profileURL != contributor.gitHubURL { - ActionButton(url: profileURL, image: .init(systemName: "globe")) - } - if let gitHubURL = contributor.gitHubURL { - ActionButton(url: gitHubURL, image: .github) - } - } - } - .padding(.vertical, 8) - } - - private var userImage: some View { - AsyncImage(url: contributor.avatarURL) { image in - image - .resizable() - .frame(width: 32, height: 32) - .clipShape(Circle()) - } placeholder: { - Image(systemName: "person.circle.fill") - .resizable() - .frame(width: 32, height: 32) - } - } - - private func tag(_ item: Contributor.Contribution) -> some View { - Text(item.rawValue.capitalized) - .font(.caption) - .padding(.horizontal, 6) - .padding(.vertical, 1) - .foregroundColor(item.color) - .background { - Capsule(style: .continuous) - .strokeBorder(lineWidth: 1) - .foregroundStyle(item.color) - .opacity(0.8) - } - } - - private struct ActionButton: View { - @Environment(\.openURL) - private var openURL - @State private var hovering = false - - let url: URL - let image: Image - - var body: some View { - Button { - openURL(url) - } label: { - image - .imageScale(.medium) - .foregroundColor(hovering ? .primary : .secondary) - } - .buttonStyle(.plain) - .onHover { hover in - hovering = hover - } - } - } -} - -struct ContributorRowView_Previews: PreviewProvider { - static var previews: some View { - let contributor = Contributor( - login: "lukepistrol", - name: "Lukas Pistrol", - avatarURLString: "https://avatars.githubusercontent.com/u/9460130?v=4", - profile: "http://lukaspistrol.com", - contributions: [.infra, .test, .code] - ) - ContributorRowView(contributor: contributor) - .frame(width: 350) - } -} diff --git a/CodeEdit/Features/Contributors/ContributorsView.swift b/CodeEdit/Features/Contributors/ContributorsView.swift deleted file mode 100644 index ed60e247fb..0000000000 --- a/CodeEdit/Features/Contributors/ContributorsView.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// ContributorsView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 19.01.23. -// - -import SwiftUI - -struct ContributorsView: View { - @StateObject var model = ContributorsViewModel() - @Binding var aboutMode: AboutMode - var namespace: Namespace.ID - - var body: some View { - AboutDetailView(title: "Contributors", aboutMode: $aboutMode, namespace: namespace) { - VStack(spacing: 0) { - ForEach(model.contributors) { contributor in - ContributorRowView(contributor: contributor) - Divider() - .frame(height: 0.5) - .opacity(0.5) - } - } - } - } -} - -class ContributorsViewModel: ObservableObject { - @Published private(set) var contributors: [Contributor] = [] - - init() { - guard let url = Bundle.main.url( - forResource: ".all-contributorsrc", - withExtension: nil - ) else { return } - do { - let data = try Data(contentsOf: url) - let root = try JSONDecoder().decode(ContributorsRoot.self, from: data) - self.contributors = root.contributors - } catch { - print(error) - } - } -} diff --git a/CodeEdit/Features/Contributors/Model/Contributor.swift b/CodeEdit/Features/Contributors/Model/Contributor.swift deleted file mode 100644 index 472371b248..0000000000 --- a/CodeEdit/Features/Contributors/Model/Contributor.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Contributor.swift -// CodeEdit -// -// Created by Lukas Pistrol on 19.01.23. -// - -import SwiftUI - -struct ContributorsRoot: Codable { - var contributors: [Contributor] -} - -struct Contributor: Codable, Identifiable { - var id: String { login } - var login: String - var name: String - var avatarURLString: String - var profile: String - var contributions: [Contribution] - - var avatarURL: URL? { - URL(string: avatarURLString) - } - - var gitHubURL: URL? { - URL(string: "https://github.com/\(login)") - } - - var profileURL: URL? { - URL(string: profile) - } - - enum CodingKeys: String, CodingKey { - case login, name, profile, contributions - case avatarURLString = "avatar_url" - } - - enum Contribution: String, Codable { - case design, code, infra, test, bug, maintenance, plugin - - var color: Color { - switch self { - case .design: return .blue - case .code: return .indigo - case .infra: return .pink - case .test: return .purple - case .bug: return .red - case .maintenance: return .brown - case .plugin: return .gray - } - } - } -} diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift deleted file mode 100644 index c258b90b22..0000000000 --- a/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// CodeEditDocumentController.swift -// CodeEdit -// -// Created by Pavel Kasila on 17.03.22. -// - -import Cocoa -import SwiftUI - -final class CodeEditDocumentController: NSDocumentController { - @Environment(\.openWindow) - private var openWindow - - lazy var fileManager: FileManager = { - FileManager.default - }() - - override func newDocument(_ sender: Any?) { - guard let newDocumentUrl = self.newDocumentUrl else { return } - - let createdFile = self.fileManager.createFile( - atPath: newDocumentUrl.path, - contents: nil, - attributes: [FileAttributeKey.creationDate: Date()] - ) - guard createdFile else { - print("Failed to create new document") - return - } - - self.openDocument(withContentsOf: newDocumentUrl, display: true) { _, _, _ in } - } - - private var newDocumentUrl: URL? { - let panel = NSSavePanel() - guard panel.runModal() == .OK else { - return nil - } - - return panel.url - } - - override func noteNewRecentDocument(_ document: NSDocument) { - // The super method is run manually when opening new documents. - } - - override func openDocument(_ sender: Any?) { - self.openDocument(onCompletion: { document, documentWasAlreadyOpen in - // TODO: handle errors - - guard let document else { - print("Failed to unwrap document") - return - } - - print(document, documentWasAlreadyOpen) - }, onCancel: {}) - } - - override func openDocument( - withContentsOf url: URL, - display displayDocument: Bool, - completionHandler: @escaping (NSDocument?, Bool, Error?) -> Void - ) { - super.noteNewRecentDocumentURL(url) - super.openDocument(withContentsOf: url, display: displayDocument) { document, documentWasAlreadyOpen, error in - - if let document { - self.addDocument(document) - self.updateRecent(url) - } else { - let errorMessage = error?.localizedDescription ?? "unknown error" - print("Unable to open document '\(url)': \(errorMessage)") - } - - completionHandler(document, documentWasAlreadyOpen, error) - } - } - - override func removeDocument(_ document: NSDocument) { - super.removeDocument(document) - - if CodeEditDocumentController.shared.documents.isEmpty { - switch Settings[\.general].reopenWindowAfterClose { - case .showWelcomeWindow: - // Opens the welcome window - openWindow(sceneID: .welcome) - case .quit: - // Quits CodeEdit - NSApplication.shared.terminate(nil) - case .doNothing: break - } - } - } - - override func clearRecentDocuments(_ sender: Any?) { - super.clearRecentDocuments(sender) - UserDefaults.standard.set([Any](), forKey: "recentProjectPaths") - } -} - -extension NSDocumentController { - final func openDocument(onCompletion: @escaping (NSDocument?, Bool) -> Void, onCancel: @escaping () -> Void) { - let dialog = NSOpenPanel() - - dialog.title = "Open Workspace or File" - dialog.showsResizeIndicator = true - dialog.showsHiddenFiles = false - dialog.canChooseFiles = true - dialog.canChooseDirectories = true - - dialog.begin { result in - if result == NSApplication.ModalResponse.OK, let url = dialog.url { - self.openDocument(withContentsOf: url, display: true) { document, documentWasAlreadyOpen, error in - if let error { - NSAlert(error: error).runModal() - return - } - - guard let document else { - let alert = NSAlert() - alert.messageText = NSLocalizedString( - "Failed to get document", - comment: "Failed to get document" - ) - alert.runModal() - return - } - self.updateRecent(url) - onCompletion(document, documentWasAlreadyOpen) - print("Document:", document) - print("Was already open?", documentWasAlreadyOpen) - } - } else if result == NSApplication.ModalResponse.cancel { - onCancel() - } - } - } - - final func updateRecent(_ url: URL) { - var recentProjectPaths: [String] = UserDefaults.standard.array( - forKey: "recentProjectPaths" - ) as? [String] ?? [] - if let containedIndex = recentProjectPaths.firstIndex(of: url.path) { - recentProjectPaths.move(fromOffsets: IndexSet(integer: containedIndex), toOffset: 0) - } else { - recentProjectPaths.insert(url.path, at: 0) - } - UserDefaults.standard.set(recentProjectPaths, forKey: "recentProjectPaths") - } -} diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift deleted file mode 100644 index 9f270f061a..0000000000 --- a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// CodeEditSplitViewController.swift -// CodeEdit -// -// Created by YAPRYNTSEV Aleksey on 31.12.2022. -// - -import Cocoa -import SwiftUI - -struct CodeEditSplitView: NSViewControllerRepresentable { - let controller: NSSplitViewController - - func makeNSViewController(context: Context) -> NSSplitViewController { - controller - } - - func updateNSViewController(_ nsViewController: NSSplitViewController, context: Context) {} -} - -private extension CGFloat { - static let snapWidth: CGFloat = 272 - - static let minSnapWidth: CGFloat = snapWidth - 10 - static let maxSnapWidth: CGFloat = snapWidth + 10 -} - -final class CodeEditSplitViewController: NSSplitViewController { - private var workspace: WorkspaceDocument - private var setWidthFromState = false - private var viewIsReady = false - - // Properties - private(set) var isSnapped: Bool = false { - willSet { - if newValue, newValue != isSnapped && viewIsReady { - feedbackPerformer.perform(.alignment, performanceTime: .now) - } - } - } - - // Dependencies - private let feedbackPerformer: NSHapticFeedbackPerformer - - // MARK: - Initialization - - init(workspace: WorkspaceDocument, feedbackPerformer: NSHapticFeedbackPerformer) { - self.workspace = workspace - self.feedbackPerformer = feedbackPerformer - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear() { - super.viewWillAppear() - - viewIsReady = false - let width = workspace.getFromWorkspaceState(.splitViewWidth) as? CGFloat - splitView.setPosition(width ?? .snapWidth, ofDividerAt: .zero) - setWidthFromState = true - - if let firstSplitView = splitViewItems.first { - firstSplitView.isCollapsed = workspace.getFromWorkspaceState( - .navigatorCollapsed - ) as? Bool ?? false - } - - if let lastSplitView = splitViewItems.last { - lastSplitView.isCollapsed = workspace.getFromWorkspaceState( - .inspectorCollapsed - ) as? Bool ?? true - } - - self.insertToolbarItemIfNeeded() - } - - override func viewDidAppear() { - viewIsReady = true - hideInspectorToolbarBackground() - } - - // MARK: - NSSplitViewDelegate - - override func splitView( - _ splitView: NSSplitView, - constrainSplitPosition proposedPosition: CGFloat, - ofSubviewAt dividerIndex: Int - ) -> CGFloat { - if dividerIndex == 0 { - // Navigator - if (CGFloat.minSnapWidth...CGFloat.maxSnapWidth).contains(proposedPosition) { - isSnapped = true - return .snapWidth - } else { - isSnapped = false - if proposedPosition <= CodeEditWindowController.minSidebarWidth / 2 { - splitViewItems.first?.isCollapsed = true - return 0 - } - return max(CodeEditWindowController.minSidebarWidth, proposedPosition) - } - } else if dividerIndex == 1 { - let proposedWidth = view.frame.width - proposedPosition - if proposedWidth <= CodeEditWindowController.minSidebarWidth / 2 { - splitViewItems.last?.isCollapsed = true - removeToolbarItemIfNeeded() - return proposedPosition - } - splitViewItems.last?.isCollapsed = false - insertToolbarItemIfNeeded() - return min(view.frame.width - CodeEditWindowController.minSidebarWidth, proposedPosition) - } - return proposedPosition - } - - override func splitViewDidResizeSubviews(_ notification: Notification) { - guard let resizedDivider = notification.userInfo?["NSSplitViewDividerIndex"] as? Int else { - return - } - - if resizedDivider == 0 { - let panel = splitView.subviews[0] - let width = panel.frame.size.width - if width > 0 && setWidthFromState { - workspace.addToWorkspaceState(key: .splitViewWidth, value: width) - } - } - } - - func saveNavigatorCollapsedState(isCollapsed: Bool) { - workspace.addToWorkspaceState(key: .navigatorCollapsed, value: isCollapsed) - } - - func saveInspectorCollapsedState(isCollapsed: Bool) { - workspace.addToWorkspaceState(key: .inspectorCollapsed, value: isCollapsed) - } - - /// Quick fix for list tracking separator needing to be added again after closing, - /// then opening the inspector with a drag. - private func insertToolbarItemIfNeeded() { - guard !( - view.window?.toolbar?.items.contains(where: { $0.itemIdentifier == .itemListTrackingSeparator }) ?? true - ) else { - return - } - view.window?.toolbar?.insertItem(withItemIdentifier: .itemListTrackingSeparator, at: 4) - } - - /// Quick fix for list tracking separator needing to be removed after closing the inspector with a drag - private func removeToolbarItemIfNeeded() { - guard let index = view.window?.toolbar?.items.firstIndex( - where: { $0.itemIdentifier == .itemListTrackingSeparator } - ) else { - return - } - view.window?.toolbar?.removeItem(at: index) - } - - func hideInspectorToolbarBackground() { - let controller = self.view.window?.perform(Selector(("titlebarViewController"))).takeUnretainedValue() - if let controller = controller as? NSViewController { - let effectViewCount = controller.view.subviews.filter { $0 is NSVisualEffectView }.count - guard effectViewCount > 2 else { return } - if let view = controller.view.subviews[0] as? NSVisualEffectView { - view.isHidden = true - } - } - } -} diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift deleted file mode 100644 index 210d130dc9..0000000000 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ /dev/null @@ -1,297 +0,0 @@ -// -// CodeEditWindowController.swift -// CodeEdit -// -// Created by Pavel Kasila on 18.03.22. -// - -import Cocoa -import SwiftUI -import Combine - -final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, ObservableObject { - static let minSidebarWidth: CGFloat = 242 - - @Published var navigatorCollapsed = false - @Published var inspectorCollapsed = false - - var observers: [NSKeyValueObservation] = [] - - var workspace: WorkspaceDocument? - var quickOpenPanel: OverlayPanel? - var commandPalettePanel: OverlayPanel? - var navigatorSidebarViewModel: NavigatorSidebarViewModel? - - var splitViewController: NSSplitViewController! - - internal var cancellables = [AnyCancellable]() - - init(window: NSWindow, workspace: WorkspaceDocument) { - super.init(window: window) - self.workspace = workspace - setupSplitView(with: workspace) - - let view = CodeEditSplitView(controller: splitViewController).ignoresSafeArea() - - // An NSHostingController is used, so the root viewController of the window is a SwiftUI-managed one. - // This allows us to use some SwiftUI features, like focusedSceneObject. - contentViewController = NSHostingController(rootView: view) - - observers = [ - splitViewController.splitViewItems.first!.observe(\.isCollapsed, changeHandler: { [weak self] item, _ in - self?.navigatorCollapsed = item.isCollapsed - }), - splitViewController.splitViewItems.last!.observe(\.isCollapsed, changeHandler: { [weak self] item, _ in - self?.navigatorCollapsed = item.isCollapsed - }) - ] - - setupToolbar() - registerCommands() - } - - deinit { - cancellables.forEach({ $0.cancel() }) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupSplitView(with workspace: WorkspaceDocument) { - let feedbackPerformer = NSHapticFeedbackManager.defaultPerformer - let splitVC = CodeEditSplitViewController(workspace: workspace, feedbackPerformer: feedbackPerformer) - - let navigatorViewModel = NavigatorSidebarViewModel() - navigatorSidebarViewModel = navigatorViewModel - - let settingsView = SettingsInjector { - NavigatorAreaView(workspace: workspace, viewModel: navigatorViewModel) - .environmentObject(workspace) - .environmentObject(workspace.editorManager) - } - - let navigator = NSSplitViewItem( - sidebarWithViewController: NSHostingController(rootView: settingsView) - ) - navigator.titlebarSeparatorStyle = .none - navigator.minimumThickness = Self.minSidebarWidth - navigator.collapseBehavior = .useConstraints - - splitVC.addSplitViewItem(navigator) - - let workspaceView = SettingsInjector { - WindowObserver(window: window!) { - WorkspaceView() - .environmentObject(workspace) - .environmentObject(workspace.editorManager) - .environmentObject(workspace.utilityAreaModel) - } - } - - let mainContent = NSSplitViewItem(viewController: NSHostingController(rootView: workspaceView)) - mainContent.titlebarSeparatorStyle = .line - mainContent.holdingPriority = .init(50) - - splitVC.addSplitViewItem(mainContent) - - let inspectorView = SettingsInjector { - InspectorAreaView(viewModel: InspectorAreaViewModel()) - .environmentObject(workspace) - .environmentObject(workspace.editorManager) - } - - let inspector = NSSplitViewItem(viewController: NSHostingController(rootView: inspectorView)) - inspector.titlebarSeparatorStyle = .none - inspector.minimumThickness = Self.minSidebarWidth - inspector.isCollapsed = true - inspector.canCollapse = true - inspector.collapseBehavior = .useConstraints - inspector.isSpringLoaded = true - - splitVC.addSplitViewItem(inspector) - - self.splitViewController = splitVC - self.listenToDocumentEdited(workspace: workspace) - } - - private func setupToolbar() { - let toolbar = NSToolbar(identifier: UUID().uuidString) - toolbar.delegate = self - toolbar.displayMode = .labelOnly - toolbar.showsBaselineSeparator = false - self.window?.titleVisibility = .hidden - self.window?.toolbarStyle = .unifiedCompact - if Settings[\.general].tabBarStyle == .native { - // Set titlebar background as transparent by default in order to - // style the toolbar background in native tab bar style. - self.window?.titlebarSeparatorStyle = .none - } else { - // In Xcode tab bar style, we use default toolbar background with - // line separator. - self.window?.titlebarSeparatorStyle = .automatic - } - self.window?.toolbar = toolbar - } - - // MARK: - Toolbar - - func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [ - .toggleFirstSidebarItem, - .sidebarTrackingSeparator, - .branchPicker, - .flexibleSpace, - .itemListTrackingSeparator, - .flexibleSpace, - .toggleLastSidebarItem - ] - } - - func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [ - .toggleFirstSidebarItem, - .sidebarTrackingSeparator, - .flexibleSpace, - .itemListTrackingSeparator, - .toggleLastSidebarItem, - .branchPicker - ] - } - - func toolbar( - _ toolbar: NSToolbar, - itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, - willBeInsertedIntoToolbar flag: Bool - ) -> NSToolbarItem? { - switch itemIdentifier { - case .itemListTrackingSeparator: - guard let splitViewController else { - return nil - } - - return NSTrackingSeparatorToolbarItem( - identifier: .itemListTrackingSeparator, - splitView: splitViewController.splitView, - dividerIndex: 1 - ) - case .toggleFirstSidebarItem: - let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.toggleFirstSidebarItem) - toolbarItem.label = "Navigator Sidebar" - toolbarItem.paletteLabel = " Navigator Sidebar" - toolbarItem.toolTip = "Hide or show the Navigator" - toolbarItem.isBordered = true - toolbarItem.target = self - toolbarItem.action = #selector(self.toggleFirstPanel) - toolbarItem.image = NSImage( - systemSymbolName: "sidebar.leading", - accessibilityDescription: nil - )?.withSymbolConfiguration(.init(scale: .large)) - - return toolbarItem - case .toggleLastSidebarItem: - let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.toggleLastSidebarItem) - toolbarItem.label = "Inspector Sidebar" - toolbarItem.paletteLabel = "Inspector Sidebar" - toolbarItem.toolTip = "Hide or show the Inspectors" - toolbarItem.isBordered = true - toolbarItem.target = self - toolbarItem.action = #selector(self.toggleLastPanel) - toolbarItem.image = NSImage( - systemSymbolName: "sidebar.trailing", - accessibilityDescription: nil - )?.withSymbolConfiguration(.init(scale: .large)) - - return toolbarItem - case .branchPicker: - let toolbarItem = NSToolbarItem(itemIdentifier: .branchPicker) - let view = NSHostingView( - rootView: ToolbarBranchPicker( - workspaceFileManager: workspace?.workspaceFileManager - ) - ) - toolbarItem.view = view - - return toolbarItem - default: - return NSToolbarItem(itemIdentifier: itemIdentifier) - } - } - - private func getSelectedCodeFile() -> CodeFileDocument? { - workspace?.editorManager.activeEditor.selectedTab?.file.fileDocument - } - - @IBAction func saveDocument(_ sender: Any) { - guard let codeFile = getSelectedCodeFile() else { return } - codeFile.save(sender) - workspace?.editorManager.activeEditor.temporaryTab = nil - } - - @IBAction func openCommandPalette(_ sender: Any) { - if let workspace, let state = workspace.commandsPaletteState { - if let commandPalettePanel { - if commandPalettePanel.isKeyWindow { - commandPalettePanel.close() - state.reset() - return - } else { - state.reset() - window?.addChildWindow(commandPalettePanel, ordered: .above) - commandPalettePanel.makeKeyAndOrderFront(self) - } - } else { - let panel = OverlayPanel() - self.commandPalettePanel = panel - let contentView = CommandPaletteView(state: state, closePalette: panel.close) - panel.contentView = NSHostingView(rootView: SettingsInjector { contentView }) - window?.addChildWindow(panel, ordered: .above) - panel.makeKeyAndOrderFront(self) - } - } - } - - @IBAction func openQuickly(_ sender: Any) { - if let workspace, let state = workspace.quickOpenViewModel { - if let quickOpenPanel { - if quickOpenPanel.isKeyWindow { - quickOpenPanel.close() - return - } else { - window?.addChildWindow(quickOpenPanel, ordered: .above) - quickOpenPanel.makeKeyAndOrderFront(self) - } - } else { - let panel = OverlayPanel() - self.quickOpenPanel = panel - - let contentView = QuickOpenView(state: state) { - panel.close() - } openFile: { file in - workspace.editorManager.openTab(item: file) - }.environmentObject(workspace) - - panel.contentView = NSHostingView(rootView: SettingsInjector { contentView }) - window?.addChildWindow(panel, ordered: .above) - panel.makeKeyAndOrderFront(self) - } - } - } - - @IBAction func closeCurrentTab(_ sender: Any) { - if (workspace?.editorManager.activeEditor.tabs ?? []).isEmpty { - self.closeActiveEditor(self) - } else { - workspace?.editorManager.activeEditor.closeSelectedTab() - } - } - - @IBAction func closeActiveEditor(_ sender: Any) { - if workspace?.editorManager.editorLayout.findSomeEditor(except: workspace?.editorManager.activeEditor) == nil { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) - } else { - workspace?.editorManager.activeEditor.close() - } - } -} diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift deleted file mode 100644 index 3a367e6601..0000000000 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// CodeEditWindowControllerExtensions.swift -// CodeEdit -// -// Created by Austin Condiff on 10/14/23. -// - -import SwiftUI -import Combine - -extension CodeEditWindowController { - @objc - func toggleFirstPanel() { - guard let firstSplitView = splitViewController.splitViewItems.first else { return } - firstSplitView.animator().isCollapsed.toggle() - if let codeEditSplitVC = splitViewController as? CodeEditSplitViewController { - codeEditSplitVC.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed) - } - } - - @objc - func toggleLastPanel() { - guard let lastSplitView = splitViewController.splitViewItems.last else { return } - - if let toolbar = window?.toolbar, - lastSplitView.isCollapsed, - !toolbar.items.map(\.itemIdentifier).contains(.itemListTrackingSeparator) { - window?.toolbar?.insertItem(withItemIdentifier: .itemListTrackingSeparator, at: 4) - } - NSAnimationContext.runAnimationGroup { _ in - lastSplitView.animator().isCollapsed.toggle() - } completionHandler: { [weak self] in - if lastSplitView.isCollapsed { - self?.window?.animator().toolbar?.removeItem(at: 4) - } - } - - if let codeEditSplitVC = splitViewController as? CodeEditSplitViewController { - codeEditSplitVC.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed) - codeEditSplitVC.hideInspectorToolbarBackground() - } - } - - /// These are example items that added as commands to command palette - func registerCommands() { - CommandManager.shared.addCommand( - name: "Quick Open", - title: "Quick Open", - id: "quick_open", - command: CommandClosureWrapper(closure: { self.openQuickly(self) }) - ) - - CommandManager.shared.addCommand( - name: "Toggle Navigator", - title: "Toggle Navigator", - id: "toggle_left_sidebar", - command: CommandClosureWrapper(closure: { self.toggleFirstPanel() }) - ) - - CommandManager.shared.addCommand( - name: "Toggle Inspector", - title: "Toggle Inspector", - id: "toggle_right_sidebar", - command: CommandClosureWrapper(closure: { self.toggleLastPanel() }) - ) - } - - // Listen to changes in all tabs/files - internal func listenToDocumentEdited(workspace: WorkspaceDocument) { - workspace.editorManager.$activeEditor - .flatMap({ editor in - editor.$tabs - }) - .compactMap({ tab in - Publishers.MergeMany(tab.elements.compactMap({ $0.file.fileDocumentPublisher })) - }) - .switchToLatest() - .compactMap({ fileDocument in - fileDocument?.isDocumentEditedPublisher - }) - .flatMap({ $0 }) - .sink { isDocumentEdited in - if isDocumentEdited { - self.setDocumentEdited(true) - return - } - - self.updateDocumentEdited(workspace: workspace) - } - .store(in: &cancellables) - - // Listen to change of tabs, if closed tab without saving content, - // we also need to recalculate isDocumentEdited - workspace.editorManager.$activeEditor - .flatMap({ editor in - editor.$tabs - }) - .sink { _ in - self.updateDocumentEdited(workspace: workspace) - } - .store(in: &cancellables) - } - - // Recalculate documentEdited by checking if any tab/file is edited - private func updateDocumentEdited(workspace: WorkspaceDocument) { - let hasEditedDocuments = !workspace - .editorManager - .editorLayout - .gatherOpenFiles() - .filter({ $0.fileDocument?.isDocumentEdited == true }) - .isEmpty - self.setDocumentEdited(hasEditedDocuments) - } -} - -extension NSToolbarItem.Identifier { - static let toggleFirstSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ToggleFirstSidebarItem") - static let toggleLastSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ToggleLastSidebarItem") - static let itemListTrackingSeparator = NSToolbarItem.Identifier("ItemListTrackingSeparator") - static let branchPicker: NSToolbarItem.Identifier = NSToolbarItem.Identifier("BranchPicker") -} diff --git a/CodeEdit/Features/Documents/Indexer/AsyncFileIterator.swift b/CodeEdit/Features/Documents/Indexer/AsyncFileIterator.swift deleted file mode 100644 index f45134d1ec..0000000000 --- a/CodeEdit/Features/Documents/Indexer/AsyncFileIterator.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// AsyncFileIterator.swift -// CodeEdit -// -// Created by Khan Winter on 12/4/23. -// - -import Foundation - -/// Given a list of file URLs, asynchronously fetches their contents and returns them iteratively. -/// Returns files as a ``SearchIndexer/AsyncManager/TextFile`` struct, used to index workspaces. -struct AsyncFileIterator: AsyncSequence, AsyncIteratorProtocol { - typealias TextFile = SearchIndexer.AsyncManager.TextFile - typealias Element = (TextFile, Int) - - let fileURLs: [URL] - var currentIdx = 0 - - mutating func next() async -> Element? { - guard !Task.isCancelled else { - return nil - } - - defer { - currentIdx += 1 - } - - // Loop until we either find a loadable file or run out of URLs - var foundContent: TextFile? - while foundContent == nil { - guard currentIdx < fileURLs.count else { - return nil - } - - let fileURL = fileURLs[currentIdx] - if let content = try? String(contentsOf: fileURL) { - foundContent = TextFile(url: fileURL.standardizedFileURL, text: content) - } else { - currentIdx += 1 - } - } - return (foundContent!, currentIdx) - } - - func makeAsyncIterator() -> AsyncFileIterator { - self - } -} diff --git a/CodeEdit/Features/Documents/Indexer/FileHelper.swift b/CodeEdit/Features/Documents/Indexer/FileHelper.swift deleted file mode 100644 index 299f8fdb54..0000000000 --- a/CodeEdit/Features/Documents/Indexer/FileHelper.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// FileHelper.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -enum FileHelper { - static func urlIsFolder(_ url: URL) -> Bool { - var isDirectory: ObjCBool = false - let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) - return exists && isDirectory.boolValue - } - - static func urlIsFile(_ url: URL) -> Bool { - var isDirectory: ObjCBool = false - let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) - return exists && !isDirectory.boolValue - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift deleted file mode 100644 index 93743b4c30..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// SearchIndexer+Add.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -extension SearchIndexer { - /// Add some text to the index for a given URL - /// - /// - Parameters: - /// - url: The identifying URL for the text - /// - text: The text to add - /// - canReplace: if true, can attempt to replace an existing document with the new one. - /// - Returns: true if the text was successfully added to the index, false otherwise - public func addFileWithText(_ url: URL, text: String, canReplace: Bool = true) -> Bool { - guard let index = self.index, - let document = SKDocumentCreateWithURL(url as CFURL) else { - return false - } - - return modifyIndexQueue.sync { - SKIndexAddDocumentWithText(index, document.takeUnretainedValue(), text as CFString, canReplace) - } - } - - /// Adds text content to the indexer using a URL string. - /// - /// - Parameters: - /// - textURL: A string representing the URL of the text content. - /// - text: The text content to be added to the indexer. - /// - canReplace: If true, can attempt to replace an existing document with the new one. Defaults to `true`. - /// - /// - Returns: `true` if the text content is successfully added to the indexer; otherwise, returns `false`. - public func addFileWithText(textURL: String, text: String, canReplace: Bool = true) -> Bool { - guard let url = URL(string: textURL) else { - return false - } - return self.addFileWithText(url, text: text, canReplace: canReplace) - } - - /// Adds a file as a document to the index. - /// - /// - Parameters: - /// - fileURL: The file URL for the document, e.g., file:///User/Essay.txt. - /// - mimeType: - /// An optional MIME type. If nil, the function attempts to determine the file type from the extension. - /// - canReplace: - /// A flag indicating whether to attempt to replace an existing document with the new one. - /// Defaults to `true`. - /// - /// - Returns: `true` if the command was successful. Even if the document wasn't updated, it still returns `true`. - /// - /// - Important: - /// If the document wasn't updated, the function still returns `true`. - /// Be cautious when relying solely on the return value to determine if the document was replaced. - public func addFile(fileURL: URL, mimeType: String? = nil, canReplace: Bool = true) -> Bool { - guard self.dataExtractorLoaded, - let index = self.index, - let document = SKDocumentCreateWithURL(fileURL as CFURL) else { - return false - } - // Try to detect the mime type if it wasn't specified - let mime = mimeType ?? self.detectMimeType(fileURL) - - return modifyIndexQueue.sync { - SKIndexAddDocument(index, document.takeUnretainedValue(), mime as CFString?, canReplace) - } - } - - /// Recursively adds the files contained within a folder to the search index. - /// - /// - Parameters: - /// - folderURL: The folder to be indexed. - /// - canReplace: - /// A flag indicating whether existing documents within the index can be replaced. Defaults to `true`. - /// - /// - Returns: The URLs of documents added to the index. If `folderURL` isn't a folder, returns an empty array. - public func addFolderContent(folderURL: URL, canReplace: Bool = true) -> [URL] { - let fileManger = FileManager.default - - var isDir: ObjCBool = false - guard fileManger.fileExists(atPath: folderURL.path, isDirectory: &isDir), - isDir.boolValue == true else { - return [] - } - - var addedUrls: [URL] = [] - let enumerator = fileManger.enumerator(at: folderURL, includingPropertiesForKeys: nil) - while let fileURL = enumerator?.nextObject() as? URL { - if fileManger.fileExists(atPath: fileURL.path, isDirectory: &isDir), - isDir.boolValue == false, - self.addFile(fileURL: fileURL, canReplace: canReplace) { - addedUrls.append(fileURL) - } - } - - return addedUrls - } - - /// Removes a document from the index. - /// - /// - Parameter url: The identifying URL for the document. - /// - /// - Returns: `true` if the document was successfully removed, `false` otherwise. - /// **Note:** If the document didn't exist, this also returns `true`. - public func removeDocument(url: URL) -> Bool { - let document = SKDocumentCreateWithURL(url as CFURL).takeUnretainedValue() - return self.remove(document: document) - } - - /// Remove an array of documents from the index - /// - /// - Parameter urls: An array of URLs identifying the documents to be removed. - public func removeDocuments(urls: [URL]) { - urls.forEach { url in - _ = self.removeDocument(url: url) - } - } - - /// Retrieves the indexing state of a document at the specified URL. - /// - /// - Parameter url: The URL of the document. - /// - /// - Returns: - /// The indexing state of the document. Returns `kSKDocumentStateNotIndexed` if the document is not indexed. - public func documentState(_ url: URL) -> SKDocumentIndexState { - if let index = self.index, - let document = SKDocumentCreateWithURL(url as CFURL) { - return SKIndexGetDocumentState(index, document.takeUnretainedValue()) - } - return kSKDocumentStateNotIndexed - } - - /// Checks if a document at the specified URL is indexed. - /// - /// - Parameter url: The URL of the document. - /// - /// - Returns: `true` if the document is indexed; otherwise, returns `false`. - public func documentIndexed(_ url: URL) -> Bool { - return self.documentState(url) == kSKDocumentStateIndexed - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+AsyncController.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+AsyncController.swift deleted file mode 100644 index 885573763e..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+AsyncController.swift +++ /dev/null @@ -1,200 +0,0 @@ -// -// SearchIndexer+AsyncController.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -extension SearchIndexer { - /// Manager for SearchIndexer object that supports async calls to the index - class AsyncManager { - /// An instance of the SearchIndexer - let index: SearchIndexer - private let addQueue = DispatchQueue(label: "app.codeedit.CodeEdit.AddFilesToIndex", attributes: .concurrent) - private let searchQueue = DispatchQueue(label: "app.codeedit.CodeEdit.SearchIndex", attributes: .concurrent) - - init(index: SearchIndexer) { - self.index = index - } - - class TextFile { - let url: URL - let text: String - - /// Create a text async task - /// - /// - Parameters: - /// - url: the identifying document URL - /// - text: The text to add to the index - init(url: URL, text: String) { - self.url = url - self.text = text - } - } - - // MARK: - Search - - /// Performs an asynchronous progressive search on the index for the specified query. - /// - /// - Parameters: - /// - query: The search query string. - /// - maxResults: The maximum number of results to retrieve in each chunk. - /// - timeout: The timeout duration for each search operation. Default is 1.0 second. - /// - /// - Returns: An asynchronous stream (`AsyncStream`) of search results in chunks. - /// The search results are returned in the form of a `SearchIndexer.ProgressiveSearch.Results` object. - /// - /// This function initiates a progressive search on the index for the specified query - /// and asynchronously yields search results in chunks using an `AsyncStream`. - /// The search continues until there are no more results or the specified timeout is reached. - /// - /// - Warning: Prior to calling this function, - /// ensure that the `index` has been flushed to search within the most up-to-date data. - /// - /// Example usage: - /// ```swift - /// let searchStream = await asyncController.search(query: searchQuery, 20) - /// for try await result in searchStream { - /// // Process each result - /// print(result) - /// } - /// ``` - func search( - query: String, - _ maxResults: Int, - timeout: TimeInterval = 1.0 - ) async -> AsyncStream { - let search = index.progressiveSearch(query: query) - - return AsyncStream { configuration in - var moreResultsAvailable = true - while moreResultsAvailable && !Task.isCancelled { - let results = search.getNextSearchResultsChunk(limit: maxResults, timeout: timeout) - moreResultsAvailable = results.moreResultsAvailable - configuration.yield(results) - } - configuration.finish() - } - } - - // MARK: - Add - - /// Adds files from an array of TextFile objects to the index asynchronously. - /// - /// - Parameters: - /// - files: An array of TextFile objects containing the information about the files to be added. - /// - flushWhenComplete: A boolean flag indicating whether to flush - /// the index when the operation is complete. Default is `false`. - /// - /// - Returns: An array of booleans indicating the success of adding each file to the index. - func addText( - files: [TextFile], - flushWhenComplete: Bool = false - ) async -> [Bool] { - - var addedFiles = [Bool]() - - // Asynchronously iterate through the provided files using a task group - await withTaskGroup(of: Bool.self) { taskGroup in - for file in files { - taskGroup.addTask { - // Add the file to the index and return the success status - return self.index.addFileWithText(file.url, text: file.text, canReplace: true) - } - } - - for await result in taskGroup { - addedFiles.append(result) - } - } - - if flushWhenComplete { - index.flush() - } - - return addedFiles - } - - /// Adds files from an array of URLs to the index asynchronously. - /// - /// - Parameters: - /// - urls: An array of URLs representing the file locations to be added to the index. - /// - flushWhenComplete: A boolean flag indicating whether to flush - /// the index when the operation is complete. Default is `false`. - /// - /// - Returns: An array of booleans indicating the success of adding each file to the index. - /// - Warning: Prefer using `addText` when possible as SearchKit does not have the ability - /// to read every file type. For example, it is often not possible to read Swift files. - func addFiles( - urls: [URL], - flushWhenComplete: Bool = false - ) async -> [Bool] { - var addedURLs = [Bool]() - - await withTaskGroup(of: Bool.self) { taskGroup in - for url in urls { - taskGroup.addTask { - return self.index.addFile(fileURL: url, canReplace: true) - } - } - - for await results in taskGroup { - addedURLs.append(results) - } - } - - return addedURLs - } - - /// Adds files from a folder specified by the given URL to the index asynchronously. - /// - /// - Parameters: - /// - url: The URL of the folder containing files to be added to the index. - /// - flushWhenComplete: A boolean flag indicating whether to flush - /// the index when the operation is complete. Default is `false`. - /// - /// This function uses asynchronous processing to add files from the specified folder to the index. - /// - /// - Note: Subfolders within the specified folder are also processed. - func addFolder( - url: URL, - flushWhenComplete: Bool = false - ) { - let dispatchGroup = DispatchGroup() - - let fileManager = FileManager.default - let enumerator = fileManager.enumerator( - at: url, - includingPropertiesForKeys: [.isRegularFileKey], - options: [.skipsHiddenFiles], - errorHandler: nil - )! - - for case let fileURL as URL in enumerator { - dispatchGroup.enter() - - if FileHelper.urlIsFolder(url) { - addQueue.async { [weak self] in - guard let self = self else { return } - self.addFolder(url: url) - dispatchGroup.leave() - } - } else { - addQueue.async { [weak self] in - guard let self = self else { return } - _ = self.index.addFile(fileURL: fileURL, canReplace: true) - dispatchGroup.leave() - } - } - } - - dispatchGroup.notify(queue: .main) { - if flushWhenComplete { - self.index.flush() - } - } - } - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+File.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+File.swift deleted file mode 100644 index deca8babe7..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+File.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// SearchIndexer+File.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -extension SearchIndexer { - /// A file based index - public class File: SearchIndexer { - /// The file url where the index is located - public let fileURL: URL - - private init(url: URL, index: SKIndex) { - self.fileURL = url - super.init(index: index) - } - - /// Create a new file based index - /// - Parameters: - /// - fileURL:The file URL to create the index at - /// - properties: The properties defining the capabilities of the index - public convenience init?(fileURL: URL, properties: CreateProperties) { - if !FileManager.default.fileExists(atPath: fileURL.absoluteString), - let skIndex = SKIndexCreateWithURL( - fileURL as CFURL, - nil, - properties.indexType, - properties.properties() - ) { - self.init(url: fileURL, index: skIndex.takeUnretainedValue()) - } else { - return nil - } - } - - /// Load an index from a file url - /// - Parameter fileURL: The file URL where the index is located at - /// - Parameter writable: Can the index be modified - public convenience init?(fileURL: URL, writeable: Bool) { - if let skIndex = SKIndexOpenWithURL(fileURL as CFURL, nil, writeable) { - self.init(url: fileURL, index: skIndex.takeUnretainedValue()) - } else { - return nil - } - } - - /// Open an index from a file url. - /// - /// - Parameters: - /// - fileURL: The file url to open - /// - writable: should the index be modifiable? - /// - Returns: A new index object if successful, nil otherwise - public static func openIndex(fileURL: URL, writeable: Bool) -> SearchIndexer.File? { - if let temp = SKIndexOpenWithURL(fileURL as CFURL, nil, writeable) { - return SearchIndexer.File(url: fileURL, index: temp.takeUnretainedValue()) - } - return nil - } - - /// Create an indexer using a new data container for the store - //// - /// - Parameters: - /// - fileURL: the file URL to store the index at. url must be a non-existent file - /// - properties: the properties for index creation - /// - Returns: A new index object if successful, nil otherwise. Returns nil if the file already exists at url. - public static func create( - fileURL: URL, - properties: CreateProperties = CreateProperties() - ) -> SearchIndexer.File? { - if !FileManager.default.fileExists(atPath: fileURL.absoluteString), - let skIndex = SKIndexCreateWithURL( - fileURL as CFURL, - nil, - properties.indexType, - properties.properties() - ) { - return SearchIndexer.File(url: fileURL, index: skIndex.takeUnretainedValue()) - } else { - return nil - } - } - - /// Flush, compact, i.e. apply all changes and write the content of the index to the file - public func save() { - flush() - compact() - } - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift deleted file mode 100644 index 76cb54b8c1..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// SearchIndexer+InternalMethods.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation -import UniformTypeIdentifiers - -extension SearchIndexer { - /// A "typealias" for a document ID, using a struct because swift lint doesn't allow type-aliases for 3 types - public struct DocumentID { - let url: URL - let document: SKDocument - let documentID: SKDocumentID - } - /// Returns the mime type for the url, or nil if the mime type couldn't be ascertained from the extension - /// - /// - Parameter url: the url to detect the mime type for - /// - Returns: the mime type of the url if able to detect, nil otherwise - func detectMimeType(_ url: URL) -> String? { - if let type = UTType(filenameExtension: url.pathExtension) { - if let mimeType = type.preferredMIMEType { - return mimeType - } - } - return nil - } - - /// Remove the given document from the index - /// When the app deletes a document, use this function to update the index to reflect the change, - /// i. e. the index does not need to get flushed. - func remove(document: SKDocument) -> Bool { - if let index = self.index { - return modifyIndexQueue.sync { - SKIndexRemoveDocument(index, document) - } - } - return false - } - - /// Returns the number of terms of the specified document - private func termCount(for document: SKDocumentID) -> Int { - guard self.index != nil else { - return 0 - } - return SKIndexGetDocumentTermCount(self.index!, document) - } - - /// Is the specified document empty (ie. it has no terms) - private func isEmpty(for document: SKDocumentID) -> Bool { - guard self.index != nil else { - return true // true would be the default value, i.e. document is Empty - } - return self.termCount(for: document) == 0 - } - - /// Recurse through the children of a document and return an array containing all the document-ids - private func addLeafURLs(index: SKIndex, inParentDocument: SKDocument?, docs: inout [DocumentID]) { - guard let index = self.index else { - return - } - - var isLeaf = true - - let iterator = SKIndexDocumentIteratorCreate(index, inParentDocument).takeUnretainedValue() - while let skDocument = SKIndexDocumentIteratorCopyNext(iterator) { - isLeaf = false - self.addLeafURLs(index: index, inParentDocument: skDocument.takeUnretainedValue(), docs: &docs) - } - - if isLeaf, inParentDocument != nil, - kSKDocumentStateNotIndexed != SKIndexGetDocumentState(index, inParentDocument) { - if let temp = SKDocumentCopyURL(inParentDocument) { - let baseURL = temp.takeUnretainedValue() as URL - let documentID = SKIndexGetDocumentID(index, inParentDocument) - docs.append( - DocumentID( - url: baseURL, - document: inParentDocument!, - documentID: documentID - ) - ) - } - } - } - - /// Return an array of all the documents contained within the index - /// - /// - Parameter termState: the TermState of documents to be returned (eg. all, empty only, non-empty only) - /// - Returns: An array containing all the documents matching the TermState - func fullDocuments(termState: TermState = .all) -> [DocumentID] { - guard let index = self.index else { - return [] - } - - var allDocs = [DocumentID]() - - self.addLeafURLs(index: index, inParentDocument: nil, docs: &allDocs) - - switch termState { - case .empty: - allDocs = allDocs.filter { - self.isEmpty(for: $0.documentID) - } - case .notEmpty: - allDocs = allDocs.filter { - !self.isEmpty(for: $0.documentID) - } - default: - break - } - - return allDocs - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Memory.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Memory.swift deleted file mode 100644 index 309bc01548..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Memory.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// SearchIndexer+Memory.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation -extension SearchIndexer { - /// Memory based indexing using NSMutable - public class Memory: SearchIndexer { - // The data index store - private var store = NSMutableData() - - /// Create a new in-memory index - /// - Parameter properties: the properties to use in the index - public init?(properties: CreateProperties = CreateProperties()) { - let data = NSMutableData() - if let skIndex = SKIndexCreateWithMutableData( - data, - nil, - properties.indexType, - properties.properties() - ) { - super.init(index: skIndex.takeUnretainedValue()) - self.store = data - } else { - return nil - } - } - - /// Create an in-memory index from the data provided - /// - Parameter data: The data to load the index data from - public convenience init?(data: Data) { - if let rawData = (data as NSData).mutableCopy() as? NSMutableData, - let skIndex = SKIndexOpenWithMutableData(rawData, nil) { - self.init(data: rawData, index: skIndex.takeUnretainedValue()) - } else { - return nil - } - } - - /// Create an indexer using a new data container for the store - /// - /// - Parameter properties: the properties for index creation - /// - Returns: A new index object if successful, nil otherwise - public static func create(properties: CreateProperties = CreateProperties()) -> SearchIndexer.Memory? { - let data = NSMutableData() - if let skIndex = SKIndexCreateWithMutableData( - data, - nil, - properties.indexType, - properties.properties() - ) { - return SearchIndexer.Memory(data: data, index: skIndex.takeUnretainedValue()) - } - return nil - } - - /// Create an indexer using the data stored in 'data'. - /// - /// **NOTE** Makes a copy of the data first - does not work on a live Data object - /// - /// - Parameter data: The data to load as an index - /// - Returns: A new index object if successful, nil otherwise - public static func loadFromData(data: Data) -> SearchIndexer.Memory? { - if let rawData = (data as NSData).mutableCopy() as? NSMutableData, - let skIndex = SKIndexOpenWithMutableData(rawData, nil) { - return SearchIndexer.Memory(data: rawData, index: skIndex.takeUnretainedValue()) - } - return nil - } - - /// Returns a copy of the index as data - public func getAsData() -> Data? { - flush() - return self.store.copy() as? Data - } - - private init(data: NSMutableData, index: SKIndex) { - super.init(index: index) - self.store = data - } - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift deleted file mode 100644 index ba7b6d5b76..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// SearchIndexer+ProgressiveSearch.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -extension SearchIndexer { - /// Object representing the search results - public class SearchResult { - /// The identifying url for the document - let url: URL - - /// The search score for the document, higher means more relevant - let score: Float - - init(url: URL, score: Float) { - self.url = url - self.score = score - } - } - - /// Start a progressive search - public func progressiveSearch( - query: String, - options: SKSearchOptions = SKSearchOptions(kSKSearchOptionDefault) - ) -> ProgressiveSearch { - return ProgressiveSearch(options: options, index: self, query: query) - } - - /// A class for creating and managing a progressive search. - /// A search starts on creation and can be cancelled at any time. - public class ProgressiveSearch { - /// A class representing the results of a search request. - public class Results { - /// Create a search result - /// - /// - Parameters: - /// - moreResultsAvailable: A boolean indicating whether more search results are available - /// - results: The partial results for the search request - public init(moreResultsAvailable: Bool, results: [SearchResult]) { - self.moreResultsAvailable = moreResultsAvailable - self.results = results - } - - /// A boolean indicating whether more search results are available - public let moreResultsAvailable: Bool - - /// The partial results for the search request - public let results: [SearchResult] - } - - private let options: SKSearchOptions - private let search: SKSearch - private let index: SearchIndexer - private let query: String - - init(options: SKSearchOptions, index: SearchIndexer, query: String) { - self.options = options - self.search = SKSearchCreate(index.index, query as CFString, options).takeRetainedValue() - self.index = index - self.query = query - } - - /// Retrieves the next chunk of search results in a progressive search. - /// - /// - Parameters: - /// - limit: The maximum number of results to retrieve in each call. Defaults to 10. - /// - timeout: The duration to wait for the search to complete before stopping. Defaults to 1.0 seconds. - /// - /// - Returns: A tuple containing search results and information about the progress of the search. - /// - /// The function performs a progressive search, - /// fetching the next set of results based on the specified limit and timeout. - /// It uses the Search Kit framework to find matches, retrieve document URLs, and their corresponding scores. - public func getNextSearchResultsChunk( - limit: Int = 10, - timeout: TimeInterval = 1.0 - ) -> (ProgressiveSearch.Results) { - guard self.index.index != nil else { - return Results(moreResultsAvailable: false, results: []) - } - - var scores: [Float] = Array(repeating: 0.0, count: limit) - var urls: [Unmanaged?] = Array(repeating: nil, count: limit) - var documentIDs: [SKDocumentID] = Array(repeating: 0, count: limit) - var foundCount = 0 - - let hasMore = SKSearchFindMatches(self.search, limit, &documentIDs, &scores, timeout, &foundCount) - SKIndexCopyDocumentURLsForDocumentIDs(self.index.index, foundCount, &documentIDs, &urls) - - let partialResult: [SearchResult] = zip(urls[0.. SearchResult? in - guard let url = cfurl?.takeUnretainedValue() as URL? else { - return nil - } - - return SearchResult(url: url, score: score) - } - - return Results(moreResultsAvailable: hasMore, results: partialResult) - } - - /// Cancel an active search - public func cancel() { - SKSearchCancel(self.search) - } - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Search.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Search.swift deleted file mode 100644 index 6248ab0104..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Search.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// SearchIndexer+Search.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -extension SearchIndexer { - /// Initiates a search operation based on the provided query. - /// - /// - Parameters: - /// - query: A string representing the term to be searched for. - /// - limit: The maximum number of search results to be returned. - /// - timeout: The duration to wait for the search to complete before stopping. - /// - /// - Returns: - /// An array of search results, each containing a match URL and its corresponding score, - /// indicating the relevance of the match to the query. - /// - /// The function performs a search using the specified query, - /// limiting the number of results based on the provided `limit`. - /// The `timeout` parameter determines how long the search operation will wait before stopping. - public func search( - _ query: String, - limit: Int = 10, - timeout: TimeInterval = 1.0, - options: SKSearchOptions = SKSearchOptions(kSKSearchOptionDefault) - ) -> [SearchResult] { - let search = self.progressiveSearch(query: query, options: options) - - var results: [SearchResult] = [] - var moreResultsAvailable = true - repeat { - let result = search.getNextSearchResultsChunk(limit: limit, timeout: timeout) - results.append(contentsOf: result.results) - moreResultsAvailable = result.moreResultsAvailable - } while moreResultsAvailable - - return results - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift deleted file mode 100644 index dadd5ccb71..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// SearchIndexer+Terms.swift -// CodeEdit -// -// Created by Tommy Ludwig on 18.11.23. -// - -import Foundation - -extension SearchIndexer { - /// A class to contain a term and the count of times it appears - public class TermCount { - /// A term within the document - public let term: String - - /// The number of occurrences of `term` - public let count: Int - - init(term: String, count: Int) { - self.term = term - self.count = count - } - } - - /// A enum to specify the state of the document - public enum TermState: Int { - /// All document states - case all = 0 - /// Only documents that have no terms - case empty = 1 - /// Only documents that have terms - case notEmpty = 2 - } - - /// Returns all the document URLs loaded into the index matching the specified term state - /// - /// - Parameter termState: Only return documents matching the specified document state - /// - Returns: An array containing all the document URLs - public func documents(termState: TermState = .all) -> [URL] { - return self.fullDocuments(termState: termState).map { $0.url } - } - - /// Returns the number of terms for the specified document url - public func termCount(for url: URL) -> Int { - if let index = self.index, - let document = SKDocumentCreateWithURL(url as CFURL) { - let documentID = SKIndexGetDocumentID(index, document.takeUnretainedValue()) - return SKIndexGetDocumentTermCount(index, documentID) - } - return 0 - } - - /// Is the specified document empty (ie. it has no terms) - public func isEmpty(for url: URL) -> Bool { - return self.termCount(for: url) > 0 - } - - /// Returns an array containing the terms and counts for a specified URL - /// - /// - Parameter url: The document URL in the index to locate - /// - Returns: An array of the terms and corresponding counts located in the document. - /// Returns an empty array if the document cannot be located. - public func terms(for url: URL) -> [TermCount] { - guard let index = self.index else { - return [] - } - - var result = [TermCount]() - - let document = SKDocumentCreateWithURL(url as CFURL).takeUnretainedValue() - let documentID = SKIndexGetDocumentID(index, document) - - guard let termVals = SKIndexCopyTermIDArrayForDocumentID(index, documentID), - let terms = termVals.takeUnretainedValue() as? [CFIndex] else { - return [] - } - - for term in terms { - if let termVal = SKIndexCopyTermStringForTermID(index, term) { - let termString = termVal.takeUnretainedValue() as String - if !self.stopWords.contains(termString) { - let count = SKIndexGetDocumentTermFrequency(index, documentID, term) as Int - result.append(TermCount(term: termString, count: count)) - } - } - } - - return result - } -} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift deleted file mode 100644 index a5852e3fd3..0000000000 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// SearchIndexer.swift -// CodeEdit -// -// Created by Tom Ludwig on 18.11.23. -// - -import Foundation - -/// Indexer using SKIndex -public class SearchIndexer { - let modifyIndexQueue = DispatchQueue(label: "app.codeedit.CodeEdit.ModifySearchIndex") - - var index: SKIndex? - - init(index: SKIndex) { - self.index = index - } - - deinit { - self.close() - } - - /// Flush any pending commands to the search index. Flush should always be called before performing a search - public func flush() { - if let index = self.index { - SKIndexFlush(index) - } - } - - /// Reduce the size of index where possible - /// - /// - Warning: Do NOT call on the main thread - public func compact() { - if let index = self.index { - SKIndexCompact(index) - } - } - - /// Remove any documents that have no search terms - public func cleanUp() -> Int { - let allDocs = self.fullDocuments(termState: .empty) - var removedCount = 0 - for docID in allDocs { - _ = self.remove(document: docID.document) - removedCount += 1 - } - return removedCount - } - - /// Close the index - public func close() { - if let index = self.index { - SKIndexClose(index) - self.index = nil - } - } - - /// Call once at application launch to tell Search Kit to use the Spotlight metadata importers. - lazy var dataExtractorLoaded: Bool = { - SKLoadDefaultExtractorPlugIns() - return true - }() - - /// Stop words for the index, - /// these are common words which should be ignored because they are not useful for searching - private(set) lazy var stopWords: Set = { - var stopWords: Set = [] - if let index = self.index, - let properties = SKIndexGetAnalysisProperties(self.index).takeUnretainedValue() as? [String: Any], - let newStopWords = properties[kSKStopWords as String] as? Set { - stopWords = newStopWords - } - return stopWords - }() - - public enum IndexType: UInt32 { - /// Unknown index type (kSKIndexUnknown) - case unknown = 0 - /// Inverted index, mapping terms to documents (kSKIndexInverted) - case inverted = 1 - /// Vector index, mapping documents to terms (kSKIndexVector) - case vector = 2 - /// Index type with all the capabilities of an inverted and a vector index (kSKIndexInvertedVector) - case invertedVector = 3 - } - - /// A class for creating properties used in the creation of a Search Kit index. - /// **Available Options:** - /// - `indexType`: The type of the index to be created. - /// Options include `.unknown`, `.inverted`, `.vector` or `.invertedVector` - /// - `proximityIndexing`: A Boolean flag indicating whether or not Search Kit should use proximity indexing. - /// - `stopWords`: A set of stop-words — words not to index. - /// - `minTermLength`: The minimum term length to index (defaults to 1). - public class CreateProperties { - /// The type of the index to be created - private(set) var indexType: SKIndexType = kSKIndexInverted - /// Whether the index should use proximity indexing - private(set) var proximityIndexing: Bool = false - /// The stop words for the index - private(set) var stopWords: Set = Set() - /// The minimum size of word to add to the index - private(set) var minTermLength: UInt = 1 - - /// Create a properties object with the specified creation parameters - /// - /// - Parameters: - /// - indexType: The type of index - /// - proximityIndexing: A Boolean flag indicating whether or not Search Kit should use proximity indexing - /// - stopWords: A set of stop-words — words not to index - /// - minTermLength: The minimum term length to index (defaults to 1) - public init( - indexType: SearchIndexer.IndexType = .inverted, - proximityIndexing: Bool = false, - stopWords: Set = [], - minTermLength: UInt = 1 - ) { - self.indexType = SKIndexType(indexType.rawValue) - self.proximityIndexing = proximityIndexing - self.stopWords = stopWords - self.minTermLength = minTermLength - } - - /// Returns a CFDictionary object to use for the call to SKIndexCreate - func properties() -> CFDictionary { - let properties: [CFString: Any] = [ - kSKProximityIndexing: self.proximityIndexing, - kSKStopWords: self.stopWords, - kSKMinTermLength: self.minTermLength, - ] - return properties as CFDictionary - } - } - -} diff --git a/CodeEdit/Features/Documents/LazyStringLoader.swift b/CodeEdit/Features/Documents/LazyStringLoader.swift deleted file mode 100644 index 7d58dbd215..0000000000 --- a/CodeEdit/Features/Documents/LazyStringLoader.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// LazyStringLoader.swift -// CodeEdit -// -// Created by Tommy Ludwig on 21.11.23. -// - -import Foundation - -class LazyStringLoader { - let fileURL: URL - var fileHandle: FileHandle? - let chunkSize: Int - let queue = DispatchQueue(label: "com.CodeEdit.LazyLoader") - - init(fileURL: URL, chunkSize: Int = 1024) { - self.fileURL = fileURL - self.chunkSize = chunkSize - } - - func getNextChunk() -> String? { - if fileHandle == nil { - do { - fileHandle = try FileHandle(forReadingFrom: fileURL) - } catch { - - } - - var data = Data() - let semaphore = DispatchSemaphore(value: 0) - - do { - data = try fileHandle?.read(upToCount: chunkSize) ?? Data() - } catch { - - } - - return String(data: data, encoding: .utf8) - } - return nil - } -} diff --git a/CodeEdit/Features/Documents/String+AppearancesOfSubstring.swift b/CodeEdit/Features/Documents/String+AppearancesOfSubstring.swift deleted file mode 100644 index c557a9df13..0000000000 --- a/CodeEdit/Features/Documents/String+AppearancesOfSubstring.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// String+AppearancesOfSubstring.swift -// CodeEdit -// -// Created by Tommy Ludwig on 24.11.23. -// - -import Foundation - -extension String { - /// Finds the appearances of a substring within the string. - /// - Parameters: - /// - substring: The substring to search for within the string. - /// - toLeft: The optional number of characters to include to the left of each found substring appearance. - /// - toRight: The optional number of characters to include to the right of each found substring appearance. - /// - /// - Returns: An array of ranges representing the appearances of the substring within the string. - func appearancesOfSubstring(substring: String, toLeft: Int=0, toRight: Int=0) -> [Range] { - guard !substring.isEmpty && self.contains(substring) else { return [] } - var appearances: [Range] = [] - for (index, character) in self.enumerated() where character == substring.first { - let startOfFoundCharacter = self.index(self.startIndex, offsetBy: index) - guard index + substring.count < self.count else { continue } - let lengthOfFoundCharacter = self.index(self.startIndex, offsetBy: (substring.count + index)) - if self[startOfFoundCharacter.. Character? { - guard index < self.count else { - return nil - } - - return self[self.index(self.startIndex, offsetBy: index)] - } -} diff --git a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift deleted file mode 100644 index b18c4a65fc..0000000000 --- a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// WorkspaceCodeFileView.swift -// CodeEdit -// -// Created by Pavel Kasila on 20.03.22. -// - -import SwiftUI -import UniformTypeIdentifiers -import CodeEditSourceEditor - -struct WorkspaceCodeFileView: View { - - @EnvironmentObject private var editorManager: EditorManager - - @EnvironmentObject private var editor: Editor - - var file: CEWorkspaceFile - var textViewCoordinators: [TextViewCoordinator] = [] - - @State private var update: Bool = false - - @ViewBuilder var codeView: some View { - if let document = file.fileDocument { - Group { - switch document.typeOfFile { - case .some(.text), .some(.data): - CodeFileView(codeFile: document, textViewCoordinators: textViewCoordinators) - default: - otherFileView(document, for: file) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else { - if update { - Spacer() - } - Spacer() - VStack(spacing: 10) { - ProgressView() - Text("Opening \(file.name)...") - } - Spacer() - .onAppear { - Task.detached { - let contentType = try await file.url.resourceValues(forKeys: [.contentTypeKey]).contentType - let codeFile = try await CodeFileDocument( - for: file.url, - withContentsOf: file.url, - ofType: contentType?.identifier ?? "" - ) - await MainActor.run { - file.fileDocument = codeFile - CodeEditDocumentController.shared.addDocument(codeFile) - update.toggle() - } - } - } - } - } - - @ViewBuilder - private func otherFileView( - _ otherFile: CodeFileDocument, - for item: CEWorkspaceFile - ) -> some View { - VStack(spacing: 0) { - if let url = otherFile.previewItemURL, - let image = NSImage(contentsOf: url), - otherFile.typeOfFile == .image { - GeometryReader { proxy in - if image.size.width > proxy.size.width || image.size.height > proxy.size.height { - OtherFileView(otherFile) - } else { - OtherFileView(otherFile) - .frame( - width: proxy.size.width * (proxy.size.width / image.size.width), - height: proxy.size.height - ) - .position(x: proxy.frame(in: .local).midX, y: proxy.frame(in: .local).midY) - } - } - } else { - OtherFileView(otherFile) - } - } - } - - var body: some View { - codeView - .frame(maxWidth: .infinity, maxHeight: .infinity) - .onHover { hover in - DispatchQueue.main.async { - if hover { - NSCursor.iBeam.push() - } else { - NSCursor.pop() - } - } - } - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Find.swift b/CodeEdit/Features/Documents/WorkspaceDocument+Find.swift deleted file mode 100644 index 4f474020c7..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceDocument+Find.swift +++ /dev/null @@ -1,333 +0,0 @@ -// -// WorkspaceDocument+Find.swift -// CodeEdit -// -// Created by Tommy Ludwig on 02.01.24. -// - -import Foundation - -extension WorkspaceDocument.SearchState { - /// Creates a search term based on the given query and search mode. - /// - /// - Parameter query: The original user query string. - /// - /// - Returns: A modified search term according to the specified search mode. - func getSearchTerm(_ query: String) -> String { - let newQuery = caseSensitive ? query : query.lowercased() - guard let mode = selectedMode.third else { - return newQuery - } - switch mode { - case .Containing: - return "*\(newQuery)*" - case .StartingWith: - return "\(newQuery)*" - case .EndingWith: - return "*\(newQuery)" - default: - return newQuery - } - } - - /// Generates a regular expression pattern based on the specified query and search mode. - /// - /// - Parameter query: The original user query string. - /// - /// - Returns: A string representing the regular expression pattern based on the selected search mode. - /// - /// - Note: This function is creating similar patterns to the - /// ``WorkspaceDocument/SearchState-swift.class/getSearchTerm(_:)`` function, - /// Except its using the word boundary anchor(\b) instead of the asterisk(\*). - /// This is needed to highlight the search results correctly. - func getRegexPattern(_ query: String) -> String { - guard let mode = selectedMode.third else { - return query - } - - switch mode { - case .Containing: - return "\(query)" - case .StartingWith: - return "\\b\(query)" - case .EndingWith: - return "\(query)\\b" - case .MatchingWord: - return "\\b\(query)\\b" - default: - return query - } - } - - /// Searches the entire workspace for the given string, using the - /// ``WorkspaceDocument/SearchState-swift.class/selectedMode`` modifiers - /// to modify the search if needed. This is done by filtering out files with SearchKit and then searching - /// within each file for the given string. - /// - /// This method will update - /// ``WorkspaceDocument/SearchState-swift.class/searchResult``, - /// ``WorkspaceDocument/SearchState-swift.class/searchResultsFileCount`` - /// and ``WorkspaceDocument/SearchState-swift.class/searchResultCount`` with any matched - /// search results. See ``SearchResultModel`` and ``SearchResultMatchModel`` - /// for more information on search results and matches. - /// - /// - Parameter query: The search query to search for. - func search(_ query: String) async { - clearResults() - - await MainActor.run { - self.searchQuery = query - self.findNavigatorStatus = .searching - } - - let searchQuery = getSearchTerm(query) - - // The regexPattern is only used for the evaluateFile function - // to ensure that the search terms are highlighted correctly - let regexPattern = getRegexPattern(query) - - guard let indexer = indexer else { - await setStatus(.failed(errorMessage: "No index found. Try rebuilding the index.")) - return - } - - let asyncController = SearchIndexer.AsyncManager(index: indexer) - - let evaluateResultGroup = DispatchGroup() - let evaluateSearchQueue = DispatchQueue(label: "app.codeedit.CodeEdit.EvaluateSearch") - - let searchStream = await asyncController.search(query: searchQuery, 20) - for try await result in searchStream { - for file in result.results { - evaluateSearchQueue.async(group: evaluateResultGroup) { - evaluateResultGroup.enter() - Task { - var newResult = SearchResultModel(file: CEWorkspaceFile(url: file.url), score: file.score) - await self.evaluateFile(query: regexPattern, searchResult: &newResult) - - // Check if the new result has any line matches. - if !newResult.lineMatches.isEmpty { - // The function needs to be called because, - // we are trying to modify the array from within a concurrent context. - self.appendNewResultsToTempResults(newResult: newResult) - } - evaluateResultGroup.leave() - } - } - } - } - - evaluateResultGroup.notify(queue: evaluateSearchQueue) { - self.setSearchResults() - } - } - - /// Appends a new search result to the temporary search results array on the main thread. - /// - /// - Parameters: - /// - newResult: The `SearchResultModel` to be appended to the temporary search results. - func appendNewResultsToTempResults(newResult: SearchResultModel) { - DispatchQueue.main.async { - self.tempSearchResults.append(newResult) - } - } - - /// Sets the search results by updating various properties on the main thread. - /// This function updates `findNavigatorStatus`, `searchResult`, `searchResultCount`, and `searchResultsFileCount` - /// and sets the `tempSearchResults` to an empty array. - /// - Important: Call this function when you are ready to - /// display or use the final search results. - func setSearchResults() { - DispatchQueue.main.async { - self.searchResult = self.tempSearchResults.sorted { $0.score > $1.score } - self.searchResultsCount = self.tempSearchResults.map { $0.lineMatches.count }.reduce(0, +) - self.searchResultsFileCount = self.tempSearchResults.count - self.findNavigatorStatus = .found - self.tempSearchResults = [] - } - } - - /// Evaluates a search query within the content of a file and updates - /// the provided `SearchResultModel` with matching occurrences. - /// - /// - Parameters: - /// - query: The search query to be evaluated, potentially containing a regular expression. - /// - searchResult: The `SearchResultModel` object to be updated with the matching occurrences. - /// - /// This function retrieves the content of a file specified in the `searchResult` parameter - /// and applies a search query using a regular expression. - /// It then iterates over the matches found in the file content, - /// creating `SearchResultMatchModel` instances for each match. - /// The resulting matches are appended to the `lineMatches` property of the `searchResult`. - /// Line matches are the preview lines that are shown in the search results. - /// - /// # Example Usage - /// ```swift - /// var resultModel = SearchResultModel() - /// await evaluateFile(query: "example", searchResult: &resultModel) - /// ``` - private func evaluateFile(query: String, searchResult: inout SearchResultModel) async { - guard let data = try? Data(contentsOf: searchResult.file.url), - let fileContent = String(data: data, encoding: .utf8) else { - return - } - - // Attempt to create a regular expression from the provided query - guard let regex = try? NSRegularExpression( - pattern: query, - options: caseSensitive ? [] : [.caseInsensitive] - ) else { - await setStatus(.failed(errorMessage: "Invalid regular expression.")) - return - } - - // Find all matches of the query within the file content using the regular expression - let matches = regex.matches(in: fileContent, range: NSRange(location: 0, length: fileContent.utf16.count)) - - var newMatches = [SearchResultMatchModel]() - - // Process each match and add it to the array of `newMatches` - for match in matches { - if let matchRange = Range(match.range, in: fileContent) { - let matchWordLength = match.range.length - let matchModel = createMatchModel( - from: matchRange, - fileContent: fileContent, - file: searchResult.file, - matchWordLength: matchWordLength - ) - newMatches.append(matchModel) - } - } - - searchResult.lineMatches = newMatches - } - - /// Creates a `SearchResultMatchModel` instance based on the provided parameters, - /// representing a matching occurrence within a file. - /// - /// - Parameters: - /// - matchRange: The range of the matched substring within the entire file content. - /// - fileContent: The content of the file where the match was found. - /// - file: The `CEWorkspaceFile` object representing the file containing the match. - /// - matchWordLength: The length of the matched substring. - /// - /// - Returns: A `SearchResultMatchModel` instance representing the matching occurrence. - /// - /// This function is responsible for constructing a `SearchResultMatchModel` - /// based on the provided parameters. It extracts the relevant portions of the file content, - /// including the lines before and after the match, and combines them into a final line. - /// The resulting model includes information about the match's range within the file, - /// the file itself, the content of the line containing the match, - /// and the range of the matched keyword within that line. - private func createMatchModel( - from matchRange: Range, - fileContent: String, - file: CEWorkspaceFile, - matchWordLength: Int - ) -> SearchResultMatchModel { - let preLine = extractPreLine(from: matchRange, fileContent: fileContent) - let keywordRange = extractKeywordRange(from: preLine, matchWordLength: matchWordLength) - let postLine = extractPostLine(from: matchRange, fileContent: fileContent) - - let finalLine = preLine + postLine - - return SearchResultMatchModel( - rangeWithinFile: matchRange, - file: file, - lineContent: finalLine, - keywordRange: keywordRange - ) - } - - /// Extracts the line preceding a matching occurrence within a file. - /// - /// - Parameters: - /// - matchRange: The range of the matched substring within the entire file content. - /// - fileContent: The content of the file where the match was found. - /// - /// - Returns: A string representing the line preceding the match. - /// - /// This function retrieves the line preceding a matching occurrence within the provided file content. - /// It considers a context of up to 60 characters before the match and clips the result to the last - /// occurrence of a newline character, ensuring that only the line containing the search term is displayed. - /// The extracted line is then trimmed of leading and trailing whitespaces and - /// newline characters before being returned. - private func extractPreLine(from matchRange: Range, fileContent: String) -> String { - let preRangeStart = fileContent.index( - matchRange.lowerBound, - offsetBy: -60, - limitedBy: fileContent.startIndex - ) ?? fileContent.startIndex - - let preRangeEnd = matchRange.upperBound - let preRange = preRangeStart.. Range { - let keywordLowerBound = preLine.index( - preLine.endIndex, - offsetBy: -matchWordLength, - limitedBy: preLine.startIndex - ) ?? preLine.endIndex - let keywordUpperBound = preLine.endIndex - - return keywordLowerBound.., fileContent: String) -> String { - let postRangeStart = matchRange.upperBound - let postRangeEnd = fileContent.index( - matchRange.upperBound, - offsetBy: 60, - limitedBy: fileContent.endIndex - ) ?? fileContent.endIndex - - let postRange = postRangeStart.. 0 { - // All files failed to updated - await setStatus( - .failed( - errorMessage: "All files failed to update. (\(errorCount)) " + - "errors occurred. Check logs for more information" - ) - ) - } else if updatedFilesCount > 0 && errorCount > 0 { - // Some files updated successfully, some failed - await setStatus( - .failed( - errorMessage: "\(updatedFilesCount) successfully updated, " + - "\(errorCount) errors occurred. Please check logs for more information." - ) - ) - } else { - // Each files updated successfully - await setStatus(.replaced(updatedFiles: updatedFilesCount)) - } - } - - /// Replaces occurrences of a specified query with a given replacement term in the content of a file. - /// - /// - Parameters: - /// - fileURL: The URL of the file to be processed. - /// - query: The string to be searched for and replaced. - /// - replacingTerm: The string to replace occurrences of the query. - /// - options: The options for the search and replace operation. - /// You can use options such as regular expression or case-insensitivity. - /// - /// The function performs the replacement in memory and then writes the modified content back to the file - func replaceOccurrencesInFile( - fileURL: URL, - query: String, - replacingTerm: String - ) async throws { - let fileContent = try String(contentsOf: fileURL, encoding: .utf8) - let updatedContent = fileContent.replacingOccurrences( - of: query, - with: replacingTerm, - options: self.replaceOptions - ) - - try updatedContent.write(to: fileURL, atomically: true, encoding: .utf8) - } - - /// Replaces a specified range of text within a file with a new string. - /// - /// - Parameters: - /// - file: The URL of the file to be modified. - /// - searchTerm: The string to be replaced within the specified range. - /// - replacingTerm: The string to replace the specified searchTerm. - /// - keywordRange: The range within which the replacement should occur. - /// - /// - Note: This function can be utilised for two specific use cases: - /// 1. To replace a particular occurrence of a string within a file, - /// provide the range of the keyword to be replaced. - /// 2. To replace all occurrences of the string within the file, - /// pass the start and end index covering the entire range. - func replaceRange( - file: URL, - searchTerm: String, - replacingTerm: String, - keywordRange: Range - ) { - guard let fileContent = try? String(contentsOf: file, encoding: .utf8) else { - let alert = NSAlert() - alert.messageText = "Error" - alert.informativeText = "An error occurred while reading file contents of: \(file)" - alert.alertStyle = .critical - alert.addButton(withTitle: "OK") - alert.runModal() - - return - } - - var replaceOptions = NSString.CompareOptions() - if selectedMode.second == .RegularExpression { - replaceOptions = [.regularExpression] - } - if !caseSensitive { - replaceOptions = [.caseInsensitive] - } - - let updatedContent = fileContent.replacingOccurrences( - of: searchTerm, - with: replacingTerm, - options: replaceOptions, - range: keywordRange - ) - - do { - try updatedContent.write(to: file, atomically: true, encoding: .utf8) - } catch { - let alert = NSAlert() - alert.messageText = "Error" - alert.informativeText = "An error occurred while writing to: \(error.localizedDescription)" - alert.alertStyle = .critical - alert.addButton(withTitle: "OK") - alert.runModal() - } - } - - func setStatus(_ status: FindNavigatorStatus) async { - await MainActor.run { - self.findNavigatorStatus = status - } - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Index.swift b/CodeEdit/Features/Documents/WorkspaceDocument+Index.swift deleted file mode 100644 index 40ca53be7f..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceDocument+Index.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// WorkspaceDocument+Index.swift -// CodeEdit -// -// Created by Tommy Ludwig on 02.01.24. -// - -import Foundation - -extension WorkspaceDocument.SearchState { - /// Adds the contents of the current workspace URL to the search index. - /// That means that the contents of the workspace will be indexed and searchable. - func addProjectToIndex() { - guard let indexer = indexer else { - return - } - - guard let url = workspace.fileURL else { - return - } - - indexStatus = .indexing(progress: 0.0) - - Task.detached { - let filePaths = self.getFileURLs(at: url) - - let asyncController = SearchIndexer.AsyncManager(index: indexer) - var lastProgress: Double = 0 - - for await (file, index) in AsyncFileIterator(fileURLs: filePaths) { - _ = await asyncController.addText(files: [file], flushWhenComplete: false) - let progress = Double(index) / Double(filePaths.count) - - // Send only if difference is > 0.5%, to keep updates from sending too frequently - if progress - lastProgress > 0.005 || index == filePaths.count - 1 { - lastProgress = progress - await MainActor.run { - self.indexStatus = .indexing(progress: progress) - } - } - } - asyncController.index.flush() - - await MainActor.run { - self.indexStatus = .done - } - } - } - - /// Retrieves an array of file URLs within the specified directory URL. - /// - /// - Parameter url: The URL of the directory to search for files. - /// - /// - Returns: An array of file URLs found within the specified directory. - func getFileURLs(at url: URL) -> [URL] { - let enumerator = FileManager.default.enumerator( - at: url, - includingPropertiesForKeys: [.isRegularFileKey], - options: [.skipsHiddenFiles, .skipsPackageDescendants] - ) - return enumerator?.allObjects as? [URL] ?? [] - } - - /// Retrieves the contents of a files from the specified file paths. - /// - /// - Parameter filePaths: An array of file URLs representing the paths of the files. - /// - /// - Returns: An array of `TextFile` objects containing the standardised file URLs and text content. - func getFileContent(from filePaths: [URL]) async -> [SearchIndexer.AsyncManager.TextFile] { - var textFiles = [SearchIndexer.AsyncManager.TextFile]() - for file in filePaths { - if let content = try? String(contentsOf: file) { - textFiles.append( - SearchIndexer.AsyncManager.TextFile(url: file.standardizedFileURL, text: content) - ) - } - } - return textFiles - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Listeners.swift b/CodeEdit/Features/Documents/WorkspaceDocument+Listeners.swift deleted file mode 100644 index 70bc18e78f..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceDocument+Listeners.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// WorkspaceDocument+CommandListeners.swift -// CodeEdit -// -// Created by Khan Winter on 6/5/22. -// - -import Foundation -import Combine - -class WorkspaceNotificationModel: ObservableObject { - - @Published var highlightedFileItem: CEWorkspaceFile? - - init() { - highlightedFileItem = nil - } - -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+SearchState.swift b/CodeEdit/Features/Documents/WorkspaceDocument+SearchState.swift deleted file mode 100644 index 8cccb01e50..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceDocument+SearchState.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// WorkspaceDocument+SearchState.swift -// CodeEdit -// -// Created by Tom Ludwig on 16.01.24. -// - -import Foundation - -extension WorkspaceDocument { - final class SearchState: ObservableObject { - enum IndexStatus: Equatable { - case none - case indexing(progress: Double) - case done - } - - enum FindNavigatorStatus: Equatable { - case none - case searching - case replacing - case found - case replaced(updatedFiles: Int) - case failed(errorMessage: String) - } - - @Published var searchResult: [SearchResultModel] = [] - @Published var searchResultsFileCount: Int = 0 - @Published var searchResultsCount: Int = 0 - /// searchQuery stands for the last search query that corresponds to the search results - /// At the time it's only purpose is to show the query if no files could be found - @Published var searchQuery: String = "" - - @Published var indexStatus: IndexStatus = .none - - @Published var findNavigatorStatus: FindNavigatorStatus = .none - - unowned var workspace: WorkspaceDocument - var tempSearchResults = [SearchResultModel]() - var caseSensitive: Bool = false - var indexer: SearchIndexer? - var selectedMode: [SearchModeModel] = [ - .Find, - .Text, - .Containing - ] - - init(_ workspace: WorkspaceDocument) { - self.workspace = workspace - self.indexer = SearchIndexer.Memory.create() - addProjectToIndex() - } - - /// Represents the compare options to be used for find and replace. - /// - /// The `replaceOptions` property is a lazy, computed property that dynamically calculates - /// the compare options based on the values of `selectedMode` and `ignoreCase`. It is used - /// for controlling string replacement behavior for the find and replace functions. - /// - /// - Note: This property is implemented as a lazy property in the main class body because - /// extensions cannot contain stored properties directly. - lazy var replaceOptions: NSString.CompareOptions = { - var options: NSString.CompareOptions = [] - - if selectedMode.second == .RegularExpression { - options.insert(.regularExpression) - } - - if !caseSensitive { - options.insert(.caseInsensitive) - } - - return options - }() - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift deleted file mode 100644 index ab49327c62..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ /dev/null @@ -1,230 +0,0 @@ -// -// WorkspaceDocument.swift -// CodeEdit -// -// Created by Pavel Kasila on 17.03.22. -// - -import Foundation -import AppKit -import SwiftUI -import Combine - -@objc(WorkspaceDocument) -final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { - - @Published var sortFoldersOnTop: Bool = true - - var workspaceFileManager: CEWorkspaceFileManager? - - var editorManager = EditorManager() - - private var workspaceState: [String: Any] { - get { - let key = "workspaceState-\(self.fileURL?.absoluteString ?? "")" - return UserDefaults.standard.object(forKey: key) as? [String: Any] ?? [:] - } - set { - let key = "workspaceState-\(self.fileURL?.absoluteString ?? "")" - UserDefaults.standard.set(newValue, forKey: key) - } - } - - var utilityAreaModel = UtilityAreaViewModel() - var searchState: SearchState? - var quickOpenViewModel: QuickOpenViewModel? - var commandsPaletteState: CommandPaletteViewModel? - var listenerModel: WorkspaceNotificationModel = .init() - var sourceControlManager: SourceControlManager? - - private var cancellables = Set() - - deinit { - cancellables.forEach { $0.cancel() } - NotificationCenter.default.removeObserver(self) - } - - func getFromWorkspaceState(_ key: WorkspaceStateKey) -> Any? { - return workspaceState[key.rawValue] - } - - func addToWorkspaceState(key: WorkspaceStateKey, value: Any?) { - if let value { - workspaceState.updateValue(value, forKey: key.rawValue) - } else { - workspaceState.removeValue(forKey: key.rawValue) - } - } - - // MARK: NSDocument - - private let ignoredFilesAndDirectory = [ - ".DS_Store" - ] - - override class var autosavesInPlace: Bool { - false - } - - override var isDocumentEdited: Bool { - false - } - - override func makeWindowControllers() { - let window = NSWindow( - contentRect: NSRect(x: 0, y: 0, width: 1400, height: 900), - styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], - backing: .buffered, - defer: false - ) - // Setting the "min size" like this is hacky, but SwiftUI overrides the contentRect and - // any of the built-in window size functions & autosave stuff. So we have to set it like this. - // SwiftUI also ignores this value, so it just manages to set the initial window size. *Hopefully* this - // is fixed in the future. - if let rectString = getFromWorkspaceState(.workspaceWindowSize) as? String { - window.minSize = NSRectFromString(rectString).size - } else { - window.minSize = .init(width: 1400, height: 900) - } - let windowController = CodeEditWindowController( - window: window, - workspace: self - ) - - if let rectString = getFromWorkspaceState(.workspaceWindowSize) as? String { - window.setFrameOrigin(NSRectFromString(rectString).origin) - } else { - window.center() - } - self.addWindowController(windowController) - } - - // MARK: Set Up Workspace - - private func initWorkspaceState(_ url: URL) throws { - self.fileURL = url - self.displayName = url.lastPathComponent - - let sourceControlManager = SourceControlManager( - workspaceURL: url, - editorManager: editorManager - ) - self.workspaceFileManager = .init( - folderUrl: url, - ignoredFilesAndFolders: Set(ignoredFilesAndDirectory), - sourceControlManager: sourceControlManager - ) - self.sourceControlManager = sourceControlManager - sourceControlManager.fileManager = workspaceFileManager - self.searchState = .init(self) - self.quickOpenViewModel = .init(fileURL: url) - self.commandsPaletteState = .init() - - editorManager.restoreFromState(self) - utilityAreaModel.restoreFromState(self) - } - - override func read(from url: URL, ofType typeName: String) throws { - try initWorkspaceState(url) - } - - override func write(to url: URL, ofType typeName: String) throws {} - - // MARK: Close Workspace - - override func close() { - editorManager.saveRestorationState(self) - utilityAreaModel.saveRestorationState(self) - super.close() - } - - /// Determines the windows should be closed. - /// - /// This method iterates all edited documents If there are any edited documents. - /// - /// A panel giving the user the choice of canceling, discarding changes, or saving is presented while iteration. - /// - /// If the user chooses cancel on the panel, iteration is broken. - /// - /// In the last step, `shouldCloseSelector` is called with true if all documents are clean, otherwise false - /// - /// - Parameters: - /// - windowController: The windowController may be closed. - /// - delegate: The object which is a target of `shouldCloseSelector`. - /// - shouldClose: The callback which receives result of this method. - /// - contextInfo: The additional info which is not used in this method. - override func shouldCloseWindowController( - _ windowController: NSWindowController, - delegate: Any?, - shouldClose shouldCloseSelector: Selector?, - contextInfo: UnsafeMutableRawPointer? - ) { - guard let object = (delegate as? NSObject), - let shouldCloseSelector = shouldCloseSelector, - let contextInfo = contextInfo - else { - super.shouldCloseWindowController( - windowController, - delegate: delegate, - shouldClose: shouldCloseSelector, - contextInfo: contextInfo - ) - return - } - // Save unsaved changes before closing - let editedCodeFiles = editorManager.editorLayout - .gatherOpenFiles() - .compactMap(\.fileDocument) - .filter(\.isDocumentEdited) - - for editedCodeFile in editedCodeFiles { - let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) - shouldClose.initialize(to: true) - defer { - _ = shouldClose.move() - shouldClose.deallocate() - } - // Present a panel giving the user the choice of canceling, discarding changes, or saving. - editedCodeFile.canClose( - withDelegate: self, - shouldClose: #selector(document(_:shouldClose:contextInfo:)), - contextInfo: shouldClose - ) - // pointee becomes false when user select cancel - guard shouldClose.pointee else { - break - } - } - // Invoke shouldCloseSelector at delegate - let implementation = object.method(for: shouldCloseSelector) - let function = unsafeBitCast( - implementation, - to: (@convention(c)(Any, Selector, Any, Bool, UnsafeMutableRawPointer?) -> Void).self - ) - let areAllOpenedCodeFilesClean = editorManager.editorLayout.gatherOpenFiles() - .compactMap(\.fileDocument) - .allSatisfy { !$0.isDocumentEdited } - function(object, shouldCloseSelector, self, areAllOpenedCodeFilesClean, contextInfo) - } - - // MARK: NSDocument delegate - - /// Receives result of `canClose` and then, set `shouldClose` to `contextInfo`'s `pointee`. - /// - /// - Parameters: - /// - document: The document may be closed. - /// - shouldClose: The result of user selection. - /// `shouldClose` becomes false if the user selects cancel, otherwise true. - /// - contextInfo: The additional info which will be set `shouldClose`. - /// `contextInfo` must be `UnsafeMutablePointer`. - @objc - func document( - _ document: NSDocument, - shouldClose: Bool, - contextInfo: UnsafeMutableRawPointer - ) { - let opaquePtr = OpaquePointer(contextInfo) - let mutablePointer = UnsafeMutablePointer(opaquePtr) - mutablePointer.pointee = shouldClose - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceStateKey.swift b/CodeEdit/Features/Documents/WorkspaceStateKey.swift deleted file mode 100644 index 67aedd6909..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceStateKey.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// WorkspaceStateKey.swift -// CodeEdit -// -// Created by Khan Winter on 7/3/23. -// - -enum WorkspaceStateKey: String { - case utilityAreaCollapsed - case utilityAreaMaximized - case utilityAreaHeight - case openTabs - case workspaceWindowSize - case splitViewWidth - case navigatorCollapsed - case inspectorCollapsed -} diff --git a/CodeEdit/Features/Editor/Models/Editor.swift b/CodeEdit/Features/Editor/Models/Editor.swift deleted file mode 100644 index ffc71f152c..0000000000 --- a/CodeEdit/Features/Editor/Models/Editor.swift +++ /dev/null @@ -1,312 +0,0 @@ -// -// Editor.swift -// CodeEdit -// -// Created by Wouter Hennen on 16/02/2023. -// - -import Foundation -import OrderedCollections -import DequeModule -import AppKit - -final class Editor: ObservableObject, Identifiable { - typealias Tab = EditorInstance - - /// Set of open tabs. - @Published var tabs: OrderedSet = [] { - didSet { - let change = tabs.symmetricDifference(oldValue) - - if tabs.count > oldValue.count { - // Amount of tabs grew, so set the first new as selected. - selectedTab = change.first - } else { - // Selected file was removed - if let selectedTab, change.contains(selectedTab) { - if let oldIndex = oldValue.firstIndex(of: selectedTab), oldIndex - 1 < tabs.count, !tabs.isEmpty { - self.selectedTab = tabs[max(0, oldIndex-1)] - } else { - self.selectedTab = nil - } - } - } - } - } - - /// The current offset in the history list. - @Published var historyOffset: Int = 0 { - didSet { - let tab = history[historyOffset] - - if !tabs.contains(tab) { - if let temporaryTab, tabs.contains(temporaryTab) { - closeTab(file: temporaryTab.file, fromHistory: true) - } - temporaryTab = tab - openTab(file: tab.file, fromHistory: true) - } - selectedTab = tab - } - } - - /// History of tab switching. - @Published var history: Deque = [] - - /// Currently selected tab. - @Published var selectedTab: Tab? - - @Published var temporaryTab: Tab? - - var id = UUID() - - weak var parent: SplitViewData? - - init() { - self.tabs = [] - self.temporaryTab = nil - self.parent = nil - } - - init( - files: OrderedSet = [], - selectedTab: Tab? = nil, - temporaryTab: Tab? = nil, - parent: SplitViewData? = nil - ) { - self.tabs = [] - self.parent = parent - files.forEach { openTab(file: $0) } - self.selectedTab = selectedTab ?? (files.isEmpty ? nil : Tab(file: files.first!)) - self.temporaryTab = temporaryTab - } - - init( - files: OrderedSet = [], - selectedTab: Tab? = nil, - temporaryTab: Tab? = nil, - parent: SplitViewData? = nil - ) { - self.tabs = [] - self.parent = parent - files.forEach { openTab(file: $0.file) } - self.selectedTab = selectedTab ?? tabs.first - self.temporaryTab = temporaryTab - } - - /// Closes the editor. - func close() { - parent?.closeEditor(with: id) - } - - /// Gets the editor layout. - func getEditorLayout() -> EditorLayout? { - return parent?.getEditorLayout(with: id) - } - - /// Closes a tab in the editor. - /// This will also write any changes to the file on disk and will add the tab to the tab history. - /// - Parameter item: the tab to close. - func closeTab(file: CEWorkspaceFile, fromHistory: Bool = false) { - guard canCloseTab(file: file) else { return } - - if temporaryTab?.file == file { - temporaryTab = nil - } - if !fromHistory { - historyOffset = 0 - } - if file != selectedTab?.file { - history.prepend(EditorInstance(file: file)) - } - removeTab(file) - if let selectedTab { - history.prepend(selectedTab) - } - // Reset change count to 0 - file.fileDocument?.updateChangeCount(.changeCleared) - // remove file from memory - file.fileDocument = nil - } - - /// Closes the currently opened tab in the tab group. - func closeSelectedTab() { - guard let file = selectedTab?.file else { - return - } - - closeTab(file: file) - } - - /// Opens a tab in the editor. - /// If a tab for the item already exists, it is used instead. - /// - Parameters: - /// - file: the file to open. - /// - asTemporary: indicates whether the tab should be opened as a temporary tab or a permanent tab. - func openTab(file: CEWorkspaceFile, asTemporary: Bool) { - let item = EditorInstance(file: file) - // Item is already opened in a tab. - guard !tabs.contains(item) || !asTemporary else { - selectedTab = item - history.prepend(item) - return - } - - switch (temporaryTab, asTemporary) { - case (.some(let tab), true): - if let index = tabs.firstIndex(of: tab) { - history.removeFirst(historyOffset) - history.prepend(item) - historyOffset = 0 - tabs.remove(tab) - tabs.insert(item, at: index) - self.selectedTab = item - temporaryTab = item - } - - case (.some(let tab), false) where tab == item: - temporaryTab = nil - - case (.none, true): - openTab(file: item.file) - temporaryTab = item - - case (.none, false): - openTab(file: item.file) - - default: - break - } - - do { - try openFile(item: item) - } catch { - print(error) - } - } - - /// Opens a tab in the editor. - /// - Parameters: - /// - file: The tab to open. - /// - index: Index where the tab needs to be added. If nil, it is added to the back. - /// - fromHistory: Indicates whether the tab has been opened from going back in history. - func openTab(file: CEWorkspaceFile, at index: Int? = nil, fromHistory: Bool = false) { - let item = Tab(file: file) - if let index { - tabs.insert(item, at: index) - } else { - tabs.append(item) - } - selectedTab = item - if !fromHistory { - history.removeFirst(historyOffset) - history.prepend(item) - historyOffset = 0 - } - do { - try openFile(item: item) - } catch { - print(error) - } - } - - private func openFile(item: Tab) throws { - guard item.file.fileDocument == nil else { - return - } - - let contentType = try item.file.url.resourceValues(forKeys: [.contentTypeKey]).contentType - let codeFile = try CodeFileDocument( - for: item.file.url, - withContentsOf: item.file.url, - ofType: contentType?.identifier ?? "" - ) - item.file.fileDocument = codeFile - CodeEditDocumentController.shared.addDocument(codeFile) - } - - func goBackInHistory() { - if canGoBackInHistory { - historyOffset += 1 - } - } - - func goForwardInHistory() { - if canGoForwardInHistory { - historyOffset -= 1 - } - } - - // TODO: move to @Observable so this works better - /// Warning: NOT published! - var canGoBackInHistory: Bool { - historyOffset != history.count-1 && !history.isEmpty - } - - // TODO: move to @Observable so this works better - /// Warning: NOT published! - var canGoForwardInHistory: Bool { - historyOffset != 0 - } - - /// Check if tab can be closed - /// If document edited it will show dialog where user can save document before closing or cancel. - private func canCloseTab(file: CEWorkspaceFile) -> Bool { - guard let codeFile = file.fileDocument else { return true } - - if codeFile.isDocumentEdited { - let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) - shouldClose.initialize(to: true) - defer { - _ = shouldClose.move() - shouldClose.deallocate() - } - codeFile.canClose( - withDelegate: self, - shouldClose: #selector(document(_:shouldClose:contextInfo:)), - contextInfo: shouldClose - ) - - return shouldClose.pointee - } - - return true - } - - /// Receives result of `canClose` and then, set `shouldClose` to `contextInfo`'s `pointee`. - /// - /// - Parameters: - /// - document: The document may be closed. - /// - shouldClose: The result of user selection. - /// `shouldClose` becomes false if the user selects cancel, otherwise true. - /// - contextInfo: The additional info which will be set `shouldClose`. - /// `contextInfo` must be `UnsafeMutablePointer`. - @objc - func document( - _ document: NSDocument, - shouldClose: Bool, - contextInfo: UnsafeMutableRawPointer - ) { - let opaquePtr = OpaquePointer(contextInfo) - let mutablePointer = UnsafeMutablePointer(opaquePtr) - mutablePointer.pointee = shouldClose - } - - /// Remove the given file from tabs. - /// - Parameter file: The file to remove. - func removeTab(_ file: CEWorkspaceFile) { - tabs.removeAll { tab in - tab.file == file - } - } -} - -extension Editor: Equatable, Hashable { - static func == (lhs: Editor, rhs: Editor) -> Bool { - lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} diff --git a/CodeEdit/Features/Editor/Models/EditorInstance.swift b/CodeEdit/Features/Editor/Models/EditorInstance.swift deleted file mode 100644 index 6cc6895bb9..0000000000 --- a/CodeEdit/Features/Editor/Models/EditorInstance.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// EditorInstance.swift -// CodeEdit -// -// Created by Khan Winter on 1/4/24. -// - -import Foundation -import AppKit -import Combine -import CodeEditTextView -import CodeEditSourceEditor - -/// A single instance of an editor in a group with a published ``EditorInstance/cursorPositions`` variable to publish -/// the user's current location in a file. -class EditorInstance: Hashable { - // Public - - /// The file presented in this editor instance. - let file: CEWorkspaceFile - - /// A publisher for the user's current location in a file. - var cursorPositions: AnyPublisher<[CursorPosition], Never> { - cursorSubject.eraseToAnyPublisher() - } - - // Public TextViewCoordinator APIs - - var rangeTranslator: RangeTranslator? - - // Internal Combine subjects - - private let cursorSubject = CurrentValueSubject<[CursorPosition], Never>([]) - - // MARK: - Init, Hashable, Equatable - - init(file: CEWorkspaceFile, cursorPositions: [CursorPosition] = []) { - self.file = file - self.cursorSubject.send(cursorPositions) - self.rangeTranslator = RangeTranslator(cursorSubject: cursorSubject) - } - - func hash(into hasher: inout Hasher) { - hasher.combine(file) - } - - static func == (lhs: EditorInstance, rhs: EditorInstance) -> Bool { - lhs.file == rhs.file - } - - // MARK: - RangeTranslator - - /// Translates ranges (eg: from a cursor position) to other information like the number of lines in a range. - class RangeTranslator: TextViewCoordinator { - private weak var textViewController: TextViewController? - private var cursorSubject: CurrentValueSubject<[CursorPosition], Never> - - init(cursorSubject: CurrentValueSubject<[CursorPosition], Never>) { - self.cursorSubject = cursorSubject - } - - func textViewDidChangeSelection(controller: TextViewController, newPositions: [CursorPosition]) { - self.cursorSubject.send(controller.cursorPositions) - } - - func prepareCoordinator(controller: TextViewController) { - self.textViewController = controller - self.cursorSubject.send(controller.cursorPositions) - } - - func destroy() { - self.textViewController = nil - } - - /// Returns the lines contained in the given range. - /// - Parameter range: The range to use. - /// - Returns: The number of lines contained by the given range. Or `0` if the text view could not be found, - /// or lines could not be found for the given range. - func linesInRange(_ range: NSRange) -> Int { - // TODO: textView should be public, workaround for now - guard let controller = textViewController, - let scrollView = controller.view as? NSScrollView, - let textView = scrollView.documentView as? TextView, - // Find the lines at the beginning and end of the range - let startTextLine = textView.layoutManager.textLineForOffset(range.location), - let endTextLine = textView.layoutManager.textLineForOffset(range.upperBound) else { - return 0 - } - return (endTextLine.index - startTextLine.index) + 1 - } - } -} diff --git a/CodeEdit/Features/Editor/Models/EditorLayout+StateRestoration.swift b/CodeEdit/Features/Editor/Models/EditorLayout+StateRestoration.swift deleted file mode 100644 index 8279986f17..0000000000 --- a/CodeEdit/Features/Editor/Models/EditorLayout+StateRestoration.swift +++ /dev/null @@ -1,230 +0,0 @@ -// -// Editor+StateRestoration.swift -// CodeEdit -// -// Created by Khan Winter on 7/3/23. -// - -import Foundation -import SwiftUI -import OrderedCollections - -extension EditorManager { - /// Restores the tab manager from a captured state obtained using `saveRestorationState` - /// - Parameter workspace: The workspace to retrieve state from. - func restoreFromState(_ workspace: WorkspaceDocument) { - do { - guard let fileManager = workspace.workspaceFileManager, - let data = workspace.getFromWorkspaceState(.openTabs) as? Data else { - return - } - - let state = try JSONDecoder().decode(EditorRestorationState.self, from: data) - - guard !state.groups.isEmpty else { - logger.warning("Empty Editor State found, restoring to clean editor state.") - initCleanState() - return - } - - guard let activeEditor = state.groups.find( - editor: state.activeEditor - ) ?? state.groups.findSomeEditor() else { - logger.warning("Editor state could not restore active editor.") - initCleanState() - return - } - - fixRestoredEditorLayout(state.groups, fileManager: fileManager) - - self.editorLayout = state.groups - self.activeEditor = activeEditor - switchToActiveEditor() - } catch { - logger.warning( - "Could not restore editor state from saved data: \(error.localizedDescription, privacy: .public)" - ) - } - } - - /// Fix any hanging files after restoring from saved state. - /// - /// After decoding the state, we're left with `CEWorkspaceFile`s that don't exist in the file manager - /// so this function maps all those to 'real' files. Works recursively on all the tab groups. - /// - Parameters: - /// - group: The tab group to fix. - /// - fileManager: The file manager to use to map files. - private func fixRestoredEditorLayout(_ group: EditorLayout, fileManager: CEWorkspaceFileManager) { - switch group { - case let .one(data): - fixEditor(data, fileManager: fileManager) - case let .vertical(splitData): - splitData.editorLayouts.forEach { group in - fixRestoredEditorLayout(group, fileManager: fileManager) - } - case let .horizontal(splitData): - splitData.editorLayouts.forEach { group in - fixRestoredEditorLayout(group, fileManager: fileManager) - } - } - } - - private func findEditorLayout(group: EditorLayout, searchFor id: UUID) -> Editor? { - switch group { - case let .one(data): - return data.id == id ? data : nil - case let .vertical(splitData): - return splitData.editorLayouts.compactMap { findEditorLayout(group: $0, searchFor: id) }.first - case let .horizontal(splitData): - return splitData.editorLayouts.compactMap { findEditorLayout(group: $0, searchFor: id) }.first - } - } - - /// Fixes any hanging files after restoring from saved state. - /// - /// Resolves all file references with the workspace's file manager to ensure any referenced files use their shared - /// object representation. - /// - /// - Parameters: - /// - data: The tab group to fix. - /// - fileManager: The file manager to use to map files.a - private func fixEditor(_ editor: Editor, fileManager: CEWorkspaceFileManager) { - let resolvedTabs = editor - .tabs - .compactMap({ fileManager.getFile($0.file.url.path(), createIfNotFound: true) }) - .map({ EditorInstance(file: $0) }) - editor.tabs = OrderedSet(resolvedTabs) - if let selectedTab = editor.selectedTab { - if let resolvedFile = fileManager.getFile(selectedTab.file.url.path(), createIfNotFound: true) { - editor.selectedTab = EditorInstance(file: resolvedFile) - } else { - editor.selectedTab = nil - } - } - } - - func saveRestorationState(_ workspace: WorkspaceDocument) { - if let data = try? JSONEncoder().encode( - EditorRestorationState(activeEditor: activeEditor.id, groups: editorLayout) - ) { - workspace.addToWorkspaceState(key: .openTabs, value: data) - } else { - workspace.addToWorkspaceState(key: .openTabs, value: nil) - } - } -} - -struct EditorRestorationState: Codable { - var activeEditor: UUID - var groups: EditorLayout -} - -extension EditorLayout: Codable { - fileprivate enum EditorLayoutType: String, Codable { - case one - case vertical - case horizontal - } - - enum CodingKeys: String, CodingKey { - case type - case tabs - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let type = try container.decode(EditorLayoutType.self, forKey: .type) - switch type { - case .one: - let editor = try container.decode(Editor.self, forKey: .tabs) - self = .one(editor) - case .vertical: - let editor = try container.decode(SplitViewData.self, forKey: .tabs) - self = .vertical(editor) - case .horizontal: - let editor = try container.decode(SplitViewData.self, forKey: .tabs) - self = .horizontal(editor) - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case let .one(data): - try container.encode(EditorLayoutType.one, forKey: .type) - try container.encode(data, forKey: .tabs) - case let .vertical(data): - try container.encode(EditorLayoutType.vertical, forKey: .type) - try container.encode(data, forKey: .tabs) - case let .horizontal(data): - try container.encode(EditorLayoutType.horizontal, forKey: .type) - try container.encode(data, forKey: .tabs) - } - } -} - -extension SplitViewData: Codable { - fileprivate enum SplitViewAxis: String, Codable { - case vertical, horizontal - - init(_ swiftUI: Axis) { - switch swiftUI { - case .vertical: self = .vertical - case .horizontal: self = .horizontal - } - } - - var swiftUI: Axis { - switch self { - case .vertical: return .vertical - case .horizontal: return .horizontal - } - } - } - - enum CodingKeys: String, CodingKey { - case editorLayouts - case axis - } - - convenience init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let axis = try container.decode(SplitViewAxis.self, forKey: .axis).swiftUI - let editorLayouts = try container.decode([EditorLayout].self, forKey: .editorLayouts) - self.init(axis, editorLayouts: editorLayouts) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(editorLayouts, forKey: .editorLayouts) - try container.encode(SplitViewAxis(axis), forKey: .axis) - } -} - -extension Editor: Codable { - enum CodingKeys: String, CodingKey { - case tabs - case selectedTab - case id - } - - convenience init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fileURLs = try container.decode([URL].self, forKey: .tabs) - let selectedTab = try? container.decode(URL.self, forKey: .selectedTab) - let id = try container.decode(UUID.self, forKey: .id) - self.init( - files: OrderedSet(fileURLs.map { CEWorkspaceFile(url: $0) }), - selectedTab: selectedTab == nil ? nil : EditorInstance(file: CEWorkspaceFile(url: selectedTab!)), - parent: nil - ) - self.id = id - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(tabs.map { $0.file.url }, forKey: .tabs) - try container.encode(selectedTab?.file.url, forKey: .selectedTab) - try container.encode(id, forKey: .id) - } -} diff --git a/CodeEdit/Features/Editor/Models/EditorLayout.swift b/CodeEdit/Features/Editor/Models/EditorLayout.swift deleted file mode 100644 index ee803a76ab..0000000000 --- a/CodeEdit/Features/Editor/Models/EditorLayout.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// EditorLayout.swift -// CodeEdit -// -// Created by Wouter Hennen on 06/02/2023. -// - -import Foundation - -enum EditorLayout: Equatable { - case one(Editor) - case vertical(SplitViewData) - case horizontal(SplitViewData) - - /// Closes all tabs which present the given file - /// - Parameter file: a file. - func closeAllTabs(of file: CEWorkspaceFile) { - switch self { - case .one(let editor): - editor.removeTab(file) - case .vertical(let data), .horizontal(let data): - data.editorLayouts.forEach { - $0.closeAllTabs(of: file) - } - } - } - - /// Returns some editor, except the given editor. - /// - Parameter except: the search will exclude this editor. - /// - Returns: Some editor. - func findSomeEditor(except: Editor? = nil) -> Editor? { - switch self { - case .one(let editor) where editor != except: - return editor - case .vertical(let data), .horizontal(let data): - for editorLayout in data.editorLayouts { - if let result = editorLayout.findSomeEditor(except: except), result != except { - return result - } - } - return nil - default: - return nil - } - } - - func find(editor id: UUID) -> Editor? { - switch self { - case .one(let editor): - if editor.id == id { - return editor - } - case .vertical(let splitViewData), .horizontal(let splitViewData): - for layout in splitViewData.editorLayouts { - if let editor = layout.find(editor: id) { - return editor - } - } - } - - return nil - } - - /// Forms a set of all files currently represented by tabs. - func gatherOpenFiles() -> Set { - switch self { - case .one(let editor): - return Set(editor.tabs.map { $0.file }) - case .vertical(let data), .horizontal(let data): - return data.editorLayouts.map { $0.gatherOpenFiles() }.reduce(into: []) { $0.formUnion($1) } - } - } - - /// Flattens the splitviews. - mutating func flatten(parent: SplitViewData) { - switch self { - case .one: - break - case .horizontal(let data), .vertical(let data): - if data.editorLayouts.count == 1 { - let one = data.editorLayouts[0] - if case .one(let editor) = one { - editor.parent = parent - } - self = one - } else { - data.flatten() - } - } - } - - /// Gets flattened splitviews. - func getFlattened(parent: SplitViewData) -> [Editor] { - switch self { - case .one(let editor): - return [editor] - case .horizontal(let data), .vertical(let data): - if data.editorLayouts.count == 1 { - let one = data.editorLayouts[0] - if case .one(let editor) = one { - return [editor] - } - return [] - } else { - return data.getFlattened() - } - } - } - - var isEmpty: Bool { - switch self { - case .one: - return false - case .vertical(let splitViewData), .horizontal(let splitViewData): - return splitViewData.editorLayouts.allSatisfy { editorLayout in - editorLayout.isEmpty - } - } - } - - static func == (lhs: EditorLayout, rhs: EditorLayout) -> Bool { - switch (lhs, rhs) { - case let (.one(lhs), .one(rhs)): - return lhs == rhs - case let (.vertical(lhs), .vertical(rhs)): - return lhs.editorLayouts == rhs.editorLayouts - case let (.horizontal(lhs), .horizontal(rhs)): - return lhs.editorLayouts == rhs.editorLayouts - default: - return false - } - } -} diff --git a/CodeEdit/Features/Editor/Models/EditorManager.swift b/CodeEdit/Features/Editor/Models/EditorManager.swift deleted file mode 100644 index 4330d2e2b2..0000000000 --- a/CodeEdit/Features/Editor/Models/EditorManager.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// TabManager.swift -// CodeEdit -// -// Created by Wouter Hennen on 03/03/2023. -// - -import Combine -import Foundation -import DequeModule -import OrderedCollections -import os - -class EditorManager: ObservableObject { - let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "EditorManager") - - /// The complete editor layout. - @Published var editorLayout: EditorLayout - - @Published var isFocusingActiveEditor: Bool - - /// The Editor with active focus. - @Published var activeEditor: Editor { - didSet { - activeEditorHistory.prepend { [weak oldValue] in oldValue } - switchToActiveEditor() - } - } - - /// History of last-used editors. - var activeEditorHistory: Deque<() -> Editor?> = [] - - var fileDocuments: [CEWorkspaceFile: CodeFileDocument] = [:] - - /// notify listeners whenever tab selection changes on the active editor. - var tabBarTabIdSubject = PassthroughSubject() - var cancellable: AnyCancellable? - - // This caching mechanism is a temporary solution and is not optimized - @Published var updateCachedFlattenedEditors: Bool = true - var cachedFlettenedEditors: [Editor] = [] - var flattenedEditors: [Editor] { - if updateCachedFlattenedEditors { - cachedFlettenedEditors = self.getFlattened() - updateCachedFlattenedEditors = false - } - return cachedFlettenedEditors - } - - // MARK: - Init - - init() { - let tab = Editor() - self.activeEditor = tab - self.activeEditorHistory.prepend { [weak tab] in tab } - self.editorLayout = .horizontal(.init(.horizontal, editorLayouts: [.one(tab)])) - self.isFocusingActiveEditor = false - switchToActiveEditor() - } - - /// Initializes the editor manager's state to the "initial" state. - /// - /// Functionally identical to the initializer for this class. - func initCleanState() { - let tab = Editor() - self.activeEditor = tab - self.activeEditorHistory.prepend { [weak tab] in tab } - self.editorLayout = .horizontal(.init(.horizontal, editorLayouts: [.one(tab)])) - self.isFocusingActiveEditor = false - switchToActiveEditor() - } - - /// Flattens the splitviews. - func flatten() { - switch editorLayout { - case .horizontal(let data), .vertical(let data): - data.flatten() - default: - break - } - } - - /// Returns and array of flattened splitviews. - func getFlattened() -> [Editor] { - switch editorLayout { - case .horizontal(let data), .vertical(let data): - return data.getFlattened() - default: - return [] - } - } - - /// Opens a new tab in a editor. - /// - Parameters: - /// - item: The tab to open. - /// - editor: The editor to add the tab to. If nil, it is added to the active tab group. - func openTab(item: CEWorkspaceFile, in editor: Editor? = nil) { - let editor = editor ?? activeEditor - editor.openTab(file: item) - } - - /// bind active tap group to listen to file selection changes. - func switchToActiveEditor() { - cancellable?.cancel() - cancellable = nil - cancellable = activeEditor.$selectedTab - .sink { [weak self] tab in - self?.tabBarTabIdSubject.send(tab?.file.id) - } - } - - // MARK: - Close Editor - - /// Close an editor and fix editor manager state, updating active editor, etc. - /// - Parameter editor: The editor to close - func closeEditor(_ editor: Editor) { - editor.close() - if activeEditor == editor { - setNewActiveEditor(excluding: editor) - } - - flatten() - objectWillChange.send() - updateCachedFlattenedEditors = true - } - - /// Set a new active editor. - /// - Parameter editor: The editor to exclude. - func setNewActiveEditor(excluding editor: Editor) { - activeEditorHistory.removeAll { $0() == nil || $0() == editor } - if activeEditorHistory.isEmpty { - activeEditor = findSomeEditor(excluding: editor) - } else { - activeEditor = activeEditorHistory.removeFirst()()! - } - } - - /// Find some editor, or if one cannot be found set up the editor manager with a clean state. - /// - Parameter editor: The editor to exclude. - /// - Returns: Some editor, order is not guaranteed. - func findSomeEditor(excluding editor: Editor) -> Editor { - guard let someEditor = editorLayout.findSomeEditor(except: editor) else { - initCleanState() - return activeEditor - } - return someEditor - } - - // MARK: - Focus - - func toggleFocusingEditor(from editor: Editor) { - if !isFocusingActiveEditor { - activeEditor = editor - } - isFocusingActiveEditor.toggle() - } -} diff --git a/CodeEdit/Features/Editor/Models/Environment+ActiveEditor.swift b/CodeEdit/Features/Editor/Models/Environment+ActiveEditor.swift deleted file mode 100644 index 6e400e3e90..0000000000 --- a/CodeEdit/Features/Editor/Models/Environment+ActiveEditor.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Environment+ActiveEditor.swift -// CodeEdit -// -// Created by Wouter Hennen on 06/03/2023. -// - -import SwiftUI - -struct ActiveEditorEnvironmentKey: EnvironmentKey { - static var defaultValue = false -} - -extension EnvironmentValues { - var isActiveEditor: Bool { - get { self[ActiveEditorEnvironmentKey.self] } - set { self[ActiveEditorEnvironmentKey.self] = newValue } - } -} diff --git a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarComponent.swift b/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarComponent.swift deleted file mode 100644 index e9607f1dde..0000000000 --- a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarComponent.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// PathBar.swift -// CodeEditModules/PathBar -// -// Created by Lukas Pistrol on 18.03.22. -// - -import SwiftUI -import Combine -import CodeEditSymbols - -struct EditorPathBarComponent: View { - private let fileItem: CEWorkspaceFile - private let tappedOpenFile: (CEWorkspaceFile) -> Void - private let isLastItem: Bool - - @Environment(\.colorScheme) - var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - @EnvironmentObject var workspace: WorkspaceDocument - - @State var position: NSPoint? - @State var selection: CEWorkspaceFile - @State var isHovering: Bool = false - @State var button = NSPopUpButton() - - init( - fileItem: CEWorkspaceFile, - tappedOpenFile: @escaping (CEWorkspaceFile) -> Void, - isLastItem: Bool - ) { - self.fileItem = fileItem - self._selection = .init(wrappedValue: fileItem) - self.tappedOpenFile = tappedOpenFile - self.isLastItem = isLastItem - } - - var siblings: [CEWorkspaceFile] { - guard let fileManager = workspace.workspaceFileManager, - let parent = fileItem.parent else { - return [fileItem] - } - if let siblings = fileManager.childrenOfFile(parent), !siblings.isEmpty { - return siblings - } else { - return [fileItem] - } - } - - var body: some View { - NSPopUpButtonView(selection: $selection) { - guard let fileManager = workspace.workspaceFileManager else { return NSPopUpButton() } - - button.menu = EditorPathBarMenu( - fileItems: siblings, - fileManager: fileManager, - tappedOpenFile: tappedOpenFile - ) - button.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) - button.isBordered = false - (button.cell as? NSPopUpButtonCell)?.arrowPosition = .noArrow - - return button - } - .padding(.trailing, 11) - .background { - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(isHovering ? 0.05 : 0) - .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4))) - HStack { - Spacer() - if isHovering { - chevronUpDown - .padding(.trailing, 4) - } else if !isLastItem { - chevron - .padding(.trailing, 3) - } - } - } - .padding(.vertical, 3) - .onHover { hover in - isHovering = hover - } - .onLongPressGesture(minimumDuration: 0) { - button.performClick(nil) - } - .opacity(activeState != .inactive ? 1 : 0.75) - } - - private var chevron: some View { - Image(systemName: "chevron.compact.right") - .font(.system(size: 9, weight: activeState != .inactive ? .medium : .bold, design: .default)) - .foregroundStyle(.secondary) - .scaleEffect(x: 1.30, y: 1.0, anchor: .center) - .imageScale(.large) - } - - private var chevronUpDown: some View { - VStack(spacing: 1) { - Image(systemName: "chevron.up") - Image(systemName: "chevron.down") - } - .font(.system(size: 6, weight: .bold, design: .default)) - .padding(.top, 0.5) - } - - struct NSPopUpButtonView: NSViewRepresentable where ItemType: Equatable { - @Binding var selection: ItemType - - var popupCreator: () -> NSPopUpButton - - typealias NSViewType = NSPopUpButton - - func makeNSView(context: NSViewRepresentableContext) -> NSPopUpButton { - let newPopupButton = popupCreator() - setPopUpFromSelection(newPopupButton, selection: selection) - if let menu = newPopupButton.menu { - context.coordinator.registerForChanges(in: menu) - } - return newPopupButton - } - - func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext) { - setPopUpFromSelection(nsView, selection: selection) - } - - func setPopUpFromSelection(_ button: NSPopUpButton, selection: ItemType) { - let itemsList = button.itemArray - let matchedMenuItem = itemsList.filter { - ($0.representedObject as? ItemType) == selection - }.first - if matchedMenuItem != nil { - button.select(matchedMenuItem) - } - } - - func makeCoordinator() -> Coordinator { - return Coordinator(self) - } - - class Coordinator: NSObject { - var parent: NSPopUpButtonView - - var cancellable: AnyCancellable? - - init(_ parent: NSPopUpButtonView) { - self.parent = parent - super.init() - } - - func registerForChanges(in menu: NSMenu) { - cancellable = NotificationCenter.default - .publisher(for: NSMenu.didSendActionNotification, object: menu) - .sink { [weak self] notification in - if let menuItem = notification.userInfo?["MenuItem"] as? NSMenuItem, - let selection = menuItem as? ItemType { - self?.parent.selection = selection - } - } - } - } - } -} diff --git a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarMenu.swift b/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarMenu.swift deleted file mode 100644 index 26e0a635d0..0000000000 --- a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarMenu.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// EditorPathBarMenu.swift -// CodeEditModules/PathBar -// -// Created by Ziyuan Zhao on 2022/3/29. -// - -import AppKit - -final class EditorPathBarMenu: NSMenu, NSMenuDelegate { - private let fileItems: [CEWorkspaceFile] - private weak var fileManager: CEWorkspaceFileManager? - private let tappedOpenFile: (CEWorkspaceFile) -> Void - - init( - fileItems: [CEWorkspaceFile], - fileManager: CEWorkspaceFileManager, - tappedOpenFile: @escaping (CEWorkspaceFile) -> Void - ) { - self.fileItems = fileItems - self.fileManager = fileManager - self.tappedOpenFile = tappedOpenFile - super.init(title: "") - delegate = self - fileItems.forEach { item in - let menuItem = PathBarMenuItem(fileItem: item, tappedOpenFile: tappedOpenFile) - menuItem.onStateImage = nil - self.addItem(menuItem) - } - autoenablesItems = false - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - /// Only when menu item is highlighted then generate its submenu - func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) { - if let highlightedItem = item, let submenuItems = highlightedItem.submenu?.items, submenuItems.isEmpty { - if let highlightedFileItem = highlightedItem.representedObject as? CEWorkspaceFile { - highlightedItem.submenu = generateSubmenu(highlightedFileItem) - } - } - } - - private func generateSubmenu(_ fileItem: CEWorkspaceFile) -> EditorPathBarMenu? { - if let fileManager = fileManager, - let children = fileManager.childrenOfFile(fileItem) { - let menu = EditorPathBarMenu( - fileItems: children, - fileManager: fileManager, - tappedOpenFile: tappedOpenFile - ) - return menu - } - return nil - } -} - -final class PathBarMenuItem: NSMenuItem { - private let fileItem: CEWorkspaceFile - private let tappedOpenFile: (CEWorkspaceFile) -> Void - - init( - fileItem: CEWorkspaceFile, - tappedOpenFile: @escaping (CEWorkspaceFile) -> Void - ) { - self.fileItem = fileItem - self.tappedOpenFile = tappedOpenFile - super.init(title: fileItem.name, action: #selector(openFile), keyEquivalent: "") - - let icon = fileItem.systemImage - var color = NSColor(fileItem.iconColor) - isEnabled = true - target = self - if fileItem.isFolder { - let subMenu = NSMenu() - submenu = subMenu - color = NSColor(named: "FolderBlue") ?? NSColor(.secondary) - } - let image = fileItem.nsIcon.withSymbolConfiguration(.init(paletteColors: [color])) - self.image = image - representedObject = fileItem - if fileItem.isFolder { - self.action = nil - } - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc - func openFile() { - tappedOpenFile(fileItem) - } -} diff --git a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift b/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift deleted file mode 100644 index 3b488d4cc0..0000000000 --- a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// EditorPathBarView.swift -// CodeEditModules/PathBar -// -// Created by Lukas Pistrol on 17.03.22. -// - -import SwiftUI - -struct EditorPathBarView: View { - private let file: CEWorkspaceFile? - private let shouldShowTabBar: Bool - private let tappedOpenFile: (CEWorkspaceFile) -> Void - - @Environment(\.colorScheme) - private var colorScheme - - @Environment(\.isActiveEditor) - private var isActiveEditor - - @Environment(\.controlActiveState) - private var activeState - - static let height = 28.0 - - init( - file: CEWorkspaceFile?, - shouldShowTabBar: Bool, - tappedOpenFile: @escaping (CEWorkspaceFile) -> Void - ) { - self.file = file ?? nil - self.shouldShowTabBar = shouldShowTabBar - self.tappedOpenFile = tappedOpenFile - } - - var fileItems: [CEWorkspaceFile] { - var treePath: [CEWorkspaceFile] = [] - var currentFile: CEWorkspaceFile? = file - - while let currentFileLoop = currentFile { - treePath.insert(currentFileLoop, at: 0) - currentFile = currentFileLoop.parent - } - - return treePath - } - - var body: some View { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 0) { - if file == nil { - Text("No Selection") - .font(.system(size: 11, weight: .regular)) - .foregroundColor( - activeState != .inactive - ? isActiveEditor ? .primary : .secondary - : Color(nsColor: .tertiaryLabelColor) - ) - } else { - ForEach(fileItems, id: \.self) { fileItem in - EditorPathBarComponent( - fileItem: fileItem, - tappedOpenFile: tappedOpenFile, - isLastItem: fileItems.last == fileItem - ) - } - } - } - } - .padding(.horizontal, shouldShowTabBar ? (file == nil ? 10 : 4) : 0) - .safeAreaInset(edge: .leading, spacing: 0) { - if !shouldShowTabBar { - EditorTabBarLeadingAccessories() - } - } - .safeAreaInset(edge: .trailing, spacing: 0) { - if !shouldShowTabBar { - EditorTabBarTrailingAccessories() - } - } - .frame(height: Self.height, alignment: .center) - .opacity(activeState == .inactive ? 0.8 : 1.0) - .grayscale(isActiveEditor ? 0.0 : 1.0) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift deleted file mode 100644 index ed7a859260..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// FileEditorTabCloseButton.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/13/23. -// - -import Foundation -import SwiftUI -import Combine - -struct EditorFileTabCloseButton: View { - var isActive: Bool - var isHoveringTab: Bool - var isDragging: Bool - var closeAction: () -> Void - @Binding var closeButtonGestureActive: Bool - var item: CEWorkspaceFile - - @State private var isDocumentEdited: Bool = false - @State private var id: Int = 0 - - var body: some View { - EditorTabCloseButton( - isActive: isActive, - isHoveringTab: isHoveringTab, - isDragging: isDragging, - closeAction: closeAction, - closeButtonGestureActive: $closeButtonGestureActive, - isDocumentEdited: isDocumentEdited - ) - .id(id) - // Detects if file document changed, when this view created item.fileDocument is nil - .onReceive(item.fileDocumentPublisher, perform: { _ in - // Force re-render so isDocumentEdited publisher is updated - self.id += 1 - }) - .onReceive( - item.fileDocument?.isDocumentEditedPublisher.eraseToAnyPublisher() ?? Empty().eraseToAnyPublisher() - ) { newValue in - self.isDocumentEdited = newValue - } - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift deleted file mode 100644 index 7e922db1a5..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// EditorTabBackground.swift -// CodeEdit -// -// Created by Austin Condiff on 1/17/23. -// - -import SwiftUI - -struct EditorTabBackground: View { - var isActive: Bool - var isPressing: Bool - var isDragging: Bool - - @Environment(\.colorScheme) - private var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - @Environment(\.isActiveEditor) - private var isActiveEditor - - private var inHoldingState: Bool { - isPressing || isDragging - } - - var body: some View { - ZStack { - // Content background (visible if active) - EffectView(.contentBackground) - .opacity(isActive ? 1 : 0) - - // Accent color (visible if active) - Color(.controlAccentColor) - .hueRotation(.degrees(-5)) - .opacity( - isActive - ? colorScheme == .dark - ? activeState == .inactive ? 0.22 : inHoldingState ? 0.33 : 0.26 - : activeState == .inactive ? 0.1 : inHoldingState ? 0.27 : 0.2 - : 0 - ) - .saturation(isActiveEditor ? 1.0 : 0.0) - - // Highlight (if in dark mode) - Color(.white) - .blendMode(.plusLighter) - .opacity( - colorScheme == .dark - ? isActive - ? activeState == .inactive ? 0.04 : inHoldingState ? 0.14 : 0.09 - : isPressing ? 0.05 : 0 - : 0 - ) - - // Dragging color (if not active) - Color(.unemphasizedSelectedTextBackgroundColor) - .opacity(isDragging && !isActive ? 0.85 : 0) - } - } -} - -struct EditorTabBackground_Previews: PreviewProvider { - static var previews: some View { - EditorTabBackground(isActive: false, isPressing: false, isDragging: false) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabButtonStyle.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabButtonStyle.swift deleted file mode 100644 index 6ecd2a964b..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabButtonStyle.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// EditorTabButtonStyle.swift -// CodeEdit -// -// Created by Khan Winter on 6/4/22. -// - -import SwiftUI - -struct EditorTabButtonStyle: ButtonStyle { - @Environment(\.colorScheme) - var colorScheme - - @Binding private var isPressing: Bool - - init(isPressing: Binding) { - self._isPressing = isPressing - } - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .onChange(of: configuration.isPressed, perform: { isPressed in - self.isPressing = isPressed - }) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift deleted file mode 100644 index 75b734cd57..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// EditorTabCloseButton.swift -// CodeEdit -// -// Created by Austin Condiff on 1/17/23. -// - -import SwiftUI - -struct EditorTabCloseButton: View { - var isActive: Bool - var isHoveringTab: Bool - var isDragging: Bool - var closeAction: () -> Void - @Binding var closeButtonGestureActive: Bool - var isDocumentEdited: Bool = false - - @Environment(\.colorScheme) - var colorScheme - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - @State private var isPressingClose: Bool = false - @State private var isHoveringClose: Bool = false - - let buttonSize: CGFloat = 16 - - var body: some View { - HStack(alignment: .center) { - if tabBarStyle == .xcode { - Image(systemName: isDocumentEdited && !isHoveringTab ? "circlebadge.fill" : "xmark") - .font( - .system( - size: isDocumentEdited && !isHoveringTab ? 9.5 : 11.5, - weight: .regular, - design: .rounded - ) - ) - .foregroundColor( - isActive - ? colorScheme == .dark ? .primary : Color(.controlAccentColor) - : .secondary - ) - } else { - Image(systemName: isDocumentEdited && !isHoveringTab ? "circlebadge.fill" : "xmark") - .font(.system(size: 9.5, weight: .medium, design: .rounded)) - } - } - .frame(width: buttonSize, height: buttonSize) - .background( - colorScheme == .dark - ? Color(nsColor: .white) - .opacity(isPressingClose ? 0.10 : isHoveringClose ? 0.05 : 0) - : ( - tabBarStyle == .xcode - ? Color(nsColor: isActive ? .controlAccentColor : .black) - .opacity( - isPressingClose - ? 0.25 - : (isHoveringClose ? (isActive ? 0.10 : 0.06) : 0) - ) - : Color(nsColor: .black) - .opacity(isPressingClose ? 0.29 : (isHoveringClose ? 0.11 : 0)) - ) - ) - .foregroundColor(isPressingClose ? .primary : .secondary) - .cornerRadius(2) - .contentShape(Rectangle()) - .gesture( - DragGesture(minimumDistance: 0) - .onChanged({ _ in - isPressingClose = true - closeButtonGestureActive = true - }) - .onEnded({ value in - // If the final position of the mouse is within the bounds of the - // close button then close the tab - if value.location.x > 0 - && value.location.x < buttonSize - && value.location.y > 0 - && value.location.y < buttonSize { - closeAction() - } - isPressingClose = false - closeButtonGestureActive = false - }) - ) - .onHover { hover in - isHoveringClose = hover - } - .accessibilityLabel(Text("Close")) - // Only show when the mouse is hovering and there is no tab dragging. - .opacity((isHoveringTab || isDocumentEdited == true) && !isDragging ? 1 : 0) - .animation(.easeInOut(duration: 0.08), value: isHoveringTab) - .padding(.leading, 4) - } -} - -struct EditorTabCloseButton_Previews: PreviewProvider { - @State static var closeButtonGestureActive = true - - static var previews: some View { - EditorTabCloseButton( - isActive: false, - isHoveringTab: false, - isDragging: false, - closeAction: { print("Close tab") }, - closeButtonGestureActive: $closeButtonGestureActive - ) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift deleted file mode 100644 index 13c0306576..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift +++ /dev/null @@ -1,299 +0,0 @@ -// -// EditorTabView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 17.03.22. -// - -import SwiftUI - -struct EditorTabView: View { - - @Environment(\.colorScheme) - private var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - @Environment(\.isActiveEditor) - private var isActiveEditor - - @Environment(\.isFullscreen) - private var isFullscreen - - @EnvironmentObject private var editorManager: EditorManager - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - @AppSettings(\.general.fileIconStyle) - var fileIconStyle - - /// Is cursor hovering over the entire tab. - @State private var isHovering: Bool = false - - /// Is cursor hovering over the close button. - @State private var isHoveringClose: Bool = false - - /// Is entire tab being pressed. - @State private var isPressing: Bool = false - - /// Is close button being pressed. - @State private var isPressingClose: Bool = false - - /// A bool state for going-in animation. - /// - /// By default, this value is `false`. When the root view is appeared, it turns `true`. - @State private var isAppeared: Bool = false - - /// The expected tab width in native tab bar style. - private var expectedWidth: CGFloat - - /// The id associating with the tab that is currently being dragged. - /// - /// When `nil`, then there is no tab being dragged. - private var draggingTabId: CEWorkspaceFile.ID? - - private var onDragTabId: CEWorkspaceFile.ID? - - @Binding private var closeButtonGestureActive: Bool - - @EnvironmentObject private var editor: Editor - - /// The item associated with the current tab. - /// - /// You can get tab-related information from here, like `label`, `icon`, etc. - private var item: CEWorkspaceFile - - var index: Int - - private var isTemporary: Bool { - editor.temporaryTab?.file == item - } - - /// Is the current tab the active tab. - private var isActive: Bool { - item == editor.selectedTab?.file - } - - /// Is the current tab being dragged. - private var isDragging: Bool { - draggingTabId == item.id - } - - /// Is the current tab being held (by click and hold, not drag). - /// - /// I use the name `inHoldingState` to avoid any confusion with `isPressing` and `isDragging`. - private var inHoldingState: Bool { - isPressing || isDragging - } - - /// Switch the active tab to current tab. - private func switchAction() { - // Only set the `selectedId` when they are not equal to avoid performance issue for now. - editorManager.activeEditor = editor - if editor.selectedTab?.file != item { - let tabItem = EditorInstance(file: item) - editor.selectedTab = tabItem - editor.history.removeFirst(editor.historyOffset) - editor.history.prepend(tabItem) - editor.historyOffset = 0 - } - } - - /// Close the current tab. - func closeAction() { - isAppeared = false - editor.closeTab(file: item) - } - - init( - expectedWidth: CGFloat, - item: CEWorkspaceFile, - index: Int, - draggingTabId: CEWorkspaceFile.ID?, - onDragTabId: CEWorkspaceFile.ID?, - closeButtonGestureActive: Binding - ) { - self.expectedWidth = expectedWidth - self.item = item - self.index = index - self.draggingTabId = draggingTabId - self.onDragTabId = onDragTabId - self._closeButtonGestureActive = closeButtonGestureActive - } - - @ViewBuilder var content: some View { - HStack(spacing: 0.0) { - EditorTabDivider() - .opacity( - (isActive || inHoldingState) - && tabBarStyle == .xcode ? 0.0 : 1.0 - ) - .padding(.top, isActive && tabBarStyle == .native ? 1.22 : 0) - // Tab content (icon and text). - HStack(alignment: .center, spacing: 3) { - Image(nsImage: item.nsIcon) - .resizable() - .aspectRatio(contentMode: .fit) - .foregroundColor( - fileIconStyle == .color - && activeState != .inactive && isActiveEditor - ? item.iconColor - : .secondary - ) - .frame(width: 16, height: 16) - Text(item.name) - .font( - isTemporary - ? .system(size: 11.0).italic() - : .system(size: 11.0) - ) - .lineLimit(1) - } - .frame( - // To horizontally max-out the given width area ONLY in native tab bar style. - maxWidth: tabBarStyle == .native ? .infinity : nil, - // To max-out the parent (tab bar) area. - maxHeight: .infinity - ) - .padding(.horizontal, tabBarStyle == .native ? 28 : 20) - .overlay { - ZStack { - // Close Button with is file changed indicator - EditorFileTabCloseButton( - isActive: isActive, - isHoveringTab: isHovering, - isDragging: draggingTabId != nil || onDragTabId != nil, - closeAction: closeAction, - closeButtonGestureActive: $closeButtonGestureActive, - item: item - ) - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .opacity( - // Inactive states for tab bar item content. - activeState != .inactive - ? 1.0 - : ( - isActive - ? (tabBarStyle == .xcode ? 0.6 : 0.35) - : (tabBarStyle == .xcode ? 0.4 : 0.55) - ) - ) - EditorTabDivider() - .opacity( - (isActive || inHoldingState) - && tabBarStyle == .xcode ? 0.0 : 1.0 - ) - .padding(.top, isActive && tabBarStyle == .native ? 1.22 : 0) - } - .overlay(alignment: .top) { - // Only show NativeTabShadow when `tabBarStyle` is native and this tab is not active. - EditorTabBarTopDivider() - .opacity(tabBarStyle == .native && !isActive ? 1 : 0) - } - .foregroundColor( - isActive && isActiveEditor - ? ( - tabBarStyle == .xcode && colorScheme != .dark - ? Color(nsColor: .controlAccentColor) - : .primary - ) - : ( - tabBarStyle == .xcode - ? .primary - : .secondary - ) - ) - .frame(maxHeight: .infinity) // To vertically max-out the parent (tab bar) area. - .contentShape(Rectangle()) // Make entire area clickable. - .onHover { hover in - isHovering = hover - DispatchQueue.main.async { - if hover { - NSCursor.arrow.push() - } else { - NSCursor.pop() - } - } - } - } - - var body: some View { - Button(action: switchAction) { - ZStack { - content - } - .background { - if tabBarStyle == .xcode { - EditorTabBackground(isActive: isActive, isPressing: isPressing, isDragging: isDragging) - .animation(.easeInOut(duration: 0.08), value: isHovering) - } else { - if isFullscreen && isActive { - EditorTabBarNativeActiveMaterial() - } else { - EditorTabBarNativeMaterial() - } - ZStack { - // Native inactive tab background dim. - EditorTabBarNativeInactiveBgColor() - // Native inactive tab hover state. - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(isHovering ? (colorScheme == .dark ? 0.08 : 0.05) : 0.0) - .animation(.easeInOut(duration: 0.10), value: isHovering) - } - .padding(.horizontal, 1) - .opacity(isActive ? 0 : 1) - } - } - // TODO: Enable the following code snippet when dragging-out behavior should be allowed. - // Since we didn't handle the drop-outside event, dragging-out is disabled for now. - // .onDrag({ - // onDragTabId = item.tabID - // return .init(object: NSString(string: "\(item.tabID)")) - // }) - } - .buttonStyle(EditorTabButtonStyle(isPressing: $isPressing)) - .simultaneousGesture( - TapGesture(count: 2) - .onEnded { _ in - if isTemporary { - editor.temporaryTab = nil - } - } - ) - .padding( - // This padding is to avoid background color overlapping with top divider. - .top, tabBarStyle == .xcode ? 1 : 0 - ) -// .offset( -// x: isAppeared || tabBarStyle == .native ? 0 : -14, -// y: 0 -// ) -// .opacity(isAppeared && onDragTabId != item.id ? 1.0 : 0.0) - .zIndex( - isActive - ? (tabBarStyle == .native ? -1 : 2) - : (isDragging ? 3 : (isPressing ? 1 : 0)) - ) - .frame( - width: ( - // Constrain the width of tab bar item for native tab style only. - tabBarStyle == .native - ? max(expectedWidth.isFinite ? expectedWidth : 0, 0) - : nil - ) - ) - .onAppear { - withAnimation( - .easeOut(duration: tabBarStyle == .native ? 0.15 : 0.20) - ) { -// isAppeared = true - } - } - .id(item.id) - .tabBarContextMenu(item: item, isTemporary: isTemporary) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorItemID.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorItemID.swift deleted file mode 100644 index 27cff36941..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorItemID.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// EditorTabID.swift -// -// -// Created by Pavel Kasila on 30.04.22. -// - -import Foundation - -/// Enum to represent item's ID to tab bar -enum EditorTabID: Codable, Identifiable, Hashable { - var id: String { - switch self { - case .codeEditor(let path): - return "codeEditor_\(path)" - case .extensionInstallation(let id): - return "extensionInstallation_\(id.uuidString)" - } - } - - /// Represents code editor tab - case codeEditor(String) - - /// Represents extension installation tab - case extensionInstallation(UUID) -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorTabRepresentable.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorTabRepresentable.swift deleted file mode 100644 index 771de2067d..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/Models/EditorTabRepresentable.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// EditorTabRepresentable.swift -// -// -// Created by Pavel Kasila on 30.04.22. -// - -import SwiftUI - -/// Protocol for data passed to EditorTabView to conform to -protocol EditorTabRepresentable { - /// Unique tab identifier - var tabID: EditorTabID { get } - /// String to be shown as tab's title - var name: String { get } - /// Image to be shown as tab's icon - var icon: Image { get } - /// Color of the tab's icon - var iconColor: Color { get } -} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift deleted file mode 100644 index dc0d3cd57b..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift +++ /dev/null @@ -1,475 +0,0 @@ -// -// EditorTabs.swift -// CodeEdit -// -// Created by Austin Condiff on 9/7/23. -// - -import SwiftUI - -// Disable the rule because the tab bar view is fairly complicated. -// It has the gesture implementation and its animations. -// I am now also disabling `file_length` rule because the dragging algorithm (with UX) is complex. -// swiftlint:disable file_length type_body_length -// - TODO: EditorTabView drop-outside event handler. - -struct EditorTabs: View { - typealias TabID = CEWorkspaceFile.ID - - @Environment(\.colorScheme) - private var colorScheme - - /// The workspace document. - @EnvironmentObject private var workspace: WorkspaceDocument - - @EnvironmentObject private var editor: Editor - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - /// The tab id of current dragging tab. - /// - /// It will be `nil` when there is no tab dragged currently. - @State private var draggingTabId: TabID? - - @State private var onDragTabId: TabID? - - /// The start location of dragging. - /// - /// When there is no tab being dragged, it will be `nil`. - /// - TODO: Check if I can use `value.startLocation` trustfully. - @State private var draggingStartLocation: CGFloat? - - /// The last location of dragging. - /// - /// This is used to determine the dragging direction. - /// - TODO: Check if I can use `value.translation` instead. - @State private var draggingLastLocation: CGFloat? - - /// Current opened tabs. - /// - /// This is a copy of `workspace.selectionState.openedTabs`. - /// I am making a copy of it because using state will hugely improve the dragging performance. - /// Updating ObservedObject too often will generate lags. - @State private var openedTabs: [TabID] = [] - - /// A map of tab width. - /// - /// All width are measured dynamically (so it can also fit the Xcode tab bar style). - /// This is used to be added on the offset of current dragging tab in order to make a smooth - /// dragging experience. - @State private var tabWidth: [TabID: CGFloat] = [:] - - /// A map of tab location (CGRect). - /// - /// All locations are measured dynamically. - /// This is used to compute when we should swap two tabs based on current cursor location. - @State private var tabLocations: [TabID: CGRect] = [:] - - /// A map of tab offsets. - /// - /// This is used to determine the tab offset of every tab (by their tab id) while dragging. - @State private var tabOffsets: [TabID: CGFloat] = [:] - - /// The expected tab width in native tab bar style. - /// - /// This is computed by the total width of tab bar. It is updated automatically. - @State private var expectedTabWidth: CGFloat = 0 - - /// This state is used to detect if the mouse is hovering over tabs. - /// If it is true, then we do not update the expected tab width immediately. - @State private var isHoveringOverTabs: Bool = false - - /// This state is used to detect if the dragging type should be changed from DragGesture to OnDrag. - /// It is basically switched when vertical displacement is exceeding the threshold. - @State private var shouldOnDrag: Bool = false - - /// Is current `onDrag` over tabs? - /// - /// When it is true, then the `onDrag` is over the tabs, then we leave the space for dragged tab. - /// When it is false, then the dragging cursor is outside the tab bar, then we should shrink the space. - /// - /// - TODO: The change of this state is overall incorrect. Should move it into workspace state. - @State private var isOnDragOverTabs: Bool = false - - /// The last location of `onDrag`. - /// - /// It can be used on reordering algorithm of `onDrag` (detecting when should we switch two tabs). - @State private var onDragLastLocation: CGPoint? - - @State private var closeButtonGestureActive: Bool = false - - @State private var scrollOffset: CGFloat = 0 - - @State private var scrollTrailingOffset: CGFloat? = 0 - - /// Update the expected tab width when corresponding UI state is updated. - /// - /// This function will be called when the number of tabs or the parent size is changed. - private func updateExpectedTabWidth(proxy: GeometryProxy) { - expectedTabWidth = max( - // Equally divided size of a native tab. - (proxy.size.width + 1) / CGFloat(editor.tabs.count) + 1, - // Min size of a native tab. - CGFloat(140) - ) - } - - // Disable the rule because this function is implementing the drag gesture and its animations. - // It is fairly complicated, so ignore the function body length limitation for now. - // swiftlint:disable function_body_length cyclomatic_complexity - private func makeTabDragGesture(id: TabID) -> some Gesture { - return DragGesture(minimumDistance: 2, coordinateSpace: .global) - .onChanged({ value in - if closeButtonGestureActive { - return - } - - if draggingTabId != id { - shouldOnDrag = false - draggingTabId = id - draggingStartLocation = value.startLocation.x - draggingLastLocation = value.location.x - } - // TODO: Enable this code snippet when re-enabling dragging-out behavior. - // I disabled (1 == 0) this behavior for now as dragging-out behavior isn't allowed. - if 1 == 0 && abs(value.location.y - value.startLocation.y) > EditorTabBarView.height { - shouldOnDrag = true - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { - shouldOnDrag = false - draggingStartLocation = nil - draggingLastLocation = nil - draggingTabId = nil - withAnimation(.easeInOut(duration: 0.25)) { - // Clean the tab offsets. - tabOffsets = [:] - } - }) - return - } - // Get the current cursor location. - let currentLocation = value.location.x - guard let startLocation = draggingStartLocation, - let currentIndex = openedTabs.firstIndex(of: id), - let currentTabWidth = tabWidth[id], - let lastLocation = draggingLastLocation - else { return } - let dragDifference = currentLocation - lastLocation - let previousIndex = currentIndex > 0 ? currentIndex - 1 : nil - let nextIndex = currentIndex < openedTabs.count - 1 ? currentIndex + 1 : nil - tabOffsets[id] = currentLocation - startLocation - // Interacting with the previous tab. - if previousIndex != nil && dragDifference < 0 { - // Wrap `previousTabIndex` because it may be `nil`. - guard let previousTabIndex = previousIndex, - let previousTabLocation = tabLocations[openedTabs[previousTabIndex]], - let previousTabWidth = tabWidth[openedTabs[previousTabIndex]] - else { return } - if currentLocation < max( - previousTabLocation.maxX - previousTabWidth * 0.1, - previousTabLocation.minX + currentTabWidth * 0.9 - ) { - let changing = previousTabWidth - 1 // One offset for overlapping divider. - draggingStartLocation! -= changing - withAnimation { - tabOffsets[id]! += changing - openedTabs.move( - fromOffsets: IndexSet(integer: previousTabIndex), - toOffset: currentIndex + 1 - ) - } - return - } - } - // Interacting with the next tab. - if nextIndex != nil && dragDifference > 0 { - // Wrap `previousTabIndex` because it may be `nil`. - guard let nextTabIndex = nextIndex, - let nextTabLocation = tabLocations[openedTabs[nextTabIndex]], - let nextTabWidth = tabWidth[openedTabs[nextTabIndex]] - else { return } - if currentLocation > min( - nextTabLocation.minX + nextTabWidth * 0.1, - nextTabLocation.maxX - currentTabWidth * 0.9 - ) { - let changing = nextTabWidth - 1 // One offset for overlapping divider. - draggingStartLocation! += changing - withAnimation { - tabOffsets[id]! -= changing - openedTabs.move( - fromOffsets: IndexSet(integer: nextTabIndex), - toOffset: currentIndex - ) - } - return - } - } - // Only update the last dragging location when there is enough offset. - if draggingLastLocation == nil || abs(value.location.x - draggingLastLocation!) >= 10 { - draggingLastLocation = value.location.x - } - }) - .onEnded({ _ in - shouldOnDrag = false - draggingStartLocation = nil - draggingLastLocation = nil - withAnimation(.easeInOut(duration: 0.25)) { - tabOffsets = [:] - } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { - draggingTabId = nil - } - // Sync the workspace's `openedTabs` 150ms after animation is finished. - // In order to avoid the lag due to the update of workspace state. - DispatchQueue.main.asyncAfter(deadline: .now() + 0.40) { - if draggingStartLocation == nil { - editor.tabs = .init(openedTabs.compactMap { id in - editor.tabs.first { $0.file.id == id } - }) - // workspace.reorderedTabs(openedTabs: openedTabs) - // TODO: Fix save state - } - } - }) - } - - private func makeTabItemGeometryReader(id: TabID) -> some View { - GeometryReader { tabItemGeoReader in - Rectangle() - .foregroundColor(.clear) - .onAppear { - tabWidth[id] = tabItemGeoReader.size.width - tabLocations[id] = tabItemGeoReader - .frame(in: .global) - } - .onChange( - of: tabItemGeoReader.frame(in: .global), - perform: { tabCGRect in - tabLocations[id] = tabCGRect - } - ) - .onChange( - of: tabItemGeoReader.size.width, - perform: { newWidth in - tabWidth[id] = newWidth - } - ) - } - } - - /// Conditionally updates the `expectedTabWidth`. - /// Called when the tab count changes or the temporary tab changes. - /// - Parameter geometryProxy: The geometry proxy to calculate the new width using. - private func updateForTabCountChange(geometryProxy: GeometryProxy) { - openedTabs = editor.tabs.map(\.file.id) - - // Only update the expected width when user is not hovering over tabs. - // This should give users a better experience on closing multiple tabs continuously. - if !isHoveringOverTabs { - withAnimation(.easeOut(duration: 0.15)) { - updateExpectedTabWidth(proxy: geometryProxy) - } - } - } - - // swiftlint:enable function_body_length cyclomatic_complexity - - var body: some View { - GeometryReader { geometryProxy in - TrackableScrollView( - .horizontal, - showIndicators: false, - contentOffset: $scrollOffset, - contentTrailingOffset: $scrollTrailingOffset - ) { - ScrollViewReader { scrollReader in - HStack( - alignment: .center, - spacing: -1 // Negative spacing for overlapping the divider. - ) { - ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in - if let item = editor.tabs.first(where: { $0.file.id == id }) { - EditorTabView( - expectedWidth: expectedTabWidth, - item: item.file, - index: index, - draggingTabId: draggingTabId, - onDragTabId: onDragTabId, - closeButtonGestureActive: $closeButtonGestureActive - ) - .transition( - .asymmetric( - insertion: .offset(x: -14).combined(with: .opacity), - removal: .opacity - ) - ) - .frame(height: EditorTabBarView.height) - .background(makeTabItemGeometryReader(id: id)) - .offset(x: tabOffsets[id] ?? 0, y: 0) - .simultaneousGesture( - makeTabDragGesture(id: id), - including: shouldOnDrag ? .subviews : .all - ) - // TODO: Detect the onDrag outside of tab bar. - // Detect the drop action of each tab. - .onDrop( - of: [.utf8PlainText], // TODO: Make a unique type for it. - delegate: EditorTabOnDropDelegate( - currentTabId: id, - openedTabs: $openedTabs, - onDragTabId: $onDragTabId, - onDragLastLocation: $onDragLastLocation, - isOnDragOverTabs: $isOnDragOverTabs, - tabWidth: $tabWidth - ) - ) - } - } - } - // This padding is to hide dividers at two ends under the accessory view divider. - .padding(.horizontal, tabBarStyle == .native ? -1 : 0) - .onAppear { - openedTabs = editor.tabs.map(\.file.id) - // On view appeared, compute the initial expected width for tabs. - updateExpectedTabWidth(proxy: geometryProxy) - // On first tab appeared, jump to the corresponding position. - scrollReader.scrollTo(editor.selectedTab) - } - .onChange(of: editor.tabs) { [tabs = editor.tabs] newValue in - if tabs.count == newValue.count { - updateForTabCountChange(geometryProxy: geometryProxy) - } else { - withAnimation( - .easeOut(duration: tabBarStyle == .native ? 0.15 : 0.20) - ) { - updateForTabCountChange(geometryProxy: geometryProxy) - } - } - Task { - try? await Task.sleep(for: .milliseconds(300)) - withAnimation { - scrollReader.scrollTo(editor.selectedTab?.file.id) - } - } - } - // When selected tab is changed, scroll to it if possible. - .onChange(of: editor.selectedTab) { newValue in - withAnimation { - scrollReader.scrollTo(newValue?.file.id) - } - } - - // When window size changes, re-compute the expected tab width. - .onChange(of: geometryProxy.size.width) { _ in - updateExpectedTabWidth(proxy: geometryProxy) - withAnimation { - scrollReader.scrollTo(editor.selectedTab?.file.id) - } - } - // When user is not hovering anymore, re-compute the expected tab width immediately. - .onHover { isHovering in - isHoveringOverTabs = isHovering - if !isHovering { - withAnimation(.easeOut(duration: 0.15)) { - updateExpectedTabWidth(proxy: geometryProxy) - } - } - } - .frame(height: EditorTabBarView.height) - } - - // To fill up the parent space of tab bar. - .frame(maxWidth: .infinity) - .background { - if tabBarStyle == .native { - EditorTabBarNativeInactiveBackground() - } - } - } - .background { - if tabBarStyle == .native { - EditorTabBarAccessoryNativeBackground(dividerAt: .none) - } - } - .overlay(alignment: .leading) { - if tabBarStyle == .xcode { - EditorTabsOverflowShadow( - width: colorScheme == .dark ? 5 : 7, - startPoint: .leading, - endPoint: .trailing - ) - .opacity(scrollOffset >= 0 ? 0 : 1) - } - } - .overlay(alignment: .trailing) { - if tabBarStyle == .xcode { - EditorTabsOverflowShadow( - width: colorScheme == .dark ? 5 : 7, - startPoint: .trailing, - endPoint: .leading - ) - .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1) - } - } - } - } - - private struct EditorTabOnDropDelegate: DropDelegate { - private let currentTabId: TabID - @Binding private var openedTabs: [TabID] - @Binding private var onDragTabId: TabID? - @Binding private var onDragLastLocation: CGPoint? - @Binding private var isOnDragOverTabs: Bool - @Binding private var tabWidth: [TabID: CGFloat] - - public init( - currentTabId: TabID, - openedTabs: Binding<[TabID]>, - onDragTabId: Binding, - onDragLastLocation: Binding, - isOnDragOverTabs: Binding, - tabWidth: Binding<[TabID: CGFloat]> - ) { - self.currentTabId = currentTabId - self._openedTabs = openedTabs - self._onDragTabId = onDragTabId - self._onDragLastLocation = onDragLastLocation - self._isOnDragOverTabs = isOnDragOverTabs - self._tabWidth = tabWidth - } - - func dropEntered(info: DropInfo) { - isOnDragOverTabs = true - guard let onDragTabId, - currentTabId != onDragTabId, - let from = openedTabs.firstIndex(of: onDragTabId), - let toIndex = openedTabs.firstIndex(of: currentTabId) - else { return } - if openedTabs[toIndex] != onDragTabId { - withAnimation { - openedTabs.move( - fromOffsets: IndexSet(integer: from), - toOffset: toIndex > from ? toIndex + 1 : toIndex - ) - } - } - } - - func dropExited(info: DropInfo) { - // Do nothing. - } - - func dropUpdated(info: DropInfo) -> DropProposal? { - return DropProposal(operation: .move) - } - - func performDrop(info: DropInfo) -> Bool { - isOnDragOverTabs = false - onDragTabId = nil - onDragLastLocation = nil - return true - } - } -} - -// swiftlint:enable file_length type_body_length diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabsOverflowShadow.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabsOverflowShadow.swift deleted file mode 100644 index 0cba8b69f2..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabsOverflowShadow.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// EditorTabsOverflowShadow.swift -// CodeEdit -// -// Created by Austin Condiff on 8/22/23. -// - -import SwiftUI - -struct EditorTabsOverflowShadow: View { - var width: CGFloat - var startPoint: UnitPoint - var endPoint: UnitPoint - - @Environment(\.colorScheme) - private var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - var body: some View { - Rectangle() - .frame(maxHeight: .infinity) - .frame(width: width) - .foregroundColor(.clear) - .background( - LinearGradient( - gradient: Gradient( - stops: [ - Gradient.Stop(color: .black.opacity(0.75), location: 0), - Gradient.Stop(color: .black.opacity(0.25), location: 0.5), - Gradient.Stop(color: .black.opacity(0), location: 1) - ] - ), - startPoint: startPoint, - endPoint: endPoint - ) - .opacity( - colorScheme == .dark - ? activeState == .inactive ? 0.25882353 : 1 - : activeState == .inactive ? 0.09803922 : 0.25882353 - ) - ) - .allowsHitTesting(false) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarAccessory.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarAccessory.swift deleted file mode 100644 index bee55982f7..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarAccessory.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// TabBarAccessory.swift -// CodeEdit -// -// Created by Lingxi Li on 4/28/22. -// - -import SwiftUI - -/// Accessory icon's view for tab bar. -struct EditorTabBarAccessoryIcon: View { - /// Unifies icon font for tab bar accessories. - static let iconFont = Font.system(size: 14, weight: .regular, design: .default) - - private let icon: Image - private let isActive: Bool - private let action: () -> Void - - init(icon: Image, isActive: Bool = false, action: @escaping () -> Void) { - self.icon = icon - self.isActive = isActive - self.action = action - } - - var body: some View { - Button( - action: action, - label: { - icon - } - ) - .buttonStyle(.icon(isActive: isActive, size: 24)) - } -} - -/// Tab bar accessory area background for native tab bar style. -struct EditorTabBarAccessoryNativeBackground: View { - enum DividerPosition { - case none - case leading - case trailing - } - - /// Divider alignment - private let dividerPosition: Self.DividerPosition - - init(dividerAt: Self.DividerPosition) { - self.dividerPosition = dividerAt - } - - private func getAlignment() -> Alignment { - switch self.dividerPosition { - case .leading: - return .leading - case .trailing: - return .trailing - default: - return .leading - } - } - - private func getPaddingDirection() -> Edge.Set { - switch self.dividerPosition { - case .leading: - return .leading - case .trailing: - return .trailing - default: - return .leading - } - } - - var body: some View { - ZStack(alignment: getAlignment()) { - EditorTabBarNativeInactiveBgColor() - .padding(getPaddingDirection(), dividerPosition == .none ? 0 : 1) - EditorTabDivider() - .opacity(dividerPosition == .none ? 0 : 1) - EditorTabBarTopDivider() - .frame(maxHeight: .infinity, alignment: .top) - } - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift deleted file mode 100644 index 7732af00f1..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift +++ /dev/null @@ -1,171 +0,0 @@ -// -// EditorTabBarContextMenu.swift -// CodeEdit -// -// Created by Khan Winter on 6/4/22. -// - -import Foundation -import SwiftUI - -extension View { - func tabBarContextMenu(item: CEWorkspaceFile, isTemporary: Bool) -> some View { - modifier(EditorTabBarContextMenu(item: item, isTemporary: isTemporary)) - } -} - -struct EditorTabBarContextMenu: ViewModifier { - init( - item: CEWorkspaceFile, - isTemporary: Bool - ) { - self.item = item - self.isTemporary = isTemporary - } - - @EnvironmentObject var workspace: WorkspaceDocument - - @EnvironmentObject var tabs: Editor - - @Environment(\.splitEditor) - var splitEditor - - private var item: CEWorkspaceFile - private var isTemporary: Bool - - // swiftlint:disable:next function_body_length - func body(content: Content) -> some View { - content.contextMenu(menuItems: { - Group { - Button("Close Tab") { - withAnimation { - tabs.closeTab(file: item) - } - } - .keyboardShortcut("w", modifiers: [.command]) - - Button("Close Other Tabs") { - withAnimation { - tabs.tabs.map({ $0.file }).forEach { file in - if file != item { - tabs.closeTab(file: file) - } - } - } - } - Button("Close Tabs to the Right") { - withAnimation { - if let index = tabs.tabs.firstIndex(where: { $0.file == item }) { - tabs.tabs[index...].forEach { - tabs.closeTab(file: $0.file) - } - } - } - } - // Disable this option when current tab is the last one. - .disabled(tabs.tabs.last?.file == item) - - Button("Close All") { - withAnimation { - tabs.tabs.forEach { - tabs.closeTab(file: $0.file) - } - } - } - - if isTemporary { - Button("Keep Open") { - tabs.temporaryTab = nil - } - } - } - - Divider() - - Group { - Button("Copy Path") { - copyPath(item: item) - } - - Button("Copy Relative Path") { - copyRelativePath(item: item) - } - } - - Divider() - - Group { - Button("Show in Finder") { - item.showInFinder() - } - - Button("Reveal in Project Navigator") { - workspace.listenerModel.highlightedFileItem = item - } - - Button("Open in New Window") { - - } - .disabled(true) - } - - Divider() - - Button("Split Up") { - moveToNewSplit(.top) - } - Button("Split Down") { - moveToNewSplit(.bottom) - } - Button("Split Left") { - moveToNewSplit(.leading) - } - Button("Split Right") { - moveToNewSplit(.trailing) - } - }) - } - - // MARK: - Actions - - /// Copies the absolute path of the given `FileItem` - /// - Parameter item: The `FileItem` to use. - private func copyPath(item: CEWorkspaceFile) { - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(item.url.standardizedFileURL.path, forType: .string) - } - - func moveToNewSplit(_ edge: Edge) { - let newEditor = Editor(files: [item]) - splitEditor(edge, newEditor) - tabs.closeTab(file: item) - workspace.editorManager.activeEditor = newEditor - } - - /// Copies the relative path from the workspace folder to the given file item to the pasteboard. - /// - Parameter item: The `FileItem` to use. - private func copyRelativePath(item: CEWorkspaceFile) { - guard let rootPath = workspace.workspaceFileManager?.folderUrl else { - return - } - // Calculate the relative path - var rootComponents = rootPath.standardizedFileURL.pathComponents - var destinationComponents = item.url.standardizedFileURL.pathComponents - - // Remove any same path components - while !rootComponents.isEmpty && !destinationComponents.isEmpty - && rootComponents.first == destinationComponents.first { - rootComponents.remove(at: 0) - destinationComponents.remove(at: 0) - } - - // Make a "../" for each remaining component in the root URL - var relativePath: String = String(repeating: "../", count: rootComponents.count) - // Add the remaining components for the destination url. - relativePath += destinationComponents.joined(separator: "/") - - // Copy it to the clipboard - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(relativePath, forType: .string) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarDivider.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarDivider.swift deleted file mode 100644 index 1233371125..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarDivider.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// EditorTabBarDivider.swift -// CodeEdit -// -// Created by Lingxi Li on 4/22/22. -// - -import SwiftUI - -/// The vertical divider between tab bar items. -struct EditorTabDivider: View { - @Environment(\.colorScheme) - var colorScheme - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - let width: CGFloat = 1 - - var body: some View { - Rectangle() - .frame(width: width) - .padding(.vertical, tabBarStyle == .xcode ? 8 : 0) - .foregroundColor( - tabBarStyle == .xcode - ? Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(0.12) - : Color(nsColor: colorScheme == .dark ? .controlColor : .black) - .opacity(colorScheme == .dark ? 0.40 : 0.13) - ) - } -} - -/// The top border for tab bar (between tab bar and titlebar). -struct EditorTabBarTopDivider: View { - @Environment(\.colorScheme) - var colorScheme - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - var body: some View { - ZStack(alignment: .top) { - if tabBarStyle == .native { - // Color background overlay in native style. - Color(nsColor: .black) - .opacity(colorScheme == .dark ? 0.80 : 0.02) - .frame(height: tabBarStyle == .xcode ? 1.0 : 0.8) - // Shadow of top divider in native style. - EditorTabBarNativeShadow() - } - } - } -} - -/// The divider shadow for native tab bar style. -/// -/// This is generally used in the top divider of tab bar when tab bar style is set to `native`. -struct EditorTabBarNativeShadow: View { - let shadowColor = Color(nsColor: .shadowColor) - - var body: some View { - LinearGradient( - colors: [ - shadowColor.opacity(0.18), - shadowColor.opacity(0.06), - shadowColor.opacity(0.03), - shadowColor.opacity(0.01), - shadowColor.opacity(0) - ], - startPoint: .top, - endPoint: .bottom - ) - .frame(height: 3.8) - .opacity(0.70) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift deleted file mode 100644 index d330da3e73..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// EditorTabBarLeadingAccessories.swift -// CodeEdit -// -// Created by Austin Condiff on 9/7/23. -// - -import SwiftUI - -struct EditorTabBarLeadingAccessories: View { - @Environment(\.controlActiveState) - private var activeState - - @EnvironmentObject private var editorManager: EditorManager - - @EnvironmentObject private var editor: Editor - - @State private var otherEditor: Editor? - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - var body: some View { - HStack(spacing: 0) { - if let otherEditor { - EditorTabBarAccessoryIcon( - icon: .init(systemName: "multiply"), - action: { [weak editor] in - guard let editor else { return } - editorManager.closeEditor(editor) - } - ) - .help("Close this Editor") - .disabled(editorManager.isFocusingActiveEditor) - .opacity(editorManager.isFocusingActiveEditor ? 0.5 : 1) - - EditorTabBarAccessoryIcon( - icon: .init( - systemName: editorManager.isFocusingActiveEditor - ? "arrow.down.forward.and.arrow.up.backward" - : "arrow.up.left.and.arrow.down.right" - ), - isActive: editorManager.isFocusingActiveEditor, - action: { - editorManager.toggleFocusingEditor(from: editor) - } - ) - .help( - editorManager.isFocusingActiveEditor - ? "Unfocus this Editor" - : "Focus this Editor" - ) - - Divider() - .frame(height: 10) - .padding(.horizontal, 4) - } - - Group { - Menu { - ForEach( - Array(editor.history.dropFirst(editor.historyOffset+1).enumerated()), - id: \.offset - ) { index, tab in - Button { - editorManager.activeEditor = editor - editor.historyOffset += index + 1 - } label: { - HStack { - tab.file.icon - Text(tab.file.name) - } - } - } - } label: { - Image(systemName: "chevron.left") - .opacity(editor.historyOffset == editor.history.count-1 || editor.history.isEmpty ? 0.5 : 1) - .frame(height: EditorTabBarView.height - 2) - .padding(.horizontal, 4) - } primaryAction: { - editorManager.activeEditor = editor - editor.goBackInHistory() - } - .disabled(editor.historyOffset == editor.history.count-1 || editor.history.isEmpty) - .help("Navigate back") - - Menu { - ForEach( - Array(editor.history.prefix(editor.historyOffset).reversed().enumerated()), - id: \.offset - ) { index, tab in - Button { - editorManager.activeEditor = editor - editor.historyOffset -= index + 1 - } label: { - HStack { - tab.file.icon - Text(tab.file.name) - } - } - } - } label: { - Image(systemName: "chevron.right") - .opacity(editor.historyOffset == 0 ? 0.5 : 1) - .frame(height: EditorTabBarView.height - 2) - .padding(.horizontal, 4) - } primaryAction: { - editorManager.activeEditor = editor - editor.goForwardInHistory() - } - .disabled(editor.historyOffset == 0) - .help("Navigate forward") - } - .buttonStyle(.icon) - .controlSize(.small) - .font(EditorTabBarAccessoryIcon.iconFont) - } - .foregroundColor(.secondary) - .buttonStyle(.plain) - .padding(.horizontal, 5) - .opacity(activeState != .inactive ? 1.0 : 0.5) - .frame(maxHeight: .infinity) // Fill out vertical spaces. - .background { - if tabBarStyle == .native { - EditorTabBarAccessoryNativeBackground(dividerAt: .trailing) - } - } - .onAppear { - otherEditor = editorManager.editorLayout.findSomeEditor(except: editor) - } - .onReceive(editorManager.objectWillChange) { _ in - otherEditor = editorManager.editorLayout.findSomeEditor(except: editor) - } - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarNative.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarNative.swift deleted file mode 100644 index a282958fae..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarNative.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// TabBarNative.swift -// CodeEdit -// -// This file contains some support views to make native tab bar style come true. -// -// Created by Lingxi Li on 4/25/22. -// - -import SwiftUI - -/// Native style background view (including color and shadow divider) for tab bar. -struct EditorTabBarNativeInactiveBackground: View { - var body: some View { - ZStack(alignment: .top) { - EditorTabBarNativeInactiveBgColor() - // When tab bar style is `native`, we put the top divider beneath tabs. - EditorTabBarTopDivider() - } - } -} - -/// Native style background color for tab bar. -struct EditorTabBarNativeInactiveBgColor: View { - @Environment(\.colorScheme) - private var colorScheme - - var body: some View { - Color(nsColor: .black) - .opacity(colorScheme == .dark ? 0.45 : 0.05) - } -} - -/// Native style background material for active tab bar in fullscreen. -/// This view is only used in fullscreen (to match the material of toolbar). -struct EditorTabBarNativeActiveMaterial: View { - @Environment(\.colorScheme) - private var colorScheme - - var body: some View { - EffectView( - NSVisualEffectView.Material.headerView, - blendingMode: NSVisualEffectView.BlendingMode.withinWindow - ) - .background( - // This layer of background is for matching the native toolbar background - // in dark mode and in fullscreen. - // There is no exactly matched material available. - // If you have a better solution, feel free to replace!! - Color(nsColor: colorScheme == .dark ? .selectedContentBackgroundColor : .clear) - .opacity(0.003) - ) - .background( - Color(nsColor: .windowBackgroundColor) - ) - } -} - -/// Native style background material for tab bar. -struct EditorTabBarNativeMaterial: View { - var body: some View { - EffectView( - NSVisualEffectView.Material.titlebar, - blendingMode: NSVisualEffectView.BlendingMode.withinWindow - ) - .background(Color(nsColor: .windowBackgroundColor)) - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift deleted file mode 100644 index d938c89859..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// EditorTabBarTrailingAccessories.swift -// CodeEdit -// -// Created by Austin Condiff on 9/7/23. -// - -import SwiftUI - -struct EditorTabBarTrailingAccessories: View { - @Environment(\.splitEditor) - var splitEditor - - @Environment(\.modifierKeys) - var modifierKeys - - @Environment(\.controlActiveState) - private var activeState - - @EnvironmentObject private var editorManager: EditorManager - - @EnvironmentObject private var editor: Editor - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - var body: some View { - HStack(spacing: 0) { - splitviewButton - } - .padding(.horizontal, 7) - .opacity(activeState != .inactive ? 1.0 : 0.5) - .frame(maxHeight: .infinity) // Fill out vertical spaces. - .background { - if tabBarStyle == .native { - EditorTabBarAccessoryNativeBackground(dividerAt: .leading) - } - } - } - - var splitviewButton: some View { - Group { - switch (editor.parent?.axis, modifierKeys.contains(.option)) { - case (.horizontal, true), (.vertical, false): - Button { - split(edge: .bottom) - } label: { - Image(symbol: "square.split.horizontal.plus") - } - .help("Split Vertically") - - case (.vertical, true), (.horizontal, false): - Button { - split(edge: .trailing) - } label: { - Image(symbol: "square.split.vertical.plus") - } - .help("Split Horizontally") - - default: - EmptyView() - } - } - .buttonStyle(.icon) - .disabled(editorManager.isFocusingActiveEditor) - .opacity(editorManager.isFocusingActiveEditor ? 0.5 : 1) - } - - func split(edge: Edge) { - let newEditor: Editor - if let tab = editor.selectedTab { - newEditor = .init(files: [tab], temporaryTab: tab) - } else { - newEditor = .init() - } - splitEditor(edge, newEditor) - editorManager.updateCachedFlattenedEditors = true - editorManager.activeEditor = newEditor - } -} - -struct TabBarTrailingAccessories_Previews: PreviewProvider { - static var previews: some View { - EditorTabBarTrailingAccessories() - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift deleted file mode 100644 index a7ab5a18e3..0000000000 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// EditorTabBarView.swift -// CodeEdit -// -// Created by Lukas Pistrol and Lingxi Li on 17.03.22. -// - -import SwiftUI - -struct EditorTabBarView: View { - /// The height of tab bar. - /// I am not making it a private variable because it may need to be used in outside views. - static let height = 28.0 - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - var body: some View { - HStack(alignment: .center, spacing: 0) { - EditorTabBarLeadingAccessories() - EditorTabs() - EditorTabBarTrailingAccessories() - } - .frame(height: EditorTabBarView.height) - .overlay(alignment: .top) { - // When tab bar style is `xcode`, we put the top divider as an overlay. - if tabBarStyle == .xcode { - EditorTabBarTopDivider() - } - } - .background { - if tabBarStyle == .native { - EditorTabBarNativeMaterial() - .edgesIgnoringSafeArea(.top) - } - } - .padding(.leading, -1) - } -} diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift deleted file mode 100644 index 408bf476a7..0000000000 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// EditorLayoutView.swift -// CodeEdit -// -// Created by Wouter Hennen on 20/02/2023. -// - -import SwiftUI - -struct EditorLayoutView: View { - var layout: EditorLayout - - @FocusState.Binding var focus: Editor? - - @Environment(\.window) - private var window - - @Environment(\.isAtEdge) - private var isAtEdge - - var toolbarHeight: CGFloat { - window.contentView?.safeAreaInsets.top ?? .zero - } - - var body: some View { - VStack { - switch layout { - case .one(let detailEditor): - EditorView(editor: detailEditor, focus: $focus) - .transformEnvironment(\.edgeInsets) { insets in - switch isAtEdge { - case .all: - insets.top += toolbarHeight - insets.bottom += StatusBarView.height + 5 - case .top: - insets.top += toolbarHeight - case .bottom: - insets.bottom += StatusBarView.height + 5 - default: - return - } - } - case .vertical(let data), .horizontal(let data): - SubEditorLayoutView(data: data, focus: $focus) - } - } - } - - struct SubEditorLayoutView: View { - @ObservedObject var data: SplitViewData - - @FocusState.Binding var focus: Editor? - - var body: some View { - SplitView(axis: data.axis) { - splitView - } - .edgesIgnoringSafeArea([.top, .bottom]) - } - - var splitView: some View { - ForEach(Array(data.editorLayouts.enumerated()), id: \.offset) { index, item in - EditorLayoutView(layout: item, focus: $focus) - .transformEnvironment(\.isAtEdge) { belowToolbar in - calcIsAtEdge(current: &belowToolbar, index: index) - } - .environment(\.splitEditor) { [weak data] edge, newEditor in - data?.split(edge, at: index, new: newEditor) - } - } - } - - func calcIsAtEdge(current: inout VerticalEdge.Set, index: Int) { - if case .vertical = data.axis { - guard data.editorLayouts.count != 1 else { return } - if index == data.editorLayouts.count - 1 { - current.remove(.top) - } else if index == 0 { - current.remove(.bottom) - } else { - current = [] - } - } - } - } -} - -private struct BelowToolbarEnvironmentKey: EnvironmentKey { - static var defaultValue: VerticalEdge.Set = .all -} - -extension EnvironmentValues { - fileprivate var isAtEdge: BelowToolbarEnvironmentKey.Value { - get { self[BelowToolbarEnvironmentKey.self] } - set { self[BelowToolbarEnvironmentKey.self] = newValue } - } -} diff --git a/CodeEdit/Features/Editor/Views/EditorView.swift b/CodeEdit/Features/Editor/Views/EditorView.swift deleted file mode 100644 index 6ca74ae35c..0000000000 --- a/CodeEdit/Features/Editor/Views/EditorView.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// EditorView.swift -// CodeEdit -// -// Created by Wouter Hennen on 16/02/2023. -// - -import SwiftUI - -struct EditorView: View { - @AppSettings(\.general.showEditorPathBar) - var showEditorPathBar - - @AppSettings(\.navigation.navigationStyle) - var navigationStyle - - @AppSettings(\.general.dimEditorsWithoutFocus) - var dimEditorsWithoutFocus - - @ObservedObject var editor: Editor - - @FocusState.Binding var focus: Editor? - - @EnvironmentObject private var editorManager: EditorManager - - var body: some View { - var shouldShowTabBar: Bool { - return navigationStyle == .openInTabs - || editorManager.flattenedEditors.contains { editor in - (editor.temporaryTab == nil && !editor.tabs.isEmpty) - || (editor.temporaryTab != nil && editor.tabs.count > 1) - } - } - - var editorInsetAmount: Double { - let tabBarHeight = shouldShowTabBar ? (EditorTabBarView.height + 1) : 0 - let pathBarHeight = showEditorPathBar ? (EditorPathBarView.height + 1) : 0 - return tabBarHeight + pathBarHeight - } - - VStack { - if let selected = editor.selectedTab { - WorkspaceCodeFileView( - file: selected.file, - textViewCoordinators: [selected.rangeTranslator].compactMap({ $0 }) - ) - .focusedObject(editor) - .transformEnvironment(\.edgeInsets) { insets in - insets.top += editorInsetAmount - } - .opacity(dimEditorsWithoutFocus && editor != editorManager.activeEditor ? 0.5 : 1) - } else { - CEContentUnavailableView("No Editor") - .padding(.top, editorInsetAmount) - .onTapGesture { - editorManager.activeEditor = editor - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .ignoresSafeArea(.all) - .safeAreaInset(edge: .top, spacing: 0) { - VStack(spacing: 0) { - if shouldShowTabBar { - EditorTabBarView() - .id("TabBarView" + editor.id.uuidString) - .environmentObject(editor) - Divider() - } - if showEditorPathBar { - EditorPathBarView( - file: editor.selectedTab?.file, - shouldShowTabBar: shouldShowTabBar - ) { [weak editor] newFile in - if let file = editor?.selectedTab, let index = editor?.tabs.firstIndex(of: file) { - editor?.openTab(file: newFile, at: index) - } - } - .environmentObject(editor) - .padding(.top, shouldShowTabBar ? -1 : 0) - Divider() - } - } - .environment(\.isActiveEditor, editor == editorManager.activeEditor) - .background(EffectView(.headerView)) - } - .focused($focus, equals: editor) - .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in - if navigationStyle == .openInTabs { - editor.temporaryTab = nil - } - } - .onChange(of: navigationStyle) { newValue in - if newValue == .openInPlace && editor.tabs.count == 1 { - editor.temporaryTab = editor.tabs[0] - } - } - } -} diff --git a/CodeEdit/Features/Extensions/Commands+ForEach.swift b/CodeEdit/Features/Extensions/Commands+ForEach.swift deleted file mode 100644 index a579378bfc..0000000000 --- a/CodeEdit/Features/Extensions/Commands+ForEach.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// Commands+ForEach.swift -// CodeEdit -// -// Created by Wouter Hennen on 11/03/2023. -// - -import SwiftUI - -// A custom ForEach struct is created instead of using the SwiftUI.ForEach one, -// as we can't create a new initializer due to a swift limitation. -// Instead, this CommandsForEach struct is used, which functions equally. - -/// A structure that builds commandmenus on demand from an underlying collection of data. -/// Maximum 10 items are supported. -struct CommandsForEach: Commands where Data.Index == Int { - - var data: Data - - var content: (Data.Element) -> Content - - init(_ data: Data, @CommandsBuilder content: @escaping (Data.Element) -> Content) { - self.data = data - self.content = content - } - - var body: some Commands { - switch data.count { - case 0: - EmptyCommands() - case 1: - CommandsBuilder.buildBlock(content(data[0])) - case 2: - CommandsBuilder.buildBlock(content(data[0]), content(data[1])) - case 3: - CommandsBuilder.buildBlock(content(data[0]), content(data[1]), content(data[2])) - case 4: - CommandsBuilder.buildBlock(content(data[0]), content(data[1]), content(data[2]), content(data[3])) - case 5: - CommandsBuilder.buildBlock( - content(data[0]), - content(data[1]), - content(data[2]), - content(data[3]), - content(data[4]) - ) - case 6: - CommandsBuilder.buildBlock( - content(data[0]), - content(data[1]), - content(data[2]), - content(data[3]), - content(data[4]), - content(data[5]) - ) - case 7: - CommandsBuilder.buildBlock( - content(data[0]), - content(data[1]), - content(data[2]), - content(data[3]), - content(data[4]), - content(data[5]), - content(data[6]) - ) - case 8: - CommandsBuilder.buildBlock( - content(data[0]), - content(data[1]), - content(data[2]), - content(data[3]), - content(data[4]), - content(data[5]), - content(data[6]), - content(data[7]) - ) - case 9: - CommandsBuilder.buildBlock( - content(data[0]), - content(data[1]), - content(data[2]), - content(data[3]), - content(data[4]), - content(data[5]), - content(data[6]), - content(data[7]), - content(data[8]) - ) - default: - CommandsBuilder.buildBlock( - content(data[0]), - content(data[1]), - content(data[2]), - content(data[3]), - content(data[4]), - content(data[5]), - content(data[6]), - content(data[7]), - content(data[8]), - content(data[9]) - ) - } - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionActivatorView.swift b/CodeEdit/Features/Extensions/ExtensionActivatorView.swift deleted file mode 100644 index f1495442cc..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionActivatorView.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// ExtensionActivatorView.swift -// CodeEdit -// -// Created by Wouter Hennen on 30/12/2022. -// - -import SwiftUI -import ExtensionKit - -struct ExtensionActivatorView: NSViewControllerRepresentable { - func makeNSViewController(context: Context) -> EXAppExtensionBrowserViewController { - EXAppExtensionBrowserViewController() - } - - func updateNSViewController(_ nsViewController: EXAppExtensionBrowserViewController, context: Context) { - - } - - func makeCoordinator() { - - } -} - -struct ExtensionActivatorView_Previews: PreviewProvider { - static var previews: some View { - ExtensionActivatorView() - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionDetailView.swift b/CodeEdit/Features/Extensions/ExtensionDetailView.swift deleted file mode 100644 index 8165596fc0..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionDetailView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ExtensionDetailView.swift -// CodeEdit -// -// Created by Wouter Hennen on 01/01/2023. -// - -import SwiftUI - -struct ExtensionDetailView: View { - var ext: ExtensionInfo - - var body: some View { - VStack(alignment: .leading) { - HStack { - if let icon = ext.icon { - Image(nsImage: icon) - .resizable() - .frame(width: 150, height: 150) - } - - Form { - Section("Features") { - ForEach(ext.availableFeatures, id: \.self) { feature in - Text(feature.description) - } - } - } - .formStyle(.grouped) - } - - Text("Extension Settings") - .font(.title3) - .fontWeight(.semibold) - .padding(.leading) - ExtensionSceneView(with: ext.endpoint, sceneID: "Settings") - .padding(.top, -5) - .ceEnvironment(\.complexValue, ["HAllo"]) - } - .navigationSubtitle(ext.name) - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionDiscovery.swift b/CodeEdit/Features/Extensions/ExtensionDiscovery.swift deleted file mode 100644 index 2eb309528b..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionDiscovery.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// ExtensionDiscovery.swift -// CodeEdit -// -// Created by Wouter Hennen on 31/12/2022. -// - -import Foundation -import ExtensionFoundation -import CollectionConcurrencyKit -import GRDB - -/// Discovery of available extension endpoints. -final class ExtensionDiscovery: ObservableObject { - /// Shared instance of this class. - static var shared = ExtensionDiscovery() - - /// Endpoint used by extensions. - static var endPointIdentifier = "codeedit.extension" - - private static var dbURL = URL.libraryDirectory - .appending(path: "Preferences/com.apple.LaunchServices/com.apple.LaunchServices.SettingsStore.sql") - - /// Publishes a list of extension endpoints approved by the user. - /// These endpoints can be used to create new extension processes with XPC. - @Published var extensions: [ExtensionInfo] = [] - - // Init is private as only 1 instance of this class may (needs to) exist. - private init() { - // Two separate tasks need to be used, as the awaits never finish. - Task { - await discover() - } - - Task { - await availabilityOverview() - } - } - - /// Discover all the extensions approved by the user. Updates `extensions` when an extension gets enabled/disabled. - /// Warning: This function will continue to run and won't return. Therefore, it should be ran in a separate `Task`. - private func discover() async { - print("Change in active extensions, reconnecting...") - do { - let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier) - - for await endpoints in sequence { - await updateExtensions(endpoints: endpoints, shouldRestartExisting: true) - } - } catch { - print("Error while searching for extensions: \(error.localizedDescription)") - } - } - - private func updateExtensions(endpoints: [AppExtensionIdentity], shouldRestartExisting: Bool = false) async { - let extensions = await endpoints.concurrentCompactMap { - try? await ExtensionInfo(endpoint: $0) - } - - await MainActor.run { - self.extensions = extensions - } - - if shouldRestartExisting { - self.extensions.filter(\.isDebug) - .forEach { - print("Restarting \($0.name)...") - $0.restart() - } - } - } - - /// Observes extensions available on the system, and reports if extensions are disabled. - /// These extensions must be enabled by the user first, before they can be discovered by `discover`. - /// Warning: This function will continue to run and won't return. Therefore, it should be ran in a separate `Task`. - private func availabilityOverview() async { - for await availability in AppExtensionIdentity.availabilityUpdates { - print(availability) - do { - if availability.disabledCount > 0 { - print("Found \(availability.disabledCount) disabled extensions, trying to activate...") - try await activateDisabledExtensions() - } - - if availability.unapprovedCount > 0 { - print("Found \(availability.disabledCount) unapproved extensions, trying to activate...") - - let identifiers = [("com.tweety.TestCodeEdit.AutoActivatedExtension", "2MMGJGVTB4")] - try await activateUnapprovedExtensions(with: identifiers) - } - - let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier) - - let extensions = await sequence.first { _ in true } - - guard let extensions else { return } - await updateExtensions(endpoints: extensions) - } catch { - print("Could not auto-activate extensions.") - } - } - } - - struct SettingsStoreRecord: Codable, TableRecord, FetchableRecord, PersistableRecord { - var identifier: String - var timestamp: String - var userElection: Int - - static var databaseTableName: String = "Election" - } - - private func activateDisabledExtensions() async throws { - let dbQueue = try DatabaseQueue(path: Self.dbURL.path()) - - return try await dbQueue.write { - try SettingsStoreRecord - .filter(Column("identifier").like("%\(Self.endPointIdentifier)%")) - .filter(Column("userElection") == 2) - .updateAll($0, Column("userElection") -= 1) - } - } - - private func activateUnapprovedExtensions(with identifiers: [(bundleID: String, devID: String)]) async throws { - let dbQueue = try DatabaseQueue(path: Self.dbURL.path()) - - return try await dbQueue.write { table in - try identifiers.map { identifier in - SettingsStoreRecord( - // swiftlint:disable:next line_length - identifier: "\(Bundle.main.bundleIdentifier!)::\(Self.endPointIdentifier):\(identifier.bundleID):\(identifier.devID)", - timestamp: String(Date.now.description.dropLast(6)), - userElection: 1 - ) - }.forEach { - try $0.save(table) - } - } - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionInfo.swift b/CodeEdit/Features/Extensions/ExtensionInfo.swift deleted file mode 100644 index 193ade0bbd..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionInfo.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// ExtensionInfo.swift -// CodeEdit -// -// Created by Wouter Hennen on 31/12/2022. -// - -import AppKit -import CodeEditKit -import ExtensionFoundation - -struct ExtensionInfo: Identifiable, Hashable { - - let endpoint: AppExtensionIdentity - - let availableFeatures: [ExtensionKind] - - let isDebug: Bool - - var bundleURL: URL - - var bundle: Bundle? - - var pid: Int32 - - var id: String { - endpoint.bundleIdentifier - } - - var name: String { - endpoint.localizedName - } - - var version: String? { - bundle?.infoDictionary?["CFBundleShortVersionString"] as? String - } - - func restart() { - kill(pid, SIGKILL) - } - - init(endpoint: AppExtensionIdentity) async throws { - self.endpoint = endpoint - - let process = try await AppExtensionProcess(configuration: .init(appExtensionIdentity: endpoint)) - - let connection = try process.makeXPCConnection() - connection.remoteObjectInterface = .init(with: XPCWrappable.self) - connection.resume() - - defer { - connection.invalidate() - } - - self.pid = try await ExtensionInfo.getProcessID(connection) - self.isDebug = try await ExtensionInfo.getDebugState(connection) - self.availableFeatures = try await ExtensionInfo.getAvailableFeatures(connection) - self.bundleURL = try await ExtensionInfo.getBundleURL(connection) - self.bundle = Bundle(url: bundleURL) - } -} - -// Functions to get basic information about extension -extension ExtensionInfo { - private static func getProcessID(_ connection: NSXPCConnection) async throws -> pid_t { - try await connection.withContinuation { (service: XPCWrappable, continuation) in - service.getExtensionProcessIdentifier { - continuation.resumingHandler($0, .none) - } - } - } - - private static func getDebugState(_ connection: NSXPCConnection) async throws -> Bool { - try await connection.withContinuation { (service: XPCWrappable, continuation) in - service.isDebug { - continuation.resumingHandler($0, .none) - } - } - } - - private static func getAvailableFeatures(_ connection: NSXPCConnection) async throws -> [ExtensionKind] { - let encodedAvailableFeatures = try await connection.withContinuation { (service: XPCWrappable, continuation) in - service.getExtensionKinds(reply: continuation.resumingHandler) - } - return try JSONDecoder().decode([ExtensionKind].self, from: encodedAvailableFeatures) - } - - private static func getBundleURL(_ connection: NSXPCConnection) async throws -> URL { - let bundleURLEncoded = try await connection.withContinuation { (service: XPCWrappable, continuation) in - service.getExtensionURL(reply: continuation.resumingHandler) - } - - return try JSONDecoder().decode(URL.self, from: bundleURLEncoded) - } -} - -extension ExtensionInfo { - /// Bundle identifier of parent app - var parentBundleIdentifier: String { - endpoint.bundleIdentifier.split(separator: ".").dropLast().joined(separator: ".") - } - - var lastPathOfBundleIdentifier: String { - String(endpoint.bundleIdentifier.split(separator: ".").last!) - } - - /// Icon of appex folder - public var icon: NSImage? { - // TODO: Use icon of extension instead of parent app - // A way to get the path of an .appex file should be used. - // Unfortunately, NSWorkspace.shared.urlForApplication only seems to work for .app - let path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: parentBundleIdentifier) - guard let path else { return nil } - - return NSWorkspace.shared.icon(forFile: path.path) - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionManagerWindow.swift b/CodeEdit/Features/Extensions/ExtensionManagerWindow.swift deleted file mode 100644 index de34f9187a..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionManagerWindow.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// ExtensionManagerWindow.swift -// CodeEdit -// -// Created by Wouter Hennen on 24/03/2023. -// - -import SwiftUI - -struct ExtensionManagerWindow: Scene { - @ObservedObject var manager = ExtensionManager.shared - - @State var selection = Set() - - var body: some Scene { - Window("Extensions", id: SceneID.extensions.rawValue) { - NavigationSplitView { - ExtensionsListView(selection: $selection) - } detail: { - switch selection.count { - case 0: - Text("Select an extension") - case 1: - ExtensionDetailView(ext: selection.first!) - default: - Text("\(selection.count) selected") - } - } - .environmentObject(manager) - .focusedObject(manager) - } - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionSceneView.swift b/CodeEdit/Features/Extensions/ExtensionSceneView.swift deleted file mode 100644 index 83e864bfff..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionSceneView.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// ExtensionSceneView.swift -// CodeEdit -// -// Created by Wouter Hennen on 31/12/2022. -// - -import SwiftUI -import CodeEditKit -import ExtensionKit -import ExtensionFoundation - -struct ExtensionSceneView: NSViewControllerRepresentable { - - @Environment(\.openWindow) - var openWindow - - let appExtension: AppExtensionIdentity - let sceneID: String - - init(with appExtension: AppExtensionIdentity, sceneID: String) { - self.appExtension = appExtension - self.sceneID = sceneID - } - - func makeNSViewController(context: Context) -> EXHostViewController { - let controller = EXHostViewController() - controller.delegate = context.coordinator - controller.configuration = .some(.init(appExtension: appExtension, sceneID: sceneID)) - context.coordinator.updateEnvironment(context.environment._ceEnvironment) - return controller - } - - func updateNSViewController(_ nsViewController: EXHostViewController, context: Context) { - nsViewController.configuration = .init(appExtension: appExtension, sceneID: sceneID) - context.coordinator.updateEnvironment(context.environment._ceEnvironment) - } - - func makeCoordinator() -> Coordinator { - Coordinator { id in - print(id) - DispatchQueue.main.async { - openWindow(id: id) - } - } - } - - class Coordinator: NSObject, EXHostViewControllerDelegate, EnvironmentPublisherObjc { - var isOnline: Bool = false - var toPublish: Data? - var openWindow: (String) -> Void - - init(openWindow: @escaping (String) -> Void) { - self.openWindow = openWindow - } - - var connection: NSXPCConnection? - - func publishEnvironment(data: Data) { - @Decoded var data = data - guard let $data else { return } - switch $data { - case .openWindow(let id): - openWindow(id) - } - } - - func updateEnvironment(@Encoded _ value: _CEEnvironment) { - guard let $value else { return } - - guard isOnline else { - toPublish = $value - return - } - - Task { - do { - try await connection!.withService { (service: EnvironmentPublisherObjc) in - service.publishEnvironment(data: $value) - } - } catch { - print(error) - } - } - } - - func hostViewControllerWillDeactivate(_ viewController: EXHostViewController, error: Error?) { - isOnline = false - print("Host will deactivate", error as Any) - } - - func hostViewControllerDidActivate(_ viewController: EXHostViewController) { - isOnline = true - do { - self.connection = try viewController.makeXPCConnection() - connection?.exportedInterface = .init(with: EnvironmentPublisherObjc.self) - connection?.exportedObject = self - connection?.remoteObjectInterface = .init(with: EnvironmentPublisherObjc.self) - connection?.resume() - if let toPublish { - Task { - try? await connection?.withService { (service: EnvironmentPublisherObjc) in - service.publishEnvironment(data: toPublish) - } - } - } - } catch { - print("Unable to create connection: \(String(describing: error))") - } - } - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionsListView.swift b/CodeEdit/Features/Extensions/ExtensionsListView.swift deleted file mode 100644 index df42ebc236..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionsListView.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// ExtensionsListView.swift -// CodeEdit -// -// Created by Wouter Hennen on 24/03/2023. -// - -import SwiftUI - -struct ExtensionsListView: View { - @EnvironmentObject var manager: ExtensionManager - @Binding var selection: Set - @State var showActivatorView = false - - var body: some View { - List(manager.extensions, id: \.self, selection: $selection) { ext in - HStack { - if let icon = ext.icon { - Image(nsImage: icon) - .resizable() - .aspectRatio(1, contentMode: .fit) - } - - VStack(alignment: .leading) { - Text(ext.name) - if let version = ext.version { - Text(version) - .font(.footnote) - .foregroundColor(.secondary) - } - } - } - .frame(height: 40) - } - - .toolbar { - Toggle(isOn: $showActivatorView) { - Image(systemName: "puzzlepiece.extension") - } - .toggleStyle(.button) - .popover(isPresented: $showActivatorView) { - ExtensionActivatorView() - .frame(width: 400, height: 300) - } - } - .onChange(of: manager.extensions) { [oldValue = manager.extensions] newValue in - // Select the first one if previously the extension list was empty. - if oldValue.isEmpty, let first = newValue.first { - selection = [first] - } - } - } -} diff --git a/CodeEdit/Features/Extensions/ExtensionsManager.swift b/CodeEdit/Features/Extensions/ExtensionsManager.swift deleted file mode 100644 index d539e1ac6d..0000000000 --- a/CodeEdit/Features/Extensions/ExtensionsManager.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ExtensionManager.swift -// CodeEdit -// -// Created by Wouter Hennen on 30/12/2022. -// - -import Foundation -import SwiftUI -import ExtensionFoundation -import CodeEditKit -import ConcurrencyPlus - -final class ExtensionManager: ObservableObject { - - static var shared = ExtensionManager() - - @Published var extensions: [ExtensionInfo] = [] - - init() { - ExtensionDiscovery.shared.$extensions.assign(to: &$extensions) - } - -} diff --git a/CodeEdit/Features/Extensions/codeedit.extension.appextensionpoint b/CodeEdit/Features/Extensions/codeedit.extension.appextensionpoint deleted file mode 100644 index 4443186940..0000000000 --- a/CodeEdit/Features/Extensions/codeedit.extension.appextensionpoint +++ /dev/null @@ -1,11 +0,0 @@ - - - - - codeedit.extension - - EXPresentsUserInterface - - - - diff --git a/CodeEdit/Features/Feedback/Controllers/FeedbackWindowController.swift b/CodeEdit/Features/Feedback/Controllers/FeedbackWindowController.swift deleted file mode 100644 index 824125d3bd..0000000000 --- a/CodeEdit/Features/Feedback/Controllers/FeedbackWindowController.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// FeedbackWindowController.swift -// CodeEditModules/Feedback -// -// Created by Nanashi Li on 2022/04/14. -// - -import SwiftUI - -final class FeedbackWindowController: NSWindowController, NSToolbarDelegate { - convenience init(view: T, size: NSSize) { - let hostingController = NSHostingController(rootView: SettingsInjector { view }) - let window = NSWindow(contentViewController: hostingController) - self.init(window: window) - window.title = "Feedback for CodeEdit" - window.setContentSize(size) - window.styleMask.insert(.fullSizeContentView) - window.styleMask.remove(.resizable) - } - - override func showWindow(_ sender: Any?) { - window?.center() - window?.alphaValue = 0.0 - - super.showWindow(sender) - - window?.animator().alphaValue = 1.0 - - window?.collectionBehavior = [.transient, .ignoresCycle] - window?.backgroundColor = .windowBackgroundColor - - feedbackToolbar() - } - - private func feedbackToolbar() { - let toolbar = NSToolbar(identifier: UUID().uuidString) - toolbar.delegate = self - toolbar.displayMode = .labelOnly - self.window?.toolbarStyle = .unifiedCompact - self.window?.toolbar = toolbar - } - - func closeAnimated() { - NSAnimationContext.beginGrouping() - NSAnimationContext.current.duration = 0.4 - NSAnimationContext.current.completionHandler = { - self.close() - } - window?.animator().alphaValue = 0.0 - NSAnimationContext.endGrouping() - } -} diff --git a/CodeEdit/Features/Feedback/FeedbackView.swift b/CodeEdit/Features/Feedback/FeedbackView.swift deleted file mode 100644 index 03586cc845..0000000000 --- a/CodeEdit/Features/Feedback/FeedbackView.swift +++ /dev/null @@ -1,216 +0,0 @@ -// -// FeedbackView.swift -// CodeEditModules/Feedback -// -// Created by Nanashi Li on 2022/04/14. -// - -import SwiftUI - -struct FeedbackView: View { - @ObservedObject private var feedbackModel: FeedbackModel = .shared - - @State var showsAlert: Bool = false - - @State var isSubmitButtonPressed: Bool = false - - var body: some View { - VStack { - ScrollView { - VStack(alignment: .leading) { - basicInformation - description - } - .padding(.horizontal, 90) - .padding(.vertical, 30) - } - FeedbackToolbar { - HelpButton(action: {}) - Spacer() - if feedbackModel.isSubmitted { - Text("Feedback submitted") - } else if feedbackModel.failedToSubmit { - Text("Failed to submit feedback") - } - Button { - feedbackModel.createIssue( - title: feedbackModel.feedbackTitle, - description: feedbackModel.issueDescription, - steps: feedbackModel.stepsReproduceDescription, - expectation: feedbackModel.expectationDescription, - actuallyHappened: feedbackModel.whatHappenedDescription - ) - isSubmitButtonPressed = true - } label: { - Text("Submit") - } - .alert(isPresented: self.$showsAlert) { - Alert( - title: Text("No GitHub Account"), - message: Text("A GitHub account is required to submit feedback."), - primaryButton: .default(Text("Cancel")), - secondaryButton: .default(Text("Add Account")) - ) - } - } - .padding(10) - .border(Color(NSColor.separatorColor)) - } - .frame(width: 1028, height: 762) - } - - private var basicInformation: some View { - VStack(alignment: .leading) { - Text("Basic Information") - .fontWeight(.bold) - .font(.system(size: 20)) - - VStack(alignment: .leading) { - HStack { - if isSubmitButtonPressed && feedbackModel.feedbackTitle.isEmpty { - HStack { - Image(systemName: "arrow.right.circle.fill") - .foregroundColor(.red) - Text("Please provide a descriptive title for your feedback:") - }.padding(.leading, -23) - } else { - Text("Please provide a descriptive title for your feedback:") - } - } - TextField("", text: $feedbackModel.feedbackTitle) - Text("Example: CodeEdit crashes when using autocomplete") - .font(.system(size: 10)) - .foregroundColor(.secondary) - } - .padding(.top, -5) - - VStack(alignment: .leading) { - HStack { - if isSubmitButtonPressed && feedbackModel.issueAreaListSelection == "none" { - HStack { - Image(systemName: "arrow.right.circle.fill") - .foregroundColor(.red) - Text("Which area are you seeing an issue with?") - }.padding(.leading, -23) - } else { - Text("Which area are you seeing an issue with?") - } - } - Picker("", selection: $feedbackModel.issueAreaListSelection) { - ForEach(feedbackModel.issueAreaList) { - if feedbackModel.issueAreaListSelection == "none" { - Text($0.name) - .tag($0.id) - .foregroundColor(.secondary) - } else { - Text($0.name).tag($0.id) - } - } - } - .frame(width: 350) - .labelsHidden() - } - .padding(.top) - - VStack(alignment: .leading) { - if isSubmitButtonPressed && feedbackModel.feedbackTypeListSelection == "none" { - HStack { - Image(systemName: "arrow.right.circle.fill") - .foregroundColor(.red) - Text("What type of feedback are you reporting?") - }.padding(.leading, -23) - } else { - Text("What type of feedback are you reporting?") - } - Picker("", selection: $feedbackModel.feedbackTypeListSelection) { - ForEach(feedbackModel.feedbackTypeList) { - if feedbackModel.feedbackTypeListSelection == "none" { - Text($0.name) - .tag($0.id) - .foregroundColor(.secondary) - } else { - Text($0.name).tag($0.id) - } - } - } - .frame(width: 350) - .labelsHidden() - } - .padding(.top) - } - } - - private var description: some View { - VStack(alignment: .leading) { - Text("Description") - .fontWeight(.bold) - .font(.system(size: 20)) - .padding(.top) - - VStack(alignment: .leading) { - HStack { - if isSubmitButtonPressed && feedbackModel.issueDescription.isEmpty { - HStack { - Image(systemName: "arrow.right.circle.fill") - .foregroundColor(.red) - Text("Please describe the issue:") - }.padding(.leading, -23) - } else { - Text("Please describe the issue:") - } - } - TextEditor(text: $feedbackModel.issueDescription) - .frame(minHeight: 127, alignment: .leading) - .border(Color(NSColor.separatorColor)) - Text("Example: CodeEdit crashes when the autocomplete popup appears on screen.") - .font(.system(size: 10)) - .foregroundColor(.secondary) - } - .padding(.top, -5) - - VStack(alignment: .leading) { - Text("Please list the steps you took to reproduce the issue:") - TextEditor(text: $feedbackModel.stepsReproduceDescription) - .frame(minHeight: 60, alignment: .leading) - .border(Color(NSColor.separatorColor)) - Text("Example:") - .font(.system(size: 10)) - .foregroundColor(.secondary) - Text("1. Open the attached sample project") - .font(.system(size: 10)) - .foregroundColor(.secondary) - Text("2. type #import and wait for autocompletion to begin") - .font(.system(size: 10)) - .foregroundColor(.secondary) - } - .padding(.top) - - VStack(alignment: .leading) { - Text("What did you expect to happen?") - TextEditor(text: $feedbackModel.expectationDescription) - .frame(minHeight: 60, alignment: .leading) - .border(Color(NSColor.separatorColor)) - Text("Example: I expected autocomplete to show me a list of headers.") - .font(.system(size: 10)) - .foregroundColor(.secondary) - } - .padding(.top) - - VStack(alignment: .leading) { - Text("What actually happened?") - TextEditor(text: $feedbackModel.whatHappenedDescription) - .frame(minHeight: 60, alignment: .leading) - .border(Color(NSColor.separatorColor)) - // swiftlint:disable:next line_length - Text("Example: The autocomplete window flickered on screen and CodeEdit crashed. See attached crashlog.") - .font(.system(size: 10)) - .foregroundColor(.secondary) - } - .padding(.top) - } - } - - func showWindow() { - FeedbackWindowController(view: self, size: NSSize(width: 1028, height: 762)).showWindow(nil) - } -} diff --git a/CodeEdit/Features/Feedback/HelperView/FeedbackToolbar.swift b/CodeEdit/Features/Feedback/HelperView/FeedbackToolbar.swift deleted file mode 100644 index 73781ea12c..0000000000 --- a/CodeEdit/Features/Feedback/HelperView/FeedbackToolbar.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// FeedbackToolbar.swift -// CodeEditModules/Feedback -// -// Created by Nanashi Li on 2022/04/14. -// - -import SwiftUI - -struct FeedbackToolbar: View { - - private var content: () -> T - - init( - bgColor: Color = Color(NSColor.controlBackgroundColor), - @ViewBuilder content: @escaping () -> T - ) { - self.content = content - } - - var body: some View { - ZStack { - HStack { - content() - .padding(.horizontal, 8) - } - } - } -} diff --git a/CodeEdit/Features/Feedback/Model/FeedbackIssueArea.swift b/CodeEdit/Features/Feedback/Model/FeedbackIssueArea.swift deleted file mode 100644 index 4368d76191..0000000000 --- a/CodeEdit/Features/Feedback/Model/FeedbackIssueArea.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// FeedbackIssueArea.swift -// CodeEditModules/Feedback -// -// Created by Nanashi Li on 2022/04/14. -// - -import Foundation - -struct FeedbackIssueArea: Identifiable, Hashable { - let name: String - let id: String -} diff --git a/CodeEdit/Features/Feedback/Model/FeedbackModel.swift b/CodeEdit/Features/Feedback/Model/FeedbackModel.swift deleted file mode 100644 index 589e762694..0000000000 --- a/CodeEdit/Features/Feedback/Model/FeedbackModel.swift +++ /dev/null @@ -1,171 +0,0 @@ -// -// FeedbackModel.swift -// CodeEditModules/Feedback -// -// Created by Nanashi Li on 2022/04/14. -// - -import SwiftUI - -public class FeedbackModel: ObservableObject { - - public static let shared: FeedbackModel = .init() - - private let keychain = CodeEditKeychain() - - @Environment(\.openURL) - var openIssueURL - - @Published var isSubmitted: Bool = false - @Published var failedToSubmit: Bool = false - @Published var feedbackTitle: String = "" - @Published var issueDescription: String = "" - @Published var stepsReproduceDescription: String = "" - @Published var expectationDescription: String = "" - @Published var whatHappenedDescription: String = "" - @Published var issueAreaListSelection: FeedbackIssueArea.ID = "none" - @Published var feedbackTypeListSelection: FeedbackType.ID = "none" - - @Published var feedbackTypeList = [ - FeedbackType(name: "Choose...", id: "none"), - FeedbackType(name: "Incorrect/Unexpected Behaviour", id: "behaviour"), - FeedbackType(name: "Application Crash", id: "crash"), - FeedbackType(name: "Application Slow/Unresponsive", id: "unresponsive"), - FeedbackType(name: "Suggestion", id: "suggestions"), - FeedbackType(name: "Other", id: "other") - ] - - @Published var issueAreaList = [ - FeedbackIssueArea(name: "Please select the problem area", id: "none"), - FeedbackIssueArea(name: "Project Navigator", id: "projectNavigator"), - FeedbackIssueArea(name: "Extensions", id: "extensions"), - FeedbackIssueArea(name: "Git", id: "git"), - FeedbackIssueArea(name: "Debugger", id: "debugger"), - FeedbackIssueArea(name: "Editor", id: "editor"), - FeedbackIssueArea(name: "Other", id: "other") - ] - - /// Gets the ID of the selected issue type and then - /// cross references it to select the right Label based on the type - private func getIssueLabel() -> String { - switch issueAreaListSelection { - case "projectNavigator": - return "Project Navigator" - case "extensions": - return "Extensions" - case "git": - return "Git" - case "debugger": - return "Debugger" - case "editor": - return "Editor" - case "other": - return "Other" - default: - return "Other" - } - } - - /// This is just temporary till we have bot that will handle this - private func getFeedbackTypeTitle() -> String { - switch feedbackTypeListSelection { - case "behaviour": - return "🐞" - case "crash": - return "🐞" - case "unresponsive": - return "🐞" - case "suggestions": - return "✨" - case "other": - return "📬" - default: - return "Other" - } - } - - /// Gets the ID of the selected feedback type and then - /// cross references it to select the right Label based on the type - private func getFeedbackTypeLabel() -> String { - switch feedbackTypeListSelection { - case "behaviour": - return "Bug" - case "crash": - return "Bug" - case "unresponsive": - return "Bug" - case "suggestions": - return "Suggestion" - case "other": - return "Feedback" - default: - return "Other" - } - } - - /// The format for the issue body is how it will be displayed on - /// repos issues. If any changes are made use markdown format - /// because the text gets converted when created. - private func createIssueBody( - description: String, - steps: String?, - expectation: String?, - actuallyHappened: String? - ) -> String { - """ - **Description** - - \(description) - - **Steps to Reproduce** - - \(steps ?? "N/A") - - **What did you expect to happen?** - - \(expectation ?? "N/A") - - **What actually happened?** - - \(actuallyHappened ?? "N/A") - """ - } - - public func createIssue( - title: String, - description: String, - steps: String?, - expectation: String?, - actuallyHappened: String? - ) { - let gitAccounts = Settings[\.accounts].sourceControlAccounts.gitAccounts - let firstGitAccount = gitAccounts.first - - let config = GitHubTokenConfiguration(keychain.get(firstGitAccount!.name)) - GitHubAccount(config).postIssue( - owner: "CodeEditApp", - repository: "CodeEdit", - title: "\(getFeedbackTypeTitle()) \(title)", - body: createIssueBody( - description: description, - steps: steps, - expectation: expectation, - actuallyHappened: actuallyHappened - ), - assignee: "", - labels: [getFeedbackTypeLabel(), getIssueLabel()] - ) { response in - switch response { - case .success(let issue): - if Settings[\.sourceControl].general.openFeedbackInBrowser { - self.openIssueURL(issue.htmlURL ?? URL(string: "https://github.com/CodeEditApp/CodeEdit/issues")!) - } - self.isSubmitted.toggle() - print(issue) - case .failure(let error): - self.failedToSubmit.toggle() - print(error) - } - } - } -} diff --git a/CodeEdit/Features/Feedback/Model/FeedbackType.swift b/CodeEdit/Features/Feedback/Model/FeedbackType.swift deleted file mode 100644 index 22b27903e8..0000000000 --- a/CodeEdit/Features/Feedback/Model/FeedbackType.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// FeedbackType.swift -// CodeEditModules/Feedback -// -// Created by Nanashi Li on 2022/04/14. -// - -import Foundation - -struct FeedbackType: Identifiable, Hashable { - let name: String - let id: String -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount+Token.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount+Token.swift deleted file mode 100644 index 79a76a836a..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount+Token.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// BitBucketAccount+Token.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) -extension BitBucketAccount { - - func refreshToken( - _ session: GitURLSession, - oauthConfig: BitBucketOAuthConfiguration, - refreshToken: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let request = BitBucketTokenRouter.refreshToken(oauthConfig, refreshToken).URLRequest - - var task: GitURLSessionDataTaskProtocol? - - if let request { - task = session.dataTask(with: request) { data, response, _ in - - guard let response = response as? HTTPURLResponse else { return } - - guard let data else { return } - do { - let responseJSON = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) - if let responseJSON = responseJSON as? [String: AnyObject] { - if response.statusCode != 200 { - let errorDescription = responseJSON["error_description"] as? String ?? "" - let error = NSError( - domain: "com.codeedit.models.accounts.bitbucket", - code: response.statusCode, - userInfo: [NSLocalizedDescriptionKey: errorDescription] - ) - completion(Result.failure(error)) - } else { - let tokenConfig = BitBucketTokenConfiguration(json: responseJSON) - completion(Result.success(tokenConfig)) - } - } - } - } - task?.resume() - } - return task - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount.swift deleted file mode 100644 index c7ea43568a..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketAccount.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// BitBucketAccount.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) - -struct BitBucketAccount { - let configuration: BitBucketTokenConfiguration - - init(_ config: BitBucketTokenConfiguration = BitBucketTokenConfiguration()) { - configuration = config - } -} - -extension GitRouter { - internal var URLRequest: Foundation.URLRequest? { - request() - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketOAuthConfiguration.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketOAuthConfiguration.swift deleted file mode 100644 index 57620ee824..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketOAuthConfiguration.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// BitBucketOAuthConfiguration.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -struct BitBucketOAuthConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.bitbucketCloud - var apiEndpoint: String? - var accessToken: String? - let token: String - let secret: String - let scopes: [String] - let webEndpoint: String? - let errorDomain = "com.codeedit.models.accounts.bitbucket" - - init( - _ url: String? = nil, - webURL: String? = nil, - token: String, - secret: String, - scopes: [String] - ) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - webEndpoint = webURL ?? provider.baseURL?.absoluteString - self.token = token - self.secret = secret - self.scopes = [] - } - - func authenticate() -> URL? { - BitBucketOAuthRouter.authorize(self).URLRequest?.url - } - - fileprivate func basicAuthenticationString() -> String { - let clientIDSecretString = [token, secret].joined(separator: ":") - let clientIDSecretData = clientIDSecretString.data(using: String.Encoding.utf8) - let base64 = clientIDSecretData?.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) - return "Basic \(base64 ?? "")" - } - - func basicAuthConfig() -> URLSessionConfiguration { - let config = URLSessionConfiguration.default - config.httpAdditionalHeaders = ["Authorization": basicAuthenticationString()] - return config - } - - func authorize( - _ session: GitURLSession, - code: String, - completion: @escaping (_ config: BitBucketTokenConfiguration) -> Void - ) { - let request = BitBucketOAuthRouter.accessToken(self, code).URLRequest - - if let request { - let task = session.dataTask(with: request) { data, response, _ in - if let response = response as? HTTPURLResponse { - if response.statusCode != 200 { - return - } else { - if let config = self.configFromData(data) { - completion(config) - } - } - } - } - task.resume() - } - } - - private func configFromData(_ data: Data?) -> BitBucketTokenConfiguration? { - guard let data else { return nil } - do { - guard let json = try JSONSerialization.jsonObject( - with: data, - options: .allowFragments - ) as? [String: AnyObject] else { - return nil - } - let config = BitBucketTokenConfiguration(json: json) - return config - } catch { - return nil - } - } - - func handleOpenURL( - _ session: GitURLSession = URLSession.shared, - url: URL, - completion: @escaping (_ config: BitBucketTokenConfiguration) -> Void - ) { - let params = url.bitbucketURLParameters() - - if let code = params["code"] { - authorize(session, code: code) { config in - completion(config) - } - } - } - - func accessTokenFromResponse(_ response: String) -> String? { - let accessTokenParam = response.components(separatedBy: "&").first - if let accessTokenParam { - return accessTokenParam.components(separatedBy: "=").last - } - return nil - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketTokenConfiguration.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketTokenConfiguration.swift deleted file mode 100644 index 46173eac19..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/BitBucketTokenConfiguration.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// BitBucketTokenConfiguration.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -struct BitBucketTokenConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.bitbucketCloud - var apiEndpoint: String? - var accessToken: String? - var refreshToken: String? - var expirationDate: Date? - let errorDomain = "com.codeedit.models.accounts.bitbucket" - - init(json: [String: AnyObject], url: String? = nil) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - accessToken = json["access_token"] as? String - refreshToken = json["refresh_token"] as? String - let expiresIn = json["expires_in"] as? Int - let currentDate = Date() - expirationDate = currentDate.addingTimeInterval(TimeInterval(expiresIn ?? 0)) - } - - init( - _ token: String? = nil, - refreshToken: String? = nil, - expirationDate: Date? = nil, - url: String? = nil - ) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - accessToken = token - self.expirationDate = expirationDate - self.refreshToken = refreshToken - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketRepositories.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketRepositories.swift deleted file mode 100644 index 73028dcd92..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketRepositories.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// BitBucketRepositories.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) -class BitBucketRepositories: Codable { - var id: String - var owner: BitBucketUser - var name: String? - var fullName: String? - var isPrivate: Bool - var repositoryDescription: String? - var gitURL: String? - var sshURL: String? - var cloneURL: String? - var size: Int - var scm: String? - - enum CodingKeys: String, CodingKey { - case id = "uuid" - case owner - case name - case fullName = "full_name" - case isPrivate = "is_private" - case repositoryDescription = "description" - case gitURL = "git://" - case sshURL = "ssh://" - case cloneURL = "https://" - case size - case scm - } -} - -enum BitbucketPaginatedResponse { - case success(values: T, nextParameters: [String: String]) - case failure(Error) -} - -extension BitBucketAccount { - - func repositories( - _ session: GitURLSession = URLSession.shared, - userName: String? = nil, - nextParameters: [String: String] = [:], - completion: @escaping (_ response: BitbucketPaginatedResponse<[BitBucketRepositories]>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = BitBucketRepositoryRouter.readRepositories(configuration, userName, nextParameters) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: BitBucketRepositories.self - ) { repo, error in - - if let error { - completion(BitbucketPaginatedResponse.failure(error)) - } else { - if let repo { - completion(BitbucketPaginatedResponse.success(values: [repo], nextParameters: [:])) - } - } - } - } - - func repository( - _ session: GitURLSession = URLSession.shared, - owner: String, - name: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = BitBucketRepositoryRouter.readRepository(configuration, owner, name) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: BitBucketRepositories.self - ) { data, error in - - if let error { - completion(Result.failure(error)) - } - - if let data { - completion(Result.success(data)) - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketUser.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketUser.swift deleted file mode 100644 index 4cb323504f..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/Model/BitBucketUser.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// BitBucketUser.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -import SwiftUI - -// TODO: DOCS (Nanashi Li) -class BitBucketUser: Codable { - var id: String? - var login: String? - var name: String? - - enum CodingKeys: String, CodingKey { - case id - case login = "username" - case name = "display_name" - } -} - -class BitBucketEmail: Codable { - var isPrimary: Bool - var isConfirmed: Bool - var type: String? - var email: String? - - enum CodingKeys: String, CodingKey { - case isPrimary = "is_primary" - case isConfirmed = "is_confirmed" - case type = "type" - case email = "email" - } -} - -extension BitBucketAccount { - - func me( - _ session: GitURLSession = URLSession.shared, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = BitBucketUserRouter.readAuthenticatedUser(configuration) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: BitBucketUser.self - ) { user, error in - if let error { - completion(.failure(error)) - } else { - if let user { - completion(.success(user)) - } - } - } - } - - func emails( - _ session: GitURLSession = URLSession.shared, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = BitBucketUserRouter.readEmails(configuration) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: BitBucketEmail.self - ) { email, error in - if let error { - completion(.failure(error)) - } else { - if let email { - completion(.success(email)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketOAuthRouter.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketOAuthRouter.swift deleted file mode 100644 index 15dd870df2..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketOAuthRouter.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// BitBucketOAuthRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum BitBucketOAuthRouter: GitRouter { - case authorize(BitBucketOAuthConfiguration) - case accessToken(BitBucketOAuthConfiguration, String) - - var configuration: GitRouterConfiguration? { - switch self { - case .authorize(let config): return config - case .accessToken(let config, _): return config - } - } - - var method: GitHTTPMethod { - switch self { - case .authorize: - return .GET - case .accessToken: - return .POST - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .authorize: - return .url - case .accessToken: - return .form - } - } - - var path: String { - switch self { - case .authorize: - return "site/oauth2/authorize" - case .accessToken: - return "site/oauth2/access_token" - } - } - - var params: [String: Any] { - switch self { - case .authorize(let config): - return ["client_id": config.token, "response_type": "code"] - case .accessToken(_, let code): - return ["code": code, "grant_type": "authorization_code"] - } - } - - var URLRequest: Foundation.URLRequest? { - switch self { - case .authorize(let config): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - case .accessToken(let config, _): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketRepositoryRouter.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketRepositoryRouter.swift deleted file mode 100644 index 5da6f1964a..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketRepositoryRouter.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// BitBucketRepositoryRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum BitBucketRepositoryRouter: GitRouter { - case readRepositories(GitRouterConfiguration, String?, [String: String]) - case readRepository(GitRouterConfiguration, String, String) - - var configuration: GitRouterConfiguration? { - switch self { - case .readRepositories(let config, _, _): return config - case .readRepository(let config, _, _): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var params: [String: Any] { - switch self { - case .readRepositories(_, let userName, var nextParameters): - if userName != nil { - return nextParameters as [String: Any] - } else { - nextParameters["role"] = "member" - return nextParameters as [String: Any] - } - case .readRepository: - return [:] - } - } - - var path: String { - switch self { - case .readRepositories(_, let userName, _): - if let userName { - return "repositories/\(userName)" - } else { - return "repositories" - } - case let .readRepository(_, owner, name): - return "repositories/\(owner)/\(name)" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketTokenRouter.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketTokenRouter.swift deleted file mode 100644 index 891f17e81c..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketTokenRouter.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// BitBucketTokenRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum BitBucketTokenRouter: GitRouter { - case refreshToken(BitBucketOAuthConfiguration, String) - case emptyToken(BitBucketOAuthConfiguration, String) - - var configuration: GitRouterConfiguration? { - switch self { - case .refreshToken(let config, _): return config - default: return nil - } - } - - var method: GitHTTPMethod { - .POST - } - - var encoding: GitHTTPEncoding { - .form - } - - var params: [String: Any] { - switch self { - case .refreshToken(_, let token): - return ["refresh_token": token, "grant_type": "refresh_token"] - default: return ["": ""] - } - } - - var path: String { - switch self { - case .refreshToken: - return "site/oauth2/access_token" - default: return "" - } - } - - var URLRequest: Foundation.URLRequest? { - switch self { - case .refreshToken(let config, _): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - default: return nil - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketUserRouter.swift b/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketUserRouter.swift deleted file mode 100644 index 02e3b56cc5..0000000000 --- a/CodeEdit/Features/Git/Accounts/Bitbucket/Routers/BitBucketUserRouter.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// BitBucketUserRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum BitBucketUserRouter: GitRouter { - case readAuthenticatedUser(GitRouterConfiguration) - case readEmails(GitRouterConfiguration) - - var configuration: GitRouterConfiguration? { - switch self { - case .readAuthenticatedUser(let config): return config - case .readEmails(let config): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var path: String { - switch self { - case .readAuthenticatedUser: - return "user" - case .readEmails: - return "user/emails" - } - } - - var params: [String: Any] { - [:] - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/GitHubAccount.swift b/CodeEdit/Features/Git/Accounts/GitHub/GitHubAccount.swift deleted file mode 100644 index 39902dc893..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/GitHubAccount.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// GitHubAccount.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) - -struct GitHubAccount { - let configuration: GitHubTokenConfiguration - - init(_ config: GitHubTokenConfiguration = GitHubTokenConfiguration()) { - configuration = config - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/GitHubConfiguration.swift b/CodeEdit/Features/Git/Accounts/GitHub/GitHubConfiguration.swift deleted file mode 100644 index 48e9b44447..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/GitHubConfiguration.swift +++ /dev/null @@ -1,194 +0,0 @@ -// -// GitHubConfiguration.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -struct GitHubTokenConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.github - var apiEndpoint: String? - var accessToken: String? - let errorDomain: String? = "com.codeedit.models.accounts.github" - let authorizationHeader: String? = "Basic" - - /// Custom `Accept` header for API previews. - /// - /// Used for preview support of new APIs, for instance Reaction API. - /// see: https://developer.github.com/changes/2016-05-12-reactions-api-preview/ - private var previewCustomHeaders: [GitHTTPHeader]? - - var customHeaders: [GitHTTPHeader]? { - /// More (non-preview) headers can be appended if needed in the future - return previewCustomHeaders - } - - init(_ token: String? = nil, url: String? = nil, previewHeaders: [GitHubPreviewHeader] = []) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - accessToken = token?.data(using: .utf8)!.base64EncodedString() - previewCustomHeaders = previewHeaders.map { $0.header } - } -} - -struct GitHubOAuthConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.github - var apiEndpoint: String? - var accessToken: String? - let token: String - let secret: String - let scopes: [String] - let webEndpoint: String? - let errorDomain = "com.codeedit.models.accounts.github" - - /// Custom `Accept` header for API previews. - /// - /// Used for preview support of new APIs, for instance Reaction API. - /// see: https://developer.github.com/changes/2016-05-12-reactions-api-preview/ - private var previewCustomHeaders: [GitHTTPHeader]? - - var customHeaders: [GitHTTPHeader]? { - /// More (non-preview) headers can be appended if needed in the future - return previewCustomHeaders - } - - init( - _ url: String? = nil, - webURL: String? = nil, - token: String, - secret: String, - scopes: [String], - previewHeaders: [GitHubPreviewHeader] = [] - ) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - webEndpoint = webURL ?? provider.baseURL?.absoluteString - self.token = token - self.secret = secret - self.scopes = scopes - previewCustomHeaders = previewHeaders.map { $0.header } - } - - func authenticate() -> URL? { - GitHubOAuthRouter.authorize(self).URLRequest?.url - } - - func authorize( - _ session: GitURLSession = URLSession.shared, - code: String, - completion: @escaping (_ config: GitHubTokenConfiguration) -> Void - ) { - - let request = GitHubOAuthRouter.accessToken(self, code).URLRequest - if let request { - let task = session.dataTask(with: request) { data, response, _ in - if let response = response as? HTTPURLResponse { - if response.statusCode != 200 { - return - } else { - if let data, let string = String(data: data, encoding: .utf8) { - let accessToken = self.accessTokenFromResponse(string) - if let accessToken { - let config = GitHubTokenConfiguration(accessToken, url: self.apiEndpoint ?? "") - completion(config) - } - } - } - } - } - task.resume() - } - } - - func handleOpenURL( - _ session: GitURLSession = URLSession.shared, - url: URL, - completion: @escaping (_ config: GitHubTokenConfiguration) -> Void - ) { - - if let code = url.URLParameters["code"] { - authorize(session, code: code) { config in - completion(config) - } - } - } - - func accessTokenFromResponse(_ response: String) -> String? { - let accessTokenParam = response.components(separatedBy: "&").first - if let accessTokenParam { - return accessTokenParam.components(separatedBy: "=").last - } - return nil - } -} - -enum GitHubOAuthRouter: GitRouter { - case authorize(GitHubOAuthConfiguration) - case accessToken(GitHubOAuthConfiguration, String) - - var configuration: GitRouterConfiguration? { - switch self { - case let .authorize(config): return config - case let .accessToken(config, _): return config - } - } - - var method: GitHTTPMethod { - switch self { - case .authorize: - return .GET - case .accessToken: - return .POST - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .authorize: - return .url - case .accessToken: - return .form - } - } - - var path: String { - switch self { - case .authorize: - return "login/oauth/authorize" - case .accessToken: - return "login/oauth/access_token" - } - } - - var params: [String: Any] { - switch self { - case let .authorize(config): - let scope = (config.scopes as NSArray).componentsJoined(by: ",") - return ["scope": scope, "client_id": config.token, "allow_signup": "false"] - case let .accessToken(config, code): - return ["client_id": config.token, "client_secret": config.secret, "code": code] - } - } - - #if canImport(FoundationNetworking) - typealias FoundationURLRequestType = FoundationNetworking.URLRequest - #else - typealias FoundationURLRequestType = Foundation.URLRequest - #endif - - var URLRequest: FoundationURLRequestType? { - switch self { - case let .authorize(config): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - case let .accessToken(config, _): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/GitHubOpenness.swift b/CodeEdit/Features/Git/Accounts/GitHub/GitHubOpenness.swift deleted file mode 100644 index 0faea8ea31..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/GitHubOpenness.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// GitHubOpenness.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubOpenness: String, Codable { - case open - case closed - case all -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/GitHubPreviewHeader.swift b/CodeEdit/Features/Git/Accounts/GitHub/GitHubPreviewHeader.swift deleted file mode 100644 index 0a8c415cf6..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/GitHubPreviewHeader.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// GitHubPreviewHeader.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) - -/// Some APIs provide additional data for new (preview) APIs if a custom header is added to the request. -/// -/// - Note: Preview APIs are subject to change. -enum GitHubPreviewHeader { - /// The `Reactions` preview header provides reactions in `Comment`s. - case reactions - - var header: GitHTTPHeader { - switch self { - case .reactions: - return GitHTTPHeader(headerField: "Accept", value: "application/vnd.github.squirrel-girl-preview") - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubAccount+deleteReference.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubAccount+deleteReference.swift deleted file mode 100644 index 648ce72faf..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubAccount+deleteReference.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// GitHubAccount+deleteReference.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -extension GitHubAccount { - - /** - Deletes a reference. - - Parameters: - - session: GitURLSession, defaults to URLSession.shared() - - owner: The user or organization that owns the repositories. - - repo: The repository on which the reference needs to be deleted. - - ref: The reference to delete. - - completion: Callback for the outcome of the deletion. - */ - @discardableResult - func deleteReference( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - ref: String, - completion: @escaping (_ response: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubRouter.deleteReference(configuration, owner, repository, ref) - return router.load(session, completion: completion) - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubComment.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubComment.swift deleted file mode 100644 index 4d294c4e15..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubComment.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// GitHubComment.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -struct GitHubComment: Codable { - let id: Int - let url: URL - let htmlURL: URL - let body: String - let user: GitHubUser - let createdAt: Date - let updatedAt: Date - - enum CodingKeys: String, CodingKey { - case id, url, body, user - case htmlURL = "html_url" - case createdAt = "created_at" - case updatedAt = "updated_at" - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubFiles.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubFiles.swift deleted file mode 100644 index adb83e1369..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubFiles.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// GitHubFiles.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) - -class GitHubFile: Codable { - private(set) var id: Int = -1 - var rawURL: URL? - var filename: String? - var type: String? - var language: String? - var size: Int? - var content: String? - - enum CodingKeys: String, CodingKey { - case rawURL = "raw_url" - case filename - case type - case language - case size - case content - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubGist.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubGist.swift deleted file mode 100644 index 2e20bbbe17..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubGist.swift +++ /dev/null @@ -1,233 +0,0 @@ -// -// GitHubGist.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -class GitHubGist: Codable { - typealias GitHubFiles = [String: GitHubFile] - - private(set) var id: String? - var url: URL? - var forksURL: URL? - var commitsURL: URL? - var gitPushURL: URL? - var gitPullURL: URL? - var commentsURL: URL? - var htmlURL: URL? - var files: GitHubFiles - var publicGist: Bool? - var createdAt: Date? - var updatedAt: Date? - var description: String? - var comments: Int? - var user: GitHubUser? - var owner: GitHubUser? - - enum CodingKeys: String, CodingKey { - case id - case url - case forksURL = "forks_url" - case commitsURL = "commits_url" - case gitPushURL = "git_pull_url" - case gitPullURL = "git_push_url" - case commentsURL = "comments_url" - case htmlURL = "html_url" - case files - case publicGist = "public" - case createdAt = "created_at" - case updatedAt = "updated_at" - case description - case comments - case user - case owner - } -} - -extension GitHubAccount { - - /** - Fetches the gists of the authenticated user - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter page: Current page for gist pagination. `1` by default. - - parameter perPage: Number of gists per page. `100` by default. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func myGists( - _ session: GitURLSession = URLSession.shared, - page: String = "1", - perPage: String = "100", - completion: @escaping (_ response: Result<[GitHubGist], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubGistRouter.readAuthenticatedGists(configuration, page, perPage) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubGist].self - ) { gists, error in - - if let error { - completion(.failure(error)) - } else { - if let gists { - completion(.success(gists)) - } - } - } - } - - /** - Fetches the gists of the specified user - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter owner: The username who owns the gists. - - parameter page: Current page for gist pagination. `1` by default. - - parameter perPage: Number of gists per page. `100` by default. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func gists( - _ session: GitURLSession = URLSession.shared, - owner: String, - page: String = "1", - perPage: String = "100", - completion: @escaping (_ response: Result<[GitHubGist], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubGistRouter.readGists(configuration, owner, page, perPage) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubGist].self - ) { gists, error in - - if let error { - completion(.failure(error)) - } else { - if let gists { - completion(.success(gists)) - } - } - } - } - - /** - Fetches an gist - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter id: The id of the gist. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func gist( - _ session: GitURLSession = URLSession.shared, - id: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubGistRouter.readGist(configuration, id) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitHubGist.self - ) { gist, error in - - if let error { - completion(.failure(error)) - } else { - if let gist { - completion(.success(gist)) - } - } - } - } - - /** - Creates an gist with a single file. - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter description: The description of the gist. - - parameter filename: The name of the file in the gist. - - parameter fileContent: The content of the file in the gist. - - parameter publicAccess: The public/private visibility of the gist. - - parameter completion: Callback for the gist that is created. - */ - @discardableResult - func postGistFile( - _ session: GitURLSession = URLSession.shared, - description: String, - filename: String, - fileContent: String, - publicAccess: Bool, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubGistRouter.postGistFile(configuration, description, filename, fileContent, publicAccess) - let decoder = JSONDecoder() - - decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter) - - return router.post( - session, - decoder: decoder, - expectedResultType: GitHubGist.self - ) { gist, error in - - if let error { - completion(.failure(error)) - } else { - if let gist { - completion(.success(gist)) - } - } - } - } - - /** - Edits an gist with a single file. - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter id: The of the gist to update. - - parameter description: The description of the gist. - - parameter filename: The name of the file in the gist. - - parameter fileContent: The content of the file in the gist. - - parameter completion: Callback for the gist that is created. - */ - @discardableResult - func patchGistFile( - _ session: GitURLSession = URLSession.shared, - id: String, - description: String, - filename: String, - fileContent: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubGistRouter.patchGistFile(configuration, id, description, filename, fileContent) - let decoder = JSONDecoder() - - decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter) - - return router.post( - session, - decoder: decoder, - expectedResultType: GitHubGist.self - ) { gist, error in - - if let error { - completion(.failure(error)) - } else { - if let gist { - completion(.success(gist)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubIssue.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubIssue.swift deleted file mode 100644 index c83a0cef02..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubIssue.swift +++ /dev/null @@ -1,368 +0,0 @@ -// -// GitHubIssue.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -class GitHubIssue: Codable { - private(set) var id: Int = -1 - var url: URL? - var repositoryURL: URL? - @available(*, deprecated) - var labelsURL: URL? - var commentsURL: URL? - var eventsURL: URL? - var htmlURL: URL? - var number: Int - var state: GitHubOpenness? - var title: String? - var body: String? - var user: GitHubUser? - var assignee: GitHubUser? - var locked: Bool? - var comments: Int? - var closedAt: Date? - var createdAt: Date? - var updatedAt: Date? - var closedBy: GitHubUser? - - enum CodingKeys: String, CodingKey { - case id - case url - case repositoryURL = "repository_url" - case commentsURL = "comments_url" - case eventsURL = "events_url" - case htmlURL = "html_url" - case number - case state - case title - case body - case user - case assignee - case locked - case comments - case closedAt = "closed_at" - case createdAt = "created_at" - case updatedAt = "updated_at" - case closedBy = "closed_by" - } -} - -extension GitHubAccount { - /** - Fetches the issues of the authenticated user - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter state: Issue state. Defaults to open if not specified. - - parameter page: Current page for issue pagination. `1` by default. - - parameter perPage: Number of issues per page. `100` by default. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func myIssues( - _ session: GitURLSession = URLSession.shared, - state: GitHubOpenness = .open, - page: String = "1", - perPage: String = "100", - completion: @escaping (_ response: Result<[GitHubIssue], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubIssueRouter.readAuthenticatedIssues(configuration, page, perPage, state) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubIssue].self - ) { issues, error in - - if let error { - completion(.failure(error)) - } else { - if let issues { - completion(.success(issues)) - } - } - } - } - - /** - Fetches an issue in a repository - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter owner: The user or organization that owns the repository. - - parameter repository: The name of the repository. - - parameter number: The number of the issue. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func issue( - _ session: GitURLSession = URLSession.shared, - owner: String, repository: String, - number: Int, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubIssueRouter.readIssue(configuration, owner, repository, number) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitHubIssue.self - ) { issue, error in - - if let error { - completion(.failure(error)) - } else { - if let issue { - completion(.success(issue)) - } - } - } - } - - /** - Fetches all issues in a repository - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter owner: The user or organization that owns the repository. - - parameter repository: The name of the repository. - - parameter state: Issue state. Defaults to open if not specified. - - parameter page: Current page for issue pagination. `1` by default. - - parameter perPage: Number of issues per page. `100` by default. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func issues( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - state: GitHubOpenness = .open, - page: String = "1", - perPage: String = "100", - completion: @escaping (_ response: Result<[GitHubIssue], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubIssueRouter.readIssues(configuration, owner, repository, page, perPage, state) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubIssue].self - ) { issues, error in - - if let error { - completion(.failure(error)) - } else { - if let issues { - completion(.success(issues)) - } - } - } - } - - /** - Creates an issue in a repository. - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter owner: The user or organization that owns the repository. - - parameter repository: The name of the repository. - - parameter title: The title of the issue. - - parameter body: The body text of the issue in GitHub-flavored Markdown format. - - parameter assignee: The name of the user to assign the issue to. - This parameter is ignored if the user lacks push access to the repository. - - parameter labels: An array of label names to add to the issue. If the labels do not exist, - GitHub will create them automatically. - This parameter is ignored if the user lacks push access to the repository. - - parameter completion: Callback for the issue that is created. - */ - @discardableResult - func postIssue( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - title: String, - body: String? = nil, - assignee: String? = nil, - labels: [String] = [], - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubIssueRouter.postIssue(configuration, owner, repository, title, body, assignee, labels) - let decoder = JSONDecoder() - - decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter) - - return router.post( - session, - decoder: decoder, - expectedResultType: GitHubIssue.self - ) { issue, error in - - if let error { - completion(.failure(error)) - } else { - if let issue { - completion(.success(issue)) - } - } - } - } - - /** - Edits an issue in a repository. - - parameter session: GitURLSession, defaults to URLSession.sharedSession() - - parameter owner: The user or organization that owns the repository. - - parameter repository: The name of the repository. - - parameter number: The number of the issue. - - parameter title: The title of the issue. - - parameter body: The body text of the issue in GitHub-flavored Markdown format. - - parameter assignee: The name of the user to assign the issue to. - This parameter is ignored if the user lacks push access to the repository. - - parameter state: Whether the issue is open or closed. - - parameter completion: Callback for the issue that is created. - */ - @discardableResult - func patchIssue( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - number: Int, - title: String? = nil, - body: String? = nil, - assignee: String? = nil, - state: GitHubOpenness? = nil, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubIssueRouter.patchIssue( - configuration, owner, repository, number, title, body, assignee, state - ) - - return router.post( - session, - expectedResultType: GitHubIssue.self - ) { issue, error in - - if let error { - completion(.failure(error)) - } else { - if let issue { - completion(.success(issue)) - } - } - } - } - - /// Posts a comment on an issue using the given body. - /// - Parameters: - /// - session: GitURLSession, defaults to URLSession.sharedSession() - /// - owner: The user or organization that owns the repository. - /// - repository: The name of the repository. - /// - number: The number of the issue. - /// - body: The contents of the comment. - /// - completion: Callback for the comment that is created. - @discardableResult - func commentIssue( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - number: Int, - body: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubIssueRouter.commentIssue(configuration, owner, repository, number, body) - let decoder = JSONDecoder() - - decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter) - - return router.post( - session, - decoder: decoder, - expectedResultType: GitHubComment.self - ) { issue, error in - - if let error { - completion(.failure(error)) - } else { - if let issue { - completion(.success(issue)) - } - } - } - } - - /// Fetches all comments for an issue - /// - Parameters: - /// - session: GitURLSession, defaults to URLSession.sharedSession() - /// - owner: The user or organization that owns the repository. - /// - repository: The name of the repository. - /// - number: The number of the issue. - /// - page: Current page for comments pagination. `1` by default. - /// - perPage: Number of comments per page. `100` by default. - /// - completion: Callback for the outcome of the fetch. - @discardableResult - func issueComments( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - number: Int, - page: String = "1", - perPage: String = "100", - completion: @escaping (_ response: Result<[GitHubComment], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubIssueRouter.readIssueComments(configuration, owner, repository, number, page, perPage) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubComment].self - ) { comments, error in - - if let error { - completion(.failure(error)) - } else { - if let comments { - completion(.success(comments)) - } - } - } - } - - /// Edits a comment on an issue using the given body. - /// - Parameters: - /// - session: GitURLSession, defaults to URLSession.sharedSession() - /// - owner: The user or organization that owns the repository. - /// - repository: The name of the repository. - /// - number: The number of the comment. - /// - body: The contents of the comment. - /// - completion: Callback for the comment that is created. - @discardableResult - func patchIssueComment( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - number: Int, - body: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let router = GitHubIssueRouter.patchIssueComment(configuration, owner, repository, number, body) - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(GitTime.rfc3339DateFormatter) - - return router.post( - session, decoder: decoder, - expectedResultType: GitHubComment.self - ) { issue, error in - - if let error { - completion(.failure(error)) - } else { - if let issue { - completion(.success(issue)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubPullRequest.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubPullRequest.swift deleted file mode 100644 index 67bd21eb6e..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubPullRequest.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// GitHubPullRequest.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -class GitHubPullRequest: Codable { - private(set) var id: Int = -1 - var url: URL? - - var htmlURL: URL? - var diffURL: URL? - var patchURL: URL? - var issueURL: URL? - var commitsURL: URL? - var reviewCommentsURL: URL? - var reviewCommentURL: URL? - var commentsURL: URL? - var statusesURL: URL? - - var title: String? - var body: String? - - var assignee: GitHubUser? - - var locked: Bool? - var createdAt: Date? - var updatedAt: Date? - var closedAt: Date? - var mergedAt: Date? - - var user: GitHubUser? - var number: Int - var state: GitHubOpenness? - - var head: GitHubPullRequest.Branch? - var base: GitHubPullRequest.Branch? - - var requestedReviewers: [GitHubUser]? - var draft: Bool? - - enum CodingKeys: String, CodingKey { - case id - case url - case diffURL = "diff_url" - case patchURL = "patch_url" - case issueURL = "issue_url" - case commitsURL = "commits_url" - case reviewCommentsURL = "review_comments_url" - case commentsURL = "comments_url" - case statusesURL = "statuses_url" - case htmlURL = "html_url" - case number - case state - case title - case body - case assignee - case locked - case user - case closedAt = "closed_at" - case createdAt = "created_at" - case updatedAt = "updated_at" - case mergedAt = "merged_at" - case head - case base - case requestedReviewers = "requested_reviewers" - case draft - } - - class Branch: Codable { - var label: String? - var ref: String? - var sha: String? - var user: GitHubUser? - var repo: GitHubRepositories? - } -} - -extension GitHubAccount { - - /** - Get a single pull request - - parameter session: GitURLSession, defaults to URLSession.shared - - parameter owner: The user or organization that owns the repositories. - - parameter repository: The name of the repository. - - parameter number: The number of the PR to fetch. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func pullRequest( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - number: Int, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubPullRequestRouter.readPullRequest(configuration, owner, repository, "\(number)") - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitHubPullRequest.self - ) { pullRequest, error in - - if let error { - completion(.failure(error)) - } else { - if let pullRequest { - completion(.success(pullRequest)) - } - } - } - } - - /** - Get a list of pull requests - - parameter session: GitURLSession, defaults to URLSession.shared - - parameter owner: The user or organization that owns the repositories. - - parameter repository: The name of the repository. - - parameter base: Filter pulls by base branch name. - - parameter head: Filter pulls by user or organization and branch name. - - parameter state: Filter pulls by their state. - - parameter direction: The direction of the sort. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func pullRequests( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - base: String? = nil, - head: String? = nil, - state: GitHubOpenness = .open, - sort: GitSortType = .created, - direction: GitSortDirection = .desc, - completion: @escaping (_ response: Result<[GitHubPullRequest], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubPullRequestRouter.readPullRequests( - configuration, - owner, - repository, - base, - head, - state, - sort, - direction - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubPullRequest].self - ) { pullRequests, error in - - if let error { - completion(.failure(error)) - } else { - if let pullRequests { - completion(.success(pullRequests)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubRepositories.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubRepositories.swift deleted file mode 100644 index e771bab31f..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubRepositories.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// Repositories.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -class GitHubRepositories: Codable { - private(set) var id: Int = -1 - private(set) var owner = GitHubUser() - var name: String? - var fullName: String? - private(set) var isPrivate: Bool = false - var repositoryDescription: String? - private(set) var isFork: Bool = false - var gitURL: String? - var sshURL: String? - var cloneURL: String? - var htmlURL: String? - private(set) var size: Int? = -1 - var lastPush: Date? - var stargazersCount: Int? - - enum CodingKeys: String, CodingKey { - case id - case owner - case name - case fullName = "full_name" - case isPrivate = "private" - case repositoryDescription = "description" - case isFork = "fork" - case gitURL = "git_url" - case sshURL = "ssh_url" - case cloneURL = "clone_url" - case htmlURL = "html_url" - case size - case lastPush = "pushed_at" - case stargazersCount = "stargazers_count" - } -} - -extension GitHubAccount { - - /** - Fetches the Repositories for a user or organization - - parameter session: GitURLSession, defaults to URLSession.shared - - parameter owner: The user or organization that owns the repositories. If `nil`, - fetches repositories for the authenticated user. - - parameter page: Current page for repository pagination. `1` by default. - - parameter perPage: Number of repositories per page. `100` by default. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func repositories( - _ session: GitURLSession = URLSession.shared, - owner: String? = nil, - page: String = "1", - perPage: String = "100", - completion: @escaping (_ response: Result<[GitHubRepositories], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = (owner != nil) - ? GitHubRepositoryRouter.readRepositories(configuration, owner!, page, perPage) - : GitHubRepositoryRouter.readAuthenticatedRepositories(configuration, page, perPage) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubRepositories].self - ) { repos, error in - if let error { - completion(.failure(error)) - } - - if let repos { - completion(.success(repos)) - } - } - } - - /** - Fetches a repository for a user or organization - - parameter session: GitURLSession, defaults to URLSession.shared - - parameter owner: The user or organization that owns the repositories. - - parameter name: The name of the repository to fetch. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func repository( - _ session: GitURLSession = URLSession.shared, - owner: String, - name: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubRepositoryRouter.readRepository(configuration, owner, name) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitHubRepositories.self - ) { repo, error in - if let error { - completion(.failure(error)) - } else { - if let repo { - completion(.success(repo)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubReview.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubReview.swift deleted file mode 100644 index f73afc3e65..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubReview.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// GitHubReview.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -// TODO: DOCS (Nanashi Li) -struct GitHubReview { - let body: String - let commitID: String - let id: Int - let state: State - let submittedAt: Date - let user: GitHubUser -} - -extension GitHubReview: Codable { - enum CodingKeys: String, CodingKey { - case body - case commitID = "commit_id" - case id - case state - case submittedAt = "submitted_at" - case user - } -} - -extension GitHubReview { - enum State: String, Codable, Equatable { - case approved = "APPROVED" - case changesRequested = "CHANGES_REQUESTED" - case comment = "COMMENTED" - case dismissed = "DISMISSED" - case pending = "PENDING" - } -} - -extension GitHubAccount { - - @discardableResult - func listReviews( - _ session: GitURLSession = URLSession.shared, - owner: String, - repository: String, - pullRequestNumber: Int, - completion: @escaping (_ response: Result<[GitHubReview], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubReviewsRouter.listReviews(configuration, owner, repository, pullRequestNumber) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: [GitHubReview].self - ) { pullRequests, error in - - if let error { - completion(.failure(error)) - } else { - if let pullRequests { - completion(.success(pullRequests)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubUser.swift b/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubUser.swift deleted file mode 100644 index 563eb5c96c..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Model/GitHubUser.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// GitHubUser.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -class GitHubUser: Codable { - private(set) var id: Int = -1 - var login: String? - var avatarURL: String? - var gravatarID: String? - var type: String? - var name: String? - var company: String? - var email: String? - var numberOfPublicRepos: Int? - var numberOfPublicGists: Int? - var numberOfPrivateRepos: Int? - var nodeID: String? - var url: String? - var htmlURL: String? - var gistsURL: String? - var starredURL: String? - var subscriptionsURL: String? - var reposURL: String? - var eventsURL: String? - var receivedEventsURL: String? - var createdAt: Date? - var updatedAt: Date? - var numberOfPrivateGists: Int? - var numberOfOwnPrivateRepos: Int? - var twoFactorAuthenticationEnabled: Bool? - - enum CodingKeys: String, CodingKey { - case id - case login - case avatarURL = "avatar_url" - case gravatarID = "gravatar_id" - case type - case name - case company - case email - case numberOfPublicRepos = "public_repos" - case numberOfPublicGists = "public_gists" - case numberOfPrivateRepos = "total_private_repos" - case nodeID = "node_id" - case url - case htmlURL = "html_url" - case gistsURL = "gists_url" - case starredURL = "starred_url" - case subscriptionsURL = "subscriptions_url" - case reposURL = "repos_url" - case eventsURL = "events_url" - case receivedEventsURL = "received_events_url" - case createdAt = "created_at" - case updatedAt = "updated_at" - case numberOfPrivateGists = "private_gists" - case numberOfOwnPrivateRepos = "owned_private_repos" - case twoFactorAuthenticationEnabled = "two_factor_authentication" - } -} - -extension GitHubAccount { - /** - Fetches a user or organization - - parameter session: GitURLSession, defaults to URLSession.shared - - parameter name: The name of the user or organization. - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func user( - _ session: GitURLSession = URLSession.shared, - name: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubUserRouter.readUser(name, configuration) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitHubUser.self - ) { user, error in - if let error { - completion(.failure(error)) - } else { - if let user { - completion(.success(user)) - } - } - } - } - - /** - Fetches the authenticated user - - parameter session: GitURLSession, defaults to URLSession.shared - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func me( - _ session: GitURLSession = URLSession.shared, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubUserRouter.readAuthenticatedUser(configuration) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitHubUser.self - ) { user, error in - if let error { - completion(.failure(error)) - } else { - if let user { - completion(.success(user)) - } - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/PublicKey.swift b/CodeEdit/Features/Git/Accounts/GitHub/PublicKey.swift deleted file mode 100644 index 0194e58a2a..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/PublicKey.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// PublicKey.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -// TODO: DOCS (Nanashi Li) -extension GitHubAccount { - func postPublicKey( - _ session: GitURLSession = URLSession.shared, - publicKey: String, - title: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitHubPublicKeyRouter.postPublicKey(publicKey, title, configuration) - - return router.postJSON( - session, - expectedResultType: [String: AnyObject].self - ) { json, error in - - if let error { - completion(.failure(error)) - } else { - if json != nil { - completion(.success(publicKey)) - } - } - } - } -} - -enum GitHubPublicKeyRouter: GitJSONPostRouter { - case postPublicKey(String, String, GitRouterConfiguration) - - var configuration: GitRouterConfiguration? { - switch self { - case let .postPublicKey(_, _, config): return config - } - } - - var method: GitHTTPMethod { - switch self { - case .postPublicKey: - return .POST - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .postPublicKey: - return .json - } - } - - var path: String { - switch self { - case .postPublicKey: - return "user/keys" - } - } - - var params: [String: Any] { - switch self { - case let .postPublicKey(publicKey, title, _): - return ["title": title, "key": publicKey] - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubGistRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubGistRouter.swift deleted file mode 100644 index 7888284afa..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubGistRouter.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// GitHubGistRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubGistRouter: GitJSONPostRouter { - case readAuthenticatedGists(GitRouterConfiguration, String, String) - case readGists(GitRouterConfiguration, String, String, String) - case readGist(GitRouterConfiguration, String) - case postGistFile(GitRouterConfiguration, String, String, String, Bool) - case patchGistFile(GitRouterConfiguration, String, String, String, String) - - var method: GitHTTPMethod { - switch self { - case .postGistFile, .patchGistFile: - return .POST - default: - return .GET - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .postGistFile, .patchGistFile: - return .json - default: - return .url - } - } - - var configuration: GitRouterConfiguration? { - switch self { - case let .readAuthenticatedGists(config, _, _): return config - case let .readGists(config, _, _, _): return config - case let .readGist(config, _): return config - case let .postGistFile(config, _, _, _, _): return config - case let .patchGistFile(config, _, _, _, _): return config - } - } - - var params: [String: Any] { - switch self { - case let .readAuthenticatedGists(_, page, perPage): - return ["per_page": perPage, "page": page] - case let .readGists(_, _, page, perPage): - return ["per_page": perPage, "page": page] - case .readGist: - return [:] - case let .postGistFile(_, description, filename, fileContent, publicAccess): - var params = [String: Any]() - params["public"] = publicAccess - params["description"] = description - var file = [String: Any]() - file["content"] = fileContent - var files = [String: Any]() - files[filename] = file - params["files"] = files - return params - case let .patchGistFile(_, _, description, filename, fileContent): - var params = [String: Any]() - params["description"] = description - var file = [String: Any]() - file["content"] = fileContent - var files = [String: Any]() - files[filename] = file - params["files"] = files - return params - } - } - - var path: String { - switch self { - case .readAuthenticatedGists: - return "gists" - case let .readGists(_, owner, _, _): - return "users/\(owner)/gists" - case let .readGist(_, id): - return "gists/\(id)" - case .postGistFile: - return "gists" - case let .patchGistFile(_, id, _, _, _): - return "gists/\(id)" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubIssueRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubIssueRouter.swift deleted file mode 100644 index ee0e8897dd..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubIssueRouter.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// GitHubIssueRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitHubIssueRouter: GitJSONPostRouter { - case readAuthenticatedIssues(GitRouterConfiguration, String, String, GitHubOpenness) - case readIssue(GitRouterConfiguration, String, String, Int) - case readIssues(GitRouterConfiguration, String, String, String, String, GitHubOpenness) - case postIssue(GitRouterConfiguration, String, String, String, String?, String?, [String]) - case patchIssue(GitRouterConfiguration, String, String, Int, String?, String?, String?, GitHubOpenness?) - case commentIssue(GitRouterConfiguration, String, String, Int, String) - case readIssueComments(GitRouterConfiguration, String, String, Int, String, String) - case patchIssueComment(GitRouterConfiguration, String, String, Int, String) - - var method: GitHTTPMethod { - switch self { - case .postIssue, .patchIssue, .commentIssue, .patchIssueComment: - return .POST - default: - return .GET - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .postIssue, .patchIssue, .commentIssue, .patchIssueComment: - return .json - default: - return .url - } - } - - var configuration: GitRouterConfiguration? { - switch self { - case let .readAuthenticatedIssues(config, _, _, _): return config - case let .readIssue(config, _, _, _): return config - case let .readIssues(config, _, _, _, _, _): return config - case let .postIssue(config, _, _, _, _, _, _): return config - case let .patchIssue(config, _, _, _, _, _, _, _): return config - case let .commentIssue(config, _, _, _, _): return config - case let .readIssueComments(config, _, _, _, _, _): return config - case let .patchIssueComment(config, _, _, _, _): return config - } - } - - var params: [String: Any] { - switch self { - case let .readAuthenticatedIssues(_, page, perPage, state): - return ["per_page": perPage, "page": page, "state": state.rawValue] - case .readIssue: - return [:] - case let .readIssues(_, _, _, page, perPage, state): - return ["per_page": perPage, "page": page, "state": state.rawValue] - case let .postIssue(_, _, _, title, body, assignee, labels): - var params: [String: Any] = ["title": title] - if let body { - params["body"] = body - } - if let assignee { - params["assignee"] = assignee - } - if !labels.isEmpty { - params["labels"] = labels - } - return params - case let .patchIssue(_, _, _, _, title, body, assignee, state): - var params: [String: String] = [:] - if let title { - params["title"] = title - } - if let body { - params["body"] = body - } - if let assignee { - params["assignee"] = assignee - } - if let state { - params["state"] = state.rawValue - } - return params - case let .commentIssue(_, _, _, _, body): - return ["body": body] - case let .readIssueComments(_, _, _, _, page, perPage): - return ["per_page": perPage, "page": page] - case let .patchIssueComment(_, _, _, _, body): - return ["body": body] - } - } - - var path: String { - switch self { - case .readAuthenticatedIssues: - return "issues" - case let .readIssue(_, owner, repository, number): - return "repos/\(owner)/\(repository)/issues/\(number)" - case let .readIssues(_, owner, repository, _, _, _): - return "repos/\(owner)/\(repository)/issues" - case let .postIssue(_, owner, repository, _, _, _, _): - return "repos/\(owner)/\(repository)/issues" - case let .patchIssue(_, owner, repository, number, _, _, _, _): - return "repos/\(owner)/\(repository)/issues/\(number)" - case let .commentIssue(_, owner, repository, number, _): - return "repos/\(owner)/\(repository)/issues/\(number)/comments" - case let .readIssueComments(_, owner, repository, number, _, _): - return "repos/\(owner)/\(repository)/issues/\(number)/comments" - case let .patchIssueComment(_, owner, repository, number, _): - return "repos/\(owner)/\(repository)/issues/comments/\(number)" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubPullRequestRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubPullRequestRouter.swift deleted file mode 100644 index ff6fe12646..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubPullRequestRouter.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// GitHubPullRequestRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubPullRequestRouter: GitJSONPostRouter { - case readPullRequest(GitRouterConfiguration, String, String, String) - case readPullRequests( - GitRouterConfiguration, String, String, String?, String?, GitHubOpenness, GitSortType, GitSortDirection - ) - - var method: GitHTTPMethod { - switch self { - case .readPullRequest, - .readPullRequests: - return .GET - } - } - - var encoding: GitHTTPEncoding { - switch self { - default: - return .url - } - } - - var configuration: GitRouterConfiguration? { - switch self { - case let .readPullRequest(config, _, _, _): return config - case let .readPullRequests(config, _, _, _, _, _, _, _): return config - } - } - - var params: [String: Any] { - switch self { - case .readPullRequest: - return [:] - case let .readPullRequests(_, _, _, base, head, state, sort, direction): - var parameters = [ - "state": state.rawValue, - "sort": sort.rawValue, - "direction": direction.rawValue - ] - - if let base { - parameters["base"] = base - } - - if let head { - parameters["head"] = head - } - - return parameters - } - } - - var path: String { - switch self { - case let .readPullRequest(_, owner, repository, number): - return "repos/\(owner)/\(repository)/pulls/\(number)" - case let .readPullRequests(_, owner, repository, _, _, _, _, _): - return "repos/\(owner)/\(repository)/pulls" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRepositoryRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRepositoryRouter.swift deleted file mode 100644 index 1d886f01d8..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRepositoryRouter.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// GitHubRepositoryRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubRepositoryRouter: GitRouter { - case readRepositories(GitRouterConfiguration, String, String, String) - case readAuthenticatedRepositories(GitRouterConfiguration, String, String) - case readRepository(GitRouterConfiguration, String, String) - - var configuration: GitRouterConfiguration? { - switch self { - case let .readRepositories(config, _, _, _): return config - case let .readAuthenticatedRepositories(config, _, _): return config - case let .readRepository(config, _, _): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var params: [String: Any] { - switch self { - case let .readRepositories(_, _, page, perPage): - return ["per_page": perPage, "page": page] - case let .readAuthenticatedRepositories(_, page, perPage): - return ["per_page": perPage, "page": page] - case .readRepository: - return [:] - } - } - - var path: String { - switch self { - case let .readRepositories(_, owner, _, _): - return "users/\(owner)/repos" - case .readAuthenticatedRepositories: - return "user/repos" - case let .readRepository(_, owner, name): - return "repos/\(owner)/\(name)" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubReviewsRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubReviewsRouter.swift deleted file mode 100644 index 51e8df6075..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubReviewsRouter.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// GitHubReviewsRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubReviewsRouter: GitJSONPostRouter { - case listReviews(GitRouterConfiguration, String, String, Int) - - var method: GitHTTPMethod { - switch self { - case .listReviews: - return .GET - } - } - - var encoding: GitHTTPEncoding { - switch self { - default: - return .url - } - } - - var configuration: GitRouterConfiguration? { - switch self { - case let .listReviews(config, _, _, _): - return config - } - } - - var params: [String: Any] { - switch self { - case .listReviews: - return [:] - } - } - - var path: String { - switch self { - case let .listReviews(_, owner, repository, pullRequestNumber): - return "repos/\(owner)/\(repository)/pulls/\(pullRequestNumber)/reviews" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRouter.swift deleted file mode 100644 index 4060a5c7d7..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubRouter.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// GitHubRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubRouter: GitJSONPostRouter { - case deleteReference(GitRouterConfiguration, String, String, String) - - var configuration: GitRouterConfiguration? { - switch self { - case let .deleteReference(config, _, _, _): return config - } - } - - var method: GitHTTPMethod { - switch self { - case .deleteReference: - return .DELETE - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .deleteReference: - return .url - } - } - - var params: [String: Any] { - switch self { - case .deleteReference: - return [:] - } - } - - var path: String { - switch self { - case let .deleteReference(_, owner, repo, reference): - return "repos/\(owner)/\(repo)/git/refs/\(reference)" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubUserRouter.swift b/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubUserRouter.swift deleted file mode 100644 index 73bab733dd..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitHub/Routers/GitHubUserRouter.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// GitHubUserRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanshi Li on 2022/03/31. -// - -import Foundation - -enum GitHubUserRouter: GitRouter { - case readAuthenticatedUser(GitRouterConfiguration) - case readUser(String, GitRouterConfiguration) - - var configuration: GitRouterConfiguration? { - switch self { - case let .readAuthenticatedUser(config): return config - case let .readUser(_, config): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var path: String { - switch self { - case .readAuthenticatedUser: - return "user" - case let .readUser(username, _): - return "users/\(username)" - } - } - - var params: [String: Any] { - [:] - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/GitLabAccount.swift b/CodeEdit/Features/Git/Accounts/GitLab/GitLabAccount.swift deleted file mode 100644 index 51772141c8..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/GitLabAccount.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// GitLabAccount.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) - -struct GitLabAccount { - let configuration: GitRouterConfiguration - - init(_ config: GitRouterConfiguration = GitLabTokenConfiguration()) { - configuration = config - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/GitLabConfiguration.swift b/CodeEdit/Features/Git/Accounts/GitLab/GitLabConfiguration.swift deleted file mode 100644 index 756f0a6dac..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/GitLabConfiguration.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// GitLabConfiguration.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -struct GitLabTokenConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.gitlab - var apiEndpoint: String? - var accessToken: String? - let errorDomain: String? = "com.codeedit.models.accounts.gitlab" - - init(_ token: String? = nil, url: String? = nil) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - accessToken = token - } -} - -struct GitLabPrivateTokenConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.gitlab - var apiEndpoint: String? - var accessToken: String? - let errorDomain: String? = "com.codeedit.models.accounts.gitlab" - - init(_ token: String? = nil, url: String? = nil) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - accessToken = token - } - - var accessTokenFieldName: String { - "private_token" - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/GitLabOAuthConfiguration.swift b/CodeEdit/Features/Git/Accounts/GitLab/GitLabOAuthConfiguration.swift deleted file mode 100644 index 8570631013..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/GitLabOAuthConfiguration.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// GitLabOAuthConfiguration.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -struct GitLabOAuthConfiguration: GitRouterConfiguration { - let provider = SourceControlAccount.Provider.gitlab - var apiEndpoint: String? - var accessToken: String? - let token: String - let secret: String - let redirectURI: String - let webEndpoint: String? - let errorDomain = "com.codeedit.models.accounts.gitlab" - - init( - _ url: String? = nil, - webURL: String? = nil, - token: String, - secret: String, - redirectURI: String - ) { - apiEndpoint = url ?? provider.apiURL?.absoluteString - webEndpoint = webURL ?? provider.baseURL?.absoluteString - self.token = token - self.secret = secret - self.redirectURI = redirectURI - } - - func authenticate() -> URL? { - GitLabOAuthRouter.authorize(self, redirectURI).URLRequest?.url - } - - func authorize( - _ session: GitURLSession = URLSession.shared, - code: String, - completion: @escaping (_ config: GitLabTokenConfiguration) -> Void - ) { - let request = GitLabOAuthRouter.accessToken(self, code, redirectURI).URLRequest - if let request { - let task = session.dataTask(with: request) { data, response, _ in - if let response = response as? HTTPURLResponse { - if response.statusCode != 200 { - return - } else { - guard let data else { - return - } - do { - let json = try JSONSerialization.jsonObject( - with: data, - options: .allowFragments - ) as? [String: Any] - if let json, let accessToken = json["access_token"] as? String { - let config = GitLabTokenConfiguration(accessToken, url: self.apiEndpoint ?? "") - completion(config) - } - } catch { - return - } - } - } - } - task.resume() - } - } - - func handleOpenURL( - _ session: GitURLSession = URLSession.shared, - url: URL, - completion: @escaping (_ config: GitLabTokenConfiguration) -> Void - ) { - if let code = url.absoluteString.components(separatedBy: "=").last { - authorize(session, code: code) { (config) in - completion(config) - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAccountModel.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAccountModel.swift deleted file mode 100644 index aeae53cc21..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAccountModel.swift +++ /dev/null @@ -1,345 +0,0 @@ -// -// GitLabAccount.swift -// CodeEditModules/GitAccounts -// -// Created by Wesley de Groot on 02/04/2022. -// - -import Foundation - -extension GitLabAccount { - /** - Fetches the Projects for which the authenticated user is a member. - - parameter page: Current page for project pagination. `1` by default. - - parameter perPage: Number of projects per page. `100` by default. - - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects. - - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `""` - - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, - or `last_activity_at` fields. Default is `created_at`. - - parameter sort: Return projects sorted in asc or desc order. Default is `desc`. - - parameter search: Return list of authorized projects matching the search criteria. Default is `""` - - parameter simple: Return only the ID, URL, name, and path of each project. Default is false, - set to `true` to only show simple info. - - parameter completion: Callback for the outcome of the fetch. - */ - func projects( - _ session: GitURLSession = URLSession.shared, - page: String = "1", - perPage: String = "20", - archived: Bool = false, - visibility: GitLabVisibility = GitLabVisibility.all, - orderBy: GitLabOrderBy = GitLabOrderBy.creationDate, - sort: GitLabSort = GitLabSort.descending, - search: String = "", - simple: Bool = false, - completion: @escaping (_ response: Result<[GitLabProject], Error>) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readAuthenticatedProjects( - configuration: configuration, - page: page, - perPage: perPage, - archived: archived, - visibility: visibility, - orderBy: orderBy, - sort: sort, - search: search, - simple: simple - ) - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProject.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success([json])) - } - } - } - - /** - Fetches project for a specified ID. - - parameter id: The ID or namespace/project-name of the project. - Make sure that the namespace/project-name is URL-encoded, eg. "%2F" for "/". - - parameter completion: Callback for the outcome of the fetch. - */ - func project( - _ session: GitURLSession = URLSession.shared, - id: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readSingleProject(configuration: configuration, id: id) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProject.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Fetches the Projects which the authenticated user can see. - - parameter page: Current page for project pagination. `1` by default. - - parameter perPage: Number of projects per page. `100` by default. - - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects. - - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `""` - - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, - or `last_activity_at` fields. Default is `created_at`. - - parameter sort: Return projects sorted in asc or desc order. Default is `desc`. - - parameter search: Return list of authorized projects matching the search criteria. Default is `""` - - parameter simple: Return only the ID, URL, name, and path of each project. Default is false, - set to `true` to only show simple info. - - parameter completion: Callback for the outcome of the fetch. - */ - func visibleProjects( - _ session: GitURLSession = URLSession.shared, - page: String = "1", - perPage: String = "20", - archived: Bool = false, - visibility: GitLabVisibility = GitLabVisibility.all, - orderBy: GitLabOrderBy = GitLabOrderBy.creationDate, - sort: GitLabSort = GitLabSort.descending, - search: String = "", - simple: Bool = false, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readVisibleProjects( - configuration: configuration, - page: page, - perPage: perPage, - archived: archived, - visibility: visibility, - orderBy: orderBy, - sort: sort, - search: search, - simple: simple - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProject.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Fetches the Projects which are owned by the authenticated user. - - parameter page: Current page for project pagination. `1` by default. - - parameter perPage: Number of projects per page. `100` by default. - - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects. - - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `""` - - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, - or `last_activity_at` fields. Default is `created_at`. - - parameter sort: Return projects sorted in asc or desc order. Default is `desc`. - - parameter search: Return list of authorized projects matching the search criteria. Default is `""` - - parameter simple: Return only the ID, URL, name, and path of each project. Default is false, - set to `true` to only show simple info. - - parameter completion: Callback for the outcome of the fetch. - */ - func ownedProjects( - _ session: GitURLSession = URLSession.shared, - page: String = "1", - perPage: String = "20", - archived: Bool = false, - visibility: GitLabVisibility = GitLabVisibility.all, - orderBy: GitLabOrderBy = GitLabOrderBy.creationDate, - sort: GitLabSort = GitLabSort.descending, - search: String = "", - simple: Bool = false, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readOwnedProjects( - configuration: configuration, - page: page, - perPage: perPage, - archived: archived, - visibility: visibility, - orderBy: orderBy, - sort: sort, - search: search, - simple: simple - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProject.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Fetches the Projects which are starred by the authenticated user. - - parameter page: Current page for project pagination. `1` by default. - - parameter perPage: Number of projects per page. `100` by default. - - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects. - - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `""` - - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, - or `last_activity_at` fields. Default is `created_at`. - - parameter sort: Return projects sorted in asc or desc order. Default is `desc`. - - parameter search: Return list of authorized projects matching the search criteria. Default is `""` - - parameter simple: Return only the ID, URL, name, and path of each project. - Default is false, set to `true` to only show simple info. - - parameter completion: Callback for the outcome of the fetch. - */ - func starredProjects( - _ session: GitURLSession = URLSession.shared, - page: String = "1", - perPage: String = "20", - archived: Bool = false, - visibility: GitLabVisibility = GitLabVisibility.all, - orderBy: GitLabOrderBy = GitLabOrderBy.creationDate, - sort: GitLabSort = GitLabSort.descending, - search: String = "", - simple: Bool = false, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readStarredProjects( - configuration: configuration, - page: page, - perPage: perPage, - archived: archived, - visibility: visibility, - orderBy: orderBy, - sort: sort, - search: search, - simple: simple - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProject.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Fetches all GitLab projects in the server **(admin only)**. - - parameter page: Current page for project pagination. `1` by default. - - parameter perPage: Number of projects per page. `100` by default. - - parameter archived: Limit by archived status. Default is false, set to `true` to only show archived projects. - - parameter visibility: Limit by visibility `public`, `internal` or `private`. Default is `""` - - parameter orderBy: Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, - or `last_activity_at` fields. Default is `created_at`. - - parameter sort: Return projects sorted in asc or desc order. Default is `desc`. - - parameter search: Return list of authorized projects matching the search criteria. Default is `""` - - parameter simple: Return only the ID, URL, name, and path of each project. - Default is false, set to `true` to only show simple info. - - parameter completion: Callback for the outcome of the fetch. - */ - func allProjects( - _ session: GitURLSession = URLSession.shared, - page: String = "1", - perPage: String = "20", - archived: Bool = false, - visibility: GitLabVisibility = GitLabVisibility.all, - orderBy: GitLabOrderBy = GitLabOrderBy.creationDate, - sort: GitLabSort = GitLabSort.descending, - search: String = "", - simple: Bool = false, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readAllProjects( - configuration: configuration, - page: page, - perPage: perPage, - archived: archived, - visibility: visibility, - orderBy: orderBy, - sort: sort, - search: search, - simple: simple - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProject.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Fetches the events for the specified project. Sorted from newest to oldest. - - parameter page: Current page for project pagination. `1` by default. - - parameter perPage: Number of projects per page. `100` by default. - - parameter id: The ID or NAMESPACE/PROJECT_NAME of the project. - - parameter completion: Callback for the outcome of the fetch. - */ - func projectEvents( - _ session: GitURLSession = URLSession.shared, - id: String, - page: String = "1", - perPage: String = "20", - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readProjectEvents( - configuration: configuration, - id: id, - page: page, - perPage: perPage - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabEvent.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAvatarURL.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAvatarURL.swift deleted file mode 100644 index bd9b86459f..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabAvatarURL.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// GitLabAvatarURL.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabAvatarURL: Codable { - var url: URL? - - init(_ json: [String: AnyObject]) { - if let urlString = json["url"] as? String, let urlFromString = URL(string: urlString) { - url = urlFromString - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabCommit.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabCommit.swift deleted file mode 100644 index c3555b1ff9..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabCommit.swift +++ /dev/null @@ -1,294 +0,0 @@ -// -// GitLabCommit.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabCommit: Codable { - var id: String - var shortID: String? - var title: String? - var authorName: String? - var authorEmail: String? - var committerName: String? - var committerEmail: String? - var createdAt: Date? - var message: String? - var committedDate: Date? - var authoredDate: Date? - var parentIDs: [String]? - var stats: GitLabCommitStats? - var status: String? - - enum CodingKeys: String, CodingKey { - case id - case shortID = "short_id" - case title - case authorName = "author_name" - case authorEmail = "author_email" - case committerName = "committer_name" - case committerEmail = "committer_email" - case createdAt = "created_at" - case message - case committedDate = "committed_date" - case authoredDate = "authored_date" - case parentIDs = "parent_ids" - case stats - case status - } -} - -class GitLabCommitStats: Codable { - var additions: Int? - var deletions: Int? - var total: Int? - - enum CodingKeys: String, CodingKey { - case additions - case deletions - case total - } -} - -class GitLabCommitDiff: Codable { - var diff: String? - var newPath: String? - var oldPath: String? - var aMode: String? - var bMode: String? - var newFile: Bool? - var renamedFile: Bool? - var deletedFile: Bool? - - enum CodingKeys: String, CodingKey { - case diff - case newPath = "new_path" - case oldPath = "old_path" - case aMode = "a_mode" - case bMode = "b_mode" - case newFile = "new_file" - case renamedFile = "renamed_file" - case deletedFile = "deleted_file" - } -} - -class GitLabCommitComment: Codable { - var note: String? - var author: GitLabUser? - - enum CodingKeys: String, CodingKey { - case note - case author - } -} - -class GitLabCommitStatus: Codable { - var status: String? - var createdAt: Date? - var startedAt: Date? - var name: String? - var allowFailure: Bool? - var author: GitLabUser? - var statusDescription: String? - var sha: String? - var targetURL: URL? - var finishedAt: Date? - var id: Int? - var ref: String? - - enum CodingKeys: String, CodingKey { - case status - case createdAt = "created_at" - case startedAt = "started_at" - case name - case allowFailure = "allow_failure" - case author - case statusDescription = "description" - case sha - case targetURL = "target_url" - case finishedAt = "finished_at" - case id - case ref - } -} - -extension GitLabAccount { - - /** - Get a list of repository commits in a project. - - parameter id: The ID of a project or namespace/project name owned by the authenticated user. - - parameter refName: The name of a repository branch or tag or if not given the default branch. - - parameter since: Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ. - - parameter until: Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ. - - parameter completion: Callback for the outcome of the fetch. - */ - func commits( - _ session: GitURLSession = URLSession.shared, - id: String, - refName: String = "", - since: String = "", - until: String = "", - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabCommitRouter.readCommits( - self.configuration, - id: id, - refName: refName, - since: since, - until: until - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabCommit.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Get a specific commit in a project. - - parameter id: The ID of a project or namespace/project name owned by the authenticated user. - - parameter sha: The commit hash or name of a repository branch or tag. - - parameter completion: Callback for the outcome of the fetch. - */ - func commit( - _ session: GitURLSession = URLSession.shared, - id: String, - sha: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabCommitRouter.readCommit(self.configuration, id: id, sha: sha) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabCommit.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Get a diff of a commit in a project. - - parameter id: The ID of a project or namespace/project name owned by the authenticated user. - - parameter sha: The commit hash or name of a repository branch or tag. - - parameter completion: Callback for the outcome of the fetch. - */ - func commitDiffs( - _ session: GitURLSession = URLSession.shared, - id: String, - sha: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabCommitRouter.readCommitDiffs(self.configuration, id: id, sha: sha) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabCommitDiff.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Get the comments of a commit in a project. - - parameter id: The ID of a project or namespace/project name owned by the authenticated user. - - parameter sha: The commit hash or name of a repository branch or tag. - - parameter completion: Callback for the outcome of the fetch. - */ - func commitComments( - _ session: GitURLSession = URLSession.shared, - id: String, - sha: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabCommitRouter.readCommitComments(self.configuration, id: id, sha: sha) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabCommitComment.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Get the statuses of a commit in a project. - - parameter id: The ID of a project or namespace/project name owned by the authenticated user. - - parameter sha: The commit hash or name of a repository branch or tag. - - parameter ref: The name of a repository branch or tag or, if not given, the default branch. - - parameter stage: Filter by build stage, e.g. `test`. - - parameter name: Filter by job name, e.g. `bundler:audit`. - - parameter all: Return all statuses, not only the latest ones. (Boolean value) - - parameter completion: Callback for the outcome of the fetch. - */ - func commitStatuses( - _ session: GitURLSession = URLSession.shared, - id: String, - sha: String, - ref: String = "", - stage: String = "", - name: String = "", - all: Bool = false, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabCommitRouter.readCommitStatuses( - self.configuration, id: id, - sha: sha, - ref: ref, - stage: stage, - name: name, - all: all - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabCommitStatus.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEvent.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEvent.swift deleted file mode 100644 index d6119aabf3..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEvent.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// GitLabEvent.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabEvent: Codable { - var title: String? - var projectID: Int? - var actionName: String? - var targetID: Int? - var targetType: String? - var authorID: Int? - var data: GitLabEventData? - var targetTitle: String? - var author: GitLabUser? - var authorUsername: String? - var createdAt: Date? - var note: GitLabEventNote? - - enum CodingKeys: String, CodingKey { - case title - case projectID = "project_id" - case actionName = "action_name" - case targetID = "target_id" - case targetType = "target_type" - case authorID = "author_id" - case data - case targetTitle = "target_title" - case author - case authorUsername = "author_username" - case createdAt = "created_at" - case note - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventData.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventData.swift deleted file mode 100644 index 9fad352c8d..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventData.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// GitLabEventData.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabEventData: Codable { - var objectKind: String? - var eventName: String? - var before: String? - var after: String? - var ref: String? - var checkoutSha: String? - var message: String? - var userID: Int? - var userName: String? - var userEmail: String? - var userAvatar: URL? - var projectID: Int? - var project: GitLabProject? - var commits: [GitLabCommit]? - var totalCommitsCount: Int? - - enum CodingKeys: String, CodingKey { - case objectKind = "object_kind" - case eventName = "event_name" - case before - case after - case ref - case checkoutSha = "checkout_sha" - case message - case userID = "user_id" - case userName = "user_name" - case userEmail = "user_email" - case userAvatar = "user_avater" - case projectID = "project_id" - case project - case commits - case totalCommitsCount = "total_commits_count" - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventNote.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventNote.swift deleted file mode 100644 index e9d1596701..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabEventNote.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// GitLabEventNote.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabEventNote: Codable { - var id: Int? - var body: String? - var attachment: String? - var author: GitLabUser? - var createdAt: Date? - var system: Bool? - var upvote: Bool? - var downvote: Bool? - var notableID: Int? - var notableType: String? - - init(_ json: [String: AnyObject]) { - id = json["id"] as? Int - body = json["body"] as? String - attachment = json["attachment"] as? String - author = GitLabUser(json["author"] as? [String: AnyObject] ?? [:]) - createdAt = GitTime.rfc3339Date(json["created_at"] as? String) - system = json["system"] as? Bool - upvote = json["upvote"] as? Bool - downvote = json["downvote"] as? Bool - notableID = json["notable_id"] as? Int - notableType = json["notable_type"] as? String - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabGroupAccess.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabGroupAccess.swift deleted file mode 100644 index 9a00addaf9..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabGroupAccess.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// GitLabGroupAccess.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabGroupAccess: Codable { - var accessLevel: Int? - var notificationLevel: Int? - - init(_ json: [String: AnyObject]) { - accessLevel = json["access_level"] as? Int - notificationLevel = json["notification_level"] as? Int - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabNamespace.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabNamespace.swift deleted file mode 100644 index 43b293bad9..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabNamespace.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// GitLabNamespace.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabNamespace: Codable { - var id: Int? - var name: String? - var path: String? - var ownerID: Int? - var createdAt: Date? - var updatedAt: Date? - var namespaceDescription: String? - var avatar: GitLabAvatarURL? - var shareWithGroupLocked: Bool? - var visibilityLevel: Int? - var requestAccessEnabled: Bool? - var deletedAt: Date? - var lfsEnabled: Bool? - - init(_ json: [String: AnyObject]) { - if let id = json["id"] as? Int { - self.id = id - name = json["name"] as? String - path = json["path"] as? String - ownerID = json["owner_id"] as? Int - createdAt = GitTime.rfc3339Date(json["created_at"] as? String) - updatedAt = GitTime.rfc3339Date(json["updated_at"] as? String) - namespaceDescription = json["description"] as? String - avatar = GitLabAvatarURL(json["avatar"] as? [String: AnyObject] ?? [:]) - shareWithGroupLocked = json["share_with_group_lock"] as? Bool - visibilityLevel = json["visibility_level"] as? Int - requestAccessEnabled = json["request_access_enabled"] as? Bool - deletedAt = GitTime.rfc3339Date(json["deleted_at"] as? String) - lfsEnabled = json["lfs_enabled"] as? Bool - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabPermissions.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabPermissions.swift deleted file mode 100644 index 9d16864b97..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabPermissions.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Permissions.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabPermissions: Codable { - var projectAccess: GitLabProjectAccess? - var groupAccess: GitLabGroupAccess? - - init(_ json: [String: AnyObject]) { - projectAccess = GitLabProjectAccess(json["project_access"] as? [String: AnyObject] ?? [:]) - groupAccess = GitLabGroupAccess(json["group_access"] as? [String: AnyObject] ?? [:]) - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProject.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProject.swift deleted file mode 100644 index 2112201a4e..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProject.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// GitLabProject.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitLabVisibilityLevel: Int { - case `private` = 0 - case `internal` = 10 - case `public` = 20 -} - -class GitLabProject: Codable { - let id: Int - let owner: GitLabUser - var name: String? - var nameWithNamespace: String? - var isPrivate: Bool? - var projectDescription: String? - var sshURL: URL? - var cloneURL: URL? - var webURL: URL? - var path: String? - var pathWithNamespace: String? - var containerRegistryEnabled: Bool? - var defaultBranch: String? - var tagList: [String]? - var isArchived: Bool? - var issuesEnabled: Bool? - var mergeRequestsEnabled: Bool? - var wikiEnabled: Bool? - var buildsEnabled: Bool? - var snippetsEnabled: Bool? - var sharedRunnersEnabled: Bool? - var creatorID: Int? - var namespace: GitLabNamespace? - var avatarURL: URL? - var starCount: Int? - var forksCount: Int? - var openIssuesCount: Int? - var runnersToken: String? - var publicBuilds: Bool? - var createdAt: Date? - var lastActivityAt: Date? - var lfsEnabled: Bool? - var visibilityLevel: String? - var onlyAllowMergeIfBuildSucceeds: Bool? - var requestAccessEnabled: Bool? - var permissions: String? - - enum CodingKeys: String, CodingKey { - case id - case owner - case name - case nameWithNamespace = "name_with_namespace" - case isPrivate = "public" - case projectDescription = "description" - case sshURL = "ssh_url_to_repo" - case cloneURL = "http_url_to_repo" - case webURL = "web_url" - case path - case pathWithNamespace = "path_with_namespace" - case containerRegistryEnabled = "container_registry_enabled" - case defaultBranch = "default_branch" - case tagList = "tag_list" - case isArchived = "archived" - case issuesEnabled = "issues_enabled" - case mergeRequestsEnabled = "merge_requests_enabled" - case wikiEnabled = "wiki_enabled" - case buildsEnabled = "builds_enabled" - case snippetsEnabled = "snippets_enabled" - case sharedRunnersEnabled = "shared_runners_enabled" - case publicBuilds = "public_builds" - case creatorID = "creator_id" - case namespace - case avatarURL = "avatar_url" - case starCount = "star_count" - case forksCount = "forks_count" - case openIssuesCount = "open_issues_count" - case visibilityLevel = "visibility_level" - case createdAt = "created_at" - case lastActivityAt = "last_activity_at" - case lfsEnabled = "lfs_enabled" - case runnersToken = "runners_token" - case onlyAllowMergeIfBuildSucceeds = "only_allow_merge_if_build_succeeds" - case requestAccessEnabled = "request_access_enabled" - case permissions = "permissions" - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectAccess.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectAccess.swift deleted file mode 100644 index 68ff693c5e..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectAccess.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// GitLabProjectAccess.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabProjectAccess: Codable { - var accessLevel: Int? - var notificationLevel: Int? - - init(_ json: [String: AnyObject]) { - accessLevel = json["access_level"] as? Int - notificationLevel = json["notification_level"] as? Int - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectHook.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectHook.swift deleted file mode 100644 index 3c9b709790..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabProjectHook.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// GitLabProjectHook.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabProjectHook: Codable { - var id: Int? - var url: URL? - var projectID: Int? - var pushEvents: Bool? - var issuesEvents: Bool? - var mergeRequestsEvents: Bool? - var tagPushEvents: Bool? - var noteEvents: Bool? - var buildEvents: Bool? - var pipelineEvents: Bool? - var wikiPageEvents: Bool? - var enableSSLVerification: Bool? - var createdAt: Date? - - enum CodingKeys: String, CodingKey { - case id - case url - case projectID = "project_id" - case pushEvents = "push_events" - case issuesEvents = "issues_events" - case mergeRequestsEvents = "merge_requests_events" - case tagPushEvents = "tag_push_events" - case noteEvents = "note_events" - case buildEvents = "build_events" - case pipelineEvents = "pipeline_events" - case wikiPageEvents = "wiki_page_events" - case enableSSLVerification = "enable_ssl_verification" - case createdAt = "created_at" - } -} - -extension GitLabAccount { - - /** - Get a list of project hooks. - - parameter id: The ID of the project or namespace/project name. - Make sure that the namespace/project-name is URL-encoded, eg. "%2F" for "/". - - parameter completion: Callback for the outcome of the fetch. - */ - func projectHooks( - _ session: GitURLSession = URLSession.shared, - id: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readProjectHooks(configuration: configuration, id: id) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProjectHook.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } - - /** - Get a specific hook from a project. - - parameter id: The ID of the project or namespace/project name. - Make sure that the namespace/project-name is URL-encoded, eg. "%2F" for "/". - - parameter hookId: The ID of the hook in the project - (you can get the ID of a hook by searching for it with the **allProjectHooks** request). - - parameter completion: Callback for the outcome of the fetch. - */ - func projectHook( - _ session: GitURLSession = URLSession.shared, - id: String, - hookId: String, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabProjectRouter.readProjectHook( - configuration: configuration, - id: id, - hookId: hookId - ) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabProjectHook.self - ) { json, error in - - if let error { - completion(Result.failure(error)) - } - - if let json { - completion(Result.success(json)) - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabUser.swift b/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabUser.swift deleted file mode 100644 index c58d0299eb..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Model/GitLabUser.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// GitLabUser.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -class GitLabUser: Codable { - var id: Int - var username: String? - var state: String? - var avatarURL: URL? - var webURL: URL? - var createdAt: Date? - var isAdmin: Bool? - var name: String? - var lastSignInAt: Date? - var confirmedAt: Date? - var email: String? - var projectsLimit: Int? - var currentSignInAt: Date? - var canCreateGroup: Bool? - var canCreateProject: Bool? - var twoFactorEnabled: Bool? - var external: Bool? - - init(_ json: [String: Any]) { - if let id = json["id"] as? Int { - name = json["name"] as? String - username = json["username"] as? String - self.id = id - state = json["state"] as? String - if let urlString = json["avatar_url"] as? String, let url = URL(string: urlString) { - avatarURL = url - } - if let urlString = json["web_url"] as? String, let url = URL(string: urlString) { - webURL = url - } - createdAt = GitTime.rfc3339Date(json["created_at"] as? String) - isAdmin = json["is_admin"] as? Bool - lastSignInAt = GitTime.rfc3339Date(json["last_sign_in_at"] as? String) - confirmedAt = GitTime.rfc3339Date(json["confirmed_at"] as? String) - email = json["email"] as? String - projectsLimit = json["projects_limit"] as? Int - currentSignInAt = GitTime.rfc3339Date(json["current_sign_in_at"] as? String) - canCreateGroup = json["can_create_group"] as? Bool - canCreateProject = json["can_create_project"] as? Bool - twoFactorEnabled = json["two_factor_enabled"] as? Bool - external = json["external"] as? Bool - } else { - id = -1 - } - } -} - -extension GitLabAccount { - - /** - Fetches the currently logged in user - - parameter completion: Callback for the outcome of the fetch. - */ - @discardableResult - func me( - _ session: GitURLSession = URLSession.shared, - completion: @escaping (_ response: Result) -> Void - ) -> GitURLSessionDataTaskProtocol? { - let router = GitLabUserRouter.readAuthenticatedUser(self.configuration) - - return router.load( - session, - dateDecodingStrategy: .formatted(GitTime.rfc3339DateFormatter), - expectedResultType: GitLabUser.self - ) { data, error in - - if let error { - completion(Result.failure(error)) - } - - if let data { - completion(Result.success(data)) - } - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabCommitRouter.swift b/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabCommitRouter.swift deleted file mode 100644 index fc9d2a4c7e..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabCommitRouter.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// GitLabCommitRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitLabCommitRouter: GitRouter { - case readCommits(GitRouterConfiguration, id: String, refName: String, since: String, until: String) - case readCommit(GitRouterConfiguration, id: String, sha: String) - case readCommitDiffs(GitRouterConfiguration, id: String, sha: String) - case readCommitComments(GitRouterConfiguration, id: String, sha: String) - case readCommitStatuses( - GitRouterConfiguration, - id: String, - sha: String, - ref: String, - stage: String, - name: String, - all: Bool - ) - - var configuration: GitRouterConfiguration? { - switch self { - case let .readCommits(config, _, _, _, _): return config - case let .readCommit(config, _, _): return config - case let .readCommitDiffs(config, _, _): return config - case let .readCommitComments(config, _, _): return config - case let .readCommitStatuses(config, _, _, _, _, _, _): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var params: [String: Any] { - switch self { - case let .readCommits(_, _, refName, since, until): - return ["ref_name": refName, "since": since, "until": until] - case .readCommit: - return [:] - case .readCommitDiffs: - return [:] - case .readCommitComments: - return [:] - case let .readCommitStatuses(_, _, _, ref, stage, name, all): - return ["ref": ref, "stage": stage, "name": name, "all": String(all)] - } - } - - var path: String { - switch self { - case let .readCommits(_, id, _, _, _): - return "project/\(id)/repository/commits" - case let .readCommit(_, id, sha): - return "project/\(id)/repository/commits/\(sha)" - case let .readCommitDiffs(_, id, sha): - return "project/\(id)/repository/commits/\(sha)/diff" - case let .readCommitComments(_, id, sha): - return "project/\(id)/repository/commits/\(sha)/comments" - case let .readCommitStatuses(_, id, sha, _, _, _, _): - return "project/\(id)/repository/commits/\(sha)/statuses" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabOAuthRouter.swift b/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabOAuthRouter.swift deleted file mode 100644 index 332251192f..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabOAuthRouter.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// GitLabOAuthRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitLabOAuthRouter: GitRouter { - case authorize(GitLabOAuthConfiguration, String) - case accessToken(GitLabOAuthConfiguration, String, String) - - var configuration: GitRouterConfiguration? { - switch self { - case .authorize(let config, _): return config - case .accessToken(let config, _, _): return config - } - } - - var method: GitHTTPMethod { - switch self { - case .authorize: - return .GET - case .accessToken: - return .POST - } - } - - var encoding: GitHTTPEncoding { - switch self { - case .authorize: - return .url - case .accessToken: - return .form - } - } - - var path: String { - switch self { - case .authorize: - return "oauth/authorize" - case .accessToken: - return "oauth/token" - } - } - - var params: [String: Any] { - switch self { - case let .authorize(config, redirectURI): - return [ - "client_id": config.token as AnyObject, - "response_type": "code" as AnyObject, - "redirect_uri": redirectURI as AnyObject] - case let .accessToken(config, code, rediredtURI): - return [ - "client_id": config.token as AnyObject, - "client_secret": config.secret as AnyObject, - "code": code as AnyObject, "grant_type": - "authorization_code" as AnyObject, - "redirect_uri": rediredtURI as AnyObject] - } - } - - var URLRequest: Foundation.URLRequest? { - switch self { - case .authorize(let config, _): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - case .accessToken(let config, _, _): - let url = URL(string: path, relativeTo: URL(string: config.webEndpoint!)!) - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - return request(components!, parameters: params) - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabProjectRouter.swift b/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabProjectRouter.swift deleted file mode 100644 index c449c6d399..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabProjectRouter.swift +++ /dev/null @@ -1,249 +0,0 @@ -// -// GitLabProjectRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitLabVisibility: String { - case visbilityPublic = "public" - case visibilityInternal = "interal" - case visibilityPrivate = "private" - case all = "" -} - -enum GitLabOrderBy: String { - case id = "id" - case name = "name" - case path = "path" - case creationDate = "created_at" - case updateDate = "updated_at" - case lastActvityDate = "last_activity_at" -} - -enum GitLabSort: String { - case ascending = "asc" - case descending = "desc" -} - -enum GitLabProjectRouter: GitRouter { - case readAuthenticatedProjects( - configuration: GitRouterConfiguration, - page: String, - perPage: String, - archived: Bool, - visibility: GitLabVisibility, - orderBy: GitLabOrderBy, - sort: GitLabSort, - search: String, - simple: Bool) - case readVisibleProjects( - configuration: GitRouterConfiguration, - page: String, - perPage: String, - archived: Bool, - visibility: GitLabVisibility, - orderBy: GitLabOrderBy, - sort: GitLabSort, - search: String, - simple: Bool) - case readOwnedProjects( - configuration: GitRouterConfiguration, - page: String, - perPage: String, - archived: Bool, - visibility: GitLabVisibility, - orderBy: GitLabOrderBy, - sort: GitLabSort, - search: String, - simple: Bool) - case readStarredProjects( - configuration: GitRouterConfiguration, - page: String, - perPage: String, - archived: Bool, - visibility: GitLabVisibility, - orderBy: GitLabOrderBy, - sort: GitLabSort, - search: String, - simple: Bool) - case readAllProjects( - configuration: GitRouterConfiguration, - page: String, - perPage: String, - archived: Bool, - visibility: GitLabVisibility, - orderBy: GitLabOrderBy, - sort: GitLabSort, - search: String, - simple: Bool) - case readSingleProject(configuration: GitRouterConfiguration, id: String) - case readProjectEvents(configuration: GitRouterConfiguration, id: String, page: String, perPage: String) - case readProjectHooks(configuration: GitRouterConfiguration, id: String) - case readProjectHook(configuration: GitRouterConfiguration, id: String, hookId: String) - - var configuration: GitRouterConfiguration? { - switch self { - case .readAuthenticatedProjects(let config, _, _, _, _, _, _, _, _): return config - case .readVisibleProjects(let config, _, _, _, _, _, _, _, _): return config - case .readOwnedProjects(let config, _, _, _, _, _, _, _, _): return config - case .readStarredProjects(let config, _, _, _, _, _, _, _, _): return config - case .readAllProjects(let config, _, _, _, _, _, _, _, _): return config - case .readSingleProject(let config, _): return config - case .readProjectEvents(let config, _, _, _): return config - case .readProjectHooks(let config, _): return config - case .readProjectHook(let config, _, _): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var params: [String: Any] { - switch self { - case let .readAuthenticatedProjects( - _, - page, - perPage, - archived, - visibility, - orderBy, - sort, - search, - simple - ): - return [ - "page": page, - "per_page": perPage, - "archived": String(archived), - "visibility": visibility, - "order_by": orderBy, - "sort": sort, - "search": search, - "simple": String(simple) - ] - case let .readVisibleProjects( - _, - page, - perPage, - archived, - visibility, - orderBy, - sort, - search, - simple - ): - return [ - "page": page, - "per_page": perPage, - "archived": String(archived), - "visibility": visibility, - "order_by": orderBy, - "sort": sort, - "search": search, - "simple": String(simple) - ] - case let .readOwnedProjects( - _, - page, - perPage, - archived, - visibility, - orderBy, - sort, - search, - simple - ): - return [ - "page": page, - "per_page": perPage, - "archived": String(archived), - "visibility": visibility, - "order_by": orderBy, - "sort": sort, - "search": search, - "simple": String(simple) - ] - case let .readStarredProjects( - _, - page, - perPage, - archived, - visibility, - orderBy, - sort, - search, - simple - ): - return [ - "page": page, - "per_page": perPage, - "archived": String(archived), - "visibility": visibility, - "order_by": orderBy, - "sort": sort, - "search": search, - "simple": String(simple) - ] - case let .readAllProjects( - _, - page, - perPage, - archived, - visibility, - orderBy, - sort, - search, - simple - ): - return [ - "page": page, - "per_page": perPage, - "archived": String(archived), - "visibility": visibility, - "order_by": orderBy, - "sort": sort, - "search": search, - "simple": String(simple) - ] - case .readSingleProject: - return [:] - case let .readProjectEvents(_, _, page, perPage): - return ["per_page": perPage, "page": page] - case .readProjectHooks: - return [:] - case .readProjectHook: - return [:] - } - } - - var path: String { - switch self { - case .readAuthenticatedProjects: - return "projects" - case .readVisibleProjects: - return "projects/visible" - case .readOwnedProjects: - return "projects/owned" - case .readStarredProjects: - return "projects/starred" - case .readAllProjects: - return "projects/all" - case .readSingleProject(_, let id): - return "projects/\(id)" - case .readProjectEvents(_, let id, _, _): - return "projects/\(id)/events" - case .readProjectHooks(_, let id): - return "projects/\(id)/hooks" - case let .readProjectHook(_, id, hookId): - return "projects/\(id)/hooks/\(hookId)" - } - } -} diff --git a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabUserRouter.swift b/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabUserRouter.swift deleted file mode 100644 index ed7b7d60e9..0000000000 --- a/CodeEdit/Features/Git/Accounts/GitLab/Routers/GitLabUserRouter.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// GitLabUserRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitLabUserRouter: GitRouter { - case readAuthenticatedUser(GitRouterConfiguration) - - var configuration: GitRouterConfiguration? { - switch self { - case .readAuthenticatedUser(let config): return config - } - } - - var method: GitHTTPMethod { - .GET - } - - var encoding: GitHTTPEncoding { - .url - } - - var path: String { - switch self { - case .readAuthenticatedUser: - return "user" - } - } - - var params: [String: Any] { - [:] - } -} diff --git a/CodeEdit/Features/Git/Accounts/Networking/GitJSONPostRouter.swift b/CodeEdit/Features/Git/Accounts/Networking/GitJSONPostRouter.swift deleted file mode 100644 index 2904982c29..0000000000 --- a/CodeEdit/Features/Git/Accounts/Networking/GitJSONPostRouter.swift +++ /dev/null @@ -1,233 +0,0 @@ -// -// GitJSONPostRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// -// This file should be strictly just be used for Accounts since it's not -// built for any other networking except those of git accounts - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -// TODO: DOCS (Nanashi Li) -protocol GitJSONPostRouter: GitRouter { - - func postJSON( - _ session: GitURLSession, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? - - func post( - _ session: GitURLSession, - decoder: JSONDecoder, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? - - #if !canImport(FoundationNetworking) - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func postJSON(_ session: GitURLSession, expectedResultType: T.Type) async throws -> T? - - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func post( - _ session: GitURLSession, - decoder: JSONDecoder, - expectedResultType: T.Type - ) async throws -> T - #endif -} - -extension GitJSONPostRouter { - func postJSON( - _ session: GitURLSession = URLSession.shared, - expectedResultType _: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - guard let request = request() else { - return nil - } - - let data: Data - - do { - data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions()) - } catch { - completion(nil, error) - return nil - } - - let task = session.uploadTask(with: request, fromData: data) { data, response, error in - if let response = response as? HTTPURLResponse { - if !response.wasSuccessful { - var userInfo = [String: Any]() - if let data, let json = try? JSONSerialization.jsonObject( - with: data, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - - } else if let data, - let string = String(data: data, encoding: .utf8) { - userInfo[gitErrorKey] = string as Any? - } - - let error = NSError( - domain: self.configuration?.errorDomain ?? "", - code: response.statusCode, - userInfo: userInfo - ) - - completion(nil, error) - return - } - } - - if let error { - completion(nil, error) - } else { - if let data { - do { - let JSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? T - completion(JSON, nil) - } catch { - completion(nil, error) - } - } - } - } - task.resume() - return task - } - - #if !canImport(FoundationNetworking) - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func postJSON( - _ session: GitURLSession = URLSession.shared, - expectedResultType _: T.Type - ) async throws -> T? { - - guard let request = request() else { - throw NSError(domain: configuration?.errorDomain ?? "", code: -876, userInfo: nil) - } - - let data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions()) - let responseTuple = try await session.upload(for: request, from: data, delegate: nil) - if let response = responseTuple.1 as? HTTPURLResponse { - if !response.wasSuccessful { - var userInfo = [String: Any]() - if let json = try? JSONSerialization.jsonObject( - with: responseTuple.0, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - - } else if let string = String(data: responseTuple.0, encoding: String.Encoding.utf8) { - userInfo[gitErrorKey] = string as Any? - } - throw NSError(domain: configuration?.errorDomain ?? "", code: response.statusCode, userInfo: userInfo) - } - } - - return try JSONSerialization.jsonObject(with: responseTuple.0, options: .mutableContainers) as? T - } - #endif - - func post( - _ session: GitURLSession = URLSession.shared, - decoder: JSONDecoder = JSONDecoder(), - expectedResultType _: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - guard let request = request() else { - return nil - } - - let data: Data - do { - data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions()) - } catch { - completion(nil, error) - return nil - } - - let task = session.uploadTask(with: request, fromData: data) { data, response, error in - if let response = response as? HTTPURLResponse, !response.wasSuccessful { - var userInfo = [String: Any]() - if let data, let json = try? JSONSerialization.jsonObject( - with: data, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - - } else if let data, let string = String(data: data, encoding: String.Encoding.utf8) { - userInfo[gitErrorKey] = string as Any? - } - let error = NSError( - domain: self.configuration?.errorDomain ?? "", - code: response.statusCode, - userInfo: userInfo - ) - - completion(nil, error) - - return - } - - if let error { - completion(nil, error) - } else { - if let data { - do { - let decoded = try decoder.decode(T.self, from: data) - completion(decoded, nil) - } catch { - completion(nil, error) - } - } - } - } - task.resume() - return task - } - - #if !canImport(FoundationNetworking) - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func post( - _ session: GitURLSession, - decoder: JSONDecoder = JSONDecoder(), - expectedResultType _: T.Type - ) async throws -> T { - - guard let request = request() else { - throw NSError(domain: configuration?.errorDomain ?? "", code: -876, userInfo: nil) - } - - let data = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions()) - let responseTuple = try await session.upload(for: request, from: data, delegate: nil) - if let response = responseTuple.1 as? HTTPURLResponse, response.wasSuccessful == false { - var userInfo = [String: Any]() - if let json = try? JSONSerialization.jsonObject( - with: responseTuple.0, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - } else if let string = String(data: responseTuple.0, encoding: String.Encoding.utf8) { - userInfo[gitErrorKey] = string as Any? - } - throw NSError(domain: configuration?.errorDomain ?? "", code: response.statusCode, userInfo: userInfo) - } - - return try decoder.decode(T.self, from: responseTuple.0) - } - #endif -} diff --git a/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift b/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift deleted file mode 100644 index c1d16e44da..0000000000 --- a/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift +++ /dev/null @@ -1,382 +0,0 @@ -// -// GitRouter.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// -// This file should be strictly just be used for Accounts since it's not -// built for any other networking except those of git accounts - -import Foundation - -// TODO: DOCS (Nanashi Li) -enum GitHTTPMethod: String { - case GET, POST, PUT, PATCH, DELETE -} - -enum GitHTTPEncoding: Int { - case url, form, json -} - -struct GitHTTPHeader { - var headerField: String - var value: String -} - -protocol GitRouterConfiguration { - var apiEndpoint: String? { get } - var accessToken: String? { get } - var accessTokenFieldName: String? { get } - var authorizationHeader: String? { get } - var errorDomain: String? { get } - var customHeaders: [GitHTTPHeader]? { get } -} - -extension GitRouterConfiguration { - var accessTokenFieldName: String? { - "access_token" - } - - var authorizationHeader: String? { - nil - } - - var errorDomain: String? { - "com.codeedit.models.accounts.networking" - } - - var customHeaders: [GitHTTPHeader]? { - nil - } -} - -protocol GitRouter { - var method: GitHTTPMethod { get } - var path: String { get } - var encoding: GitHTTPEncoding { get } - var params: [String: Any] { get } - var configuration: GitRouterConfiguration? { get } - - func urlQuery(_ parameters: [String: Any]) -> [URLQueryItem]? - - func request(_ urlComponents: URLComponents, parameters: [String: Any]) -> URLRequest? - - func loadJSON( - _ session: GitURLSession, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? - - func load( - _ session: GitURLSession, - dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? - - func load( - _ session: GitURLSession, - decoder: JSONDecoder, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? - - func request() -> URLRequest? -} - -extension GitRouter { - - var gitErrorKey: String { "ErrorKey" } - - func request() -> URLRequest? { - let url = URL(string: path, relativeTo: URL(string: configuration?.apiEndpoint ?? "")!) - - var parameters = encoding == .json ? [:] : params - - if let accessToken = configuration?.accessToken, configuration?.authorizationHeader == nil { - parameters[configuration?.accessTokenFieldName ?? ""] = accessToken as Any? - } - - let components = URLComponents(url: url!, resolvingAgainstBaseURL: true) - - var urlRequest = request(components!, parameters: parameters) - - if let accessToken = configuration?.accessToken, let tokenType = configuration?.authorizationHeader { - urlRequest?.addValue("\(tokenType) \(accessToken)", forHTTPHeaderField: "Authorization") - } - - if let customHeaders = configuration?.customHeaders { - customHeaders.forEach { httpHeader in - urlRequest?.addValue(httpHeader.value, forHTTPHeaderField: httpHeader.headerField) - } - } - - return urlRequest - } - - /// Due to the complexity of the the urlQuery method we disabled lint for the this method - /// only so that it doesn't complain... Note this level of complexity is needed to give us as - /// much success rate as possible due to all git providers having different types of url schemes. - func urlQuery(_ parameters: [String: Any]) -> [URLQueryItem]? { // swiftlint:disable:this cyclomatic_complexity - guard !parameters.isEmpty else { return nil } - - var components: [URLQueryItem] = [] - - for key in parameters.keys.sorted(by: <) { - guard let value = parameters[key] else { continue } - - switch value { - case let value as String: - if let escapedValue = value.addingPercentEncoding( - withAllowedCharacters: CharacterSet.URLQueryAllowedCharacterSet() - ) { - components.append(URLQueryItem(name: key, value: escapedValue)) - } - case let valueArray as [String]: - for (index, item) in valueArray.enumerated() { - if let escapedValue = item.addingPercentEncoding( - withAllowedCharacters: CharacterSet.URLQueryAllowedCharacterSet() - ) { - components.append(URLQueryItem(name: "\(key)[\(index)]", value: escapedValue)) - } - } - case let valueDict as [String: Any]: - for nestedKey in valueDict.keys.sorted(by: <) { - guard let value = valueDict[nestedKey] as? String else { continue } - if let escapedValue = value.addingPercentEncoding( - withAllowedCharacters: CharacterSet.URLQueryAllowedCharacterSet() - ) { - components.append(URLQueryItem(name: "\(key)[\(nestedKey)]", value: escapedValue)) - } - } - default: - print("Cannot encode object of type \(type(of: value))") - } - } - - return components - } - - func request(_ urlComponents: URLComponents, parameters: [String: Any]) -> URLRequest? { - - var urlComponents = urlComponents - - urlComponents.percentEncodedQuery = urlQuery(parameters)?.map { - [$0.name, $0.value ?? ""].joined(separator: "=") - }.joined(separator: "&") - - guard let url = urlComponents.url else { return nil } - - switch encoding { - case .url, .json: - var mutableURLRequest = Foundation.URLRequest(url: url) - - mutableURLRequest.httpMethod = method.rawValue - - return mutableURLRequest - case .form: - let queryData = urlComponents.percentEncodedQuery?.data(using: String.Encoding.utf8) - - // clear the query items as they go into the body - urlComponents.queryItems = nil - - var mutableURLRequest = Foundation.URLRequest(url: urlComponents.url!) - - mutableURLRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "content-type") - - mutableURLRequest.httpBody = queryData - - mutableURLRequest.httpMethod = method.rawValue - - return mutableURLRequest as URLRequest - } - } - - @available(*, deprecated, message: "Plase use `load` method instead") - func loadJSON( - _ session: GitURLSession = URLSession.shared, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - load(session, expectedResultType: expectedResultType, completion: completion) - } - - func load( - _ session: GitURLSession = URLSession.shared, - dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?, - expectedResultType: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - let decoder = JSONDecoder() - - if let dateDecodingStrategy { - decoder.dateDecodingStrategy = dateDecodingStrategy - } - - return load(session, decoder: decoder, expectedResultType: expectedResultType, completion: completion) - } - - func load( - _ session: GitURLSession = URLSession.shared, - decoder: JSONDecoder = JSONDecoder(), expectedResultType _: T.Type, - completion: @escaping (_ json: T?, _ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - guard let request = request() else { - return nil - } - - let task = session.dataTask(with: request) { data, response, err in - if let response = response as? HTTPURLResponse { - if response.wasSuccessful == false { - var userInfo = [String: Any]() - if let data, let json = try? JSONSerialization.jsonObject( - with: data, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - } - - let error = NSError( - domain: self.configuration?.errorDomain ?? "", - code: response.statusCode, - userInfo: userInfo - ) - - completion(nil, error) - - return - } - } - - if let err { - completion(nil, err) - } else { - if let data { - do { - let decoded = try decoder.decode(T.self, from: data) - completion(decoded, nil) - } catch { - completion(nil, error) - } - } - } - } - task.resume() - return task - } - - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func load( - _ session: GitURLSession = URLSession.shared, - decoder: JSONDecoder = JSONDecoder(), - expectedResultType _: T.Type - ) async throws -> T { - - guard let request = request() else { - throw NSError(domain: configuration?.errorDomain ?? "", code: -876, userInfo: nil) - } - - let responseTuple = try await session.data(for: request, delegate: nil) - - if let response = responseTuple.1 as? HTTPURLResponse { - if response.wasSuccessful == false { - var userInfo = [String: Any]() - if let json = try? JSONSerialization.jsonObject( - with: responseTuple.0, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - - } - - throw NSError(domain: configuration?.errorDomain ?? "", code: response.statusCode, userInfo: userInfo) - } - } - - return try decoder.decode(T.self, from: responseTuple.0) - } - - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func load( - _ session: GitURLSession = URLSession.shared, - dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?, - expectedResultType: T.Type - ) async throws -> T { - - let decoder = JSONDecoder() - - if let dateDecodingStrategy { - decoder.dateDecodingStrategy = dateDecodingStrategy - } - - return try await load(session, decoder: decoder, expectedResultType: expectedResultType) - } - - func load( - _ session: GitURLSession = URLSession.shared, - completion: @escaping (_ error: Error?) -> Void - ) -> GitURLSessionDataTaskProtocol? { - - guard let request = request() else { - return nil - } - - let task = session.dataTask(with: request) { data, response, err in - if let response = response as? HTTPURLResponse { - if response.wasSuccessful == false { - var userInfo = [String: Any]() - if let data, let json = try? JSONSerialization.jsonObject( - with: data, - options: .mutableContainers - ) as? [String: Any] { - - userInfo[gitErrorKey] = json as Any? - - } - - let error = NSError( - domain: self.configuration?.errorDomain ?? "", - code: response.statusCode, - userInfo: userInfo - ) - - completion(error) - - return - } - } - - completion(err) - } - task.resume() - return task - } -} - -private extension CharacterSet { - - /// https://github.com/Alamofire/Alamofire/blob/3.5rameterEncoding.swift#L220-L225 - static func URLQueryAllowedCharacterSet() -> CharacterSet { - - // does not include "?" or "/" due to RFC 3986 - Section 3.4 - let generalDelimitersToEncode = ":#[]@" - let subDelimitersToEncode = "!$&'()*+,;=" - - var allowedCharacterSet = CharacterSet.urlQueryAllowed - allowedCharacterSet.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode) - return allowedCharacterSet - } -} - -extension HTTPURLResponse { - - /// Checks what kind of HTTP response we get from the server - var wasSuccessful: Bool { - let successRange = 200 ..< 300 - return successRange.contains(statusCode) - } -} diff --git a/CodeEdit/Features/Git/Accounts/Networking/GitURLSession.swift b/CodeEdit/Features/Git/Accounts/Networking/GitURLSession.swift deleted file mode 100644 index 6a8d62bb36..0000000000 --- a/CodeEdit/Features/Git/Accounts/Networking/GitURLSession.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Session.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// -// This file should be strictly just be used for Accounts since it's not -// built for any other networking except those of git accounts - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -// TODO: DOCS (Nanashi Li) -protocol GitURLSession { - - func dataTask( - with request: URLRequest, - completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void - ) -> GitURLSessionDataTaskProtocol - - func uploadTask( - with request: URLRequest, - fromData bodyData: Data?, - completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void - ) -> GitURLSessionDataTaskProtocol - -#if !canImport(FoundationNetworking) - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func data( - for request: URLRequest, - delegate: URLSessionTaskDelegate? - ) async throws -> (Data, URLResponse) - - @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) - func upload( - for request: URLRequest, - from bodyData: Data, - delegate: URLSessionTaskDelegate? - ) async throws -> (Data, URLResponse) -#endif -} - -protocol GitURLSessionDataTaskProtocol { - func resume() -} - -extension URLSessionDataTask: GitURLSessionDataTaskProtocol {} - -extension URLSession: GitURLSession { - - func dataTask( - with request: URLRequest, - completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void - ) -> GitURLSessionDataTaskProtocol { - (dataTask(with: request, completionHandler: completionHandler) as URLSessionDataTask) - } - - func uploadTask( - with request: URLRequest, - fromData bodyData: Data?, - completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void - ) -> GitURLSessionDataTaskProtocol { - uploadTask(with: request, from: bodyData, completionHandler: completionHandler) - } -} diff --git a/CodeEdit/Features/Git/Accounts/Parameters.swift b/CodeEdit/Features/Git/Accounts/Parameters.swift deleted file mode 100644 index 237eaea683..0000000000 --- a/CodeEdit/Features/Git/Accounts/Parameters.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Parameters.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -enum GitSortDirection: String { - case asc - case desc -} - -enum GitSortType: String { - case created - case updated - case popularity - case longRunning = "long-running" -} diff --git a/CodeEdit/Features/Git/Accounts/Utils/GitTime.swift b/CodeEdit/Features/Git/Accounts/Utils/GitTime.swift deleted file mode 100644 index 8f41b24c91..0000000000 --- a/CodeEdit/Features/Git/Accounts/Utils/GitTime.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// GitTime.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -// TODO: DOCS (Nanashi Li) -enum GitTime { - - /** - A date formatter for RFC 3339 style timestamps. - Uses POSIX locale and GMT timezone so that date values are parsed as absolutes. - - (https://tools.ietf.org/html/rfc3339) - - (https://developer.apple.com/library/mac/qa/qa1480/_index.html) - */ - static var rfc3339DateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'" - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - return formatter - }() - - /** - Parses RFC 3339 date strings into NSDate - - parameter string: The string representation of the date - - returns: An `NSDate` with a successful parse, otherwise `nil` - */ - static func rfc3339Date(_ string: String?) -> Date? { - guard let string else { return nil } - return GitTime.rfc3339DateFormatter.date(from: string) - } -} diff --git a/CodeEdit/Features/Git/Accounts/Utils/String+PercentEncoding.swift b/CodeEdit/Features/Git/Accounts/Utils/String+PercentEncoding.swift deleted file mode 100644 index ff14fb9c25..0000000000 --- a/CodeEdit/Features/Git/Accounts/Utils/String+PercentEncoding.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// String+PercentEncoding.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -extension String { - - /// Percent-encodes a string to be URL-safe - /// - /// See https://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ for more info - /// - returns: An optional string, with percent encoding to match RFC3986 - func stringByAddingPercentEncodingForRFC3986() -> String? { - let unreserved = "-._~/?" - var allowed = CharacterSet.alphanumerics - allowed.insert(charactersIn: unreserved) - return addingPercentEncoding(withAllowedCharacters: allowed) - } -} diff --git a/CodeEdit/Features/Git/Accounts/Utils/String+QueryParameters.swift b/CodeEdit/Features/Git/Accounts/Utils/String+QueryParameters.swift deleted file mode 100644 index d1cea7642a..0000000000 --- a/CodeEdit/Features/Git/Accounts/Utils/String+QueryParameters.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// String+QueryParameters.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// - -import Foundation - -extension String { - var bitbucketQueryParameters: [String: String] { - let parametersArray = components(separatedBy: "&") - var parameters = [String: String]() - parametersArray.forEach { parameter in - let keyValueArray = parameter.components(separatedBy: "=") - let (key, value) = (keyValueArray.first, keyValueArray.last) - if let key = key?.removingPercentEncoding, let value = value?.removingPercentEncoding { - parameters[key] = value - } - } - return parameters - } -} diff --git a/CodeEdit/Features/Git/Accounts/Utils/URL+URLParameters.swift b/CodeEdit/Features/Git/Accounts/Utils/URL+URLParameters.swift deleted file mode 100644 index 3eb73f5da1..0000000000 --- a/CodeEdit/Features/Git/Accounts/Utils/URL+URLParameters.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// URL+URLParameters.swift -// CodeEditModules/GitAccounts -// -// Created by Nanashi Li on 2022/03/31. -// -import Foundation - -extension URL { - - var URLParameters: [String: String] { - guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return [:] } - var params = [String: String]() - components.queryItems?.forEach { queryItem in - params[queryItem.name] = queryItem.value - } - return params - } - - func bitbucketURLParameters() -> [String: String] { - let stringParams = absoluteString.components(separatedBy: "?").last - let params = stringParams?.components(separatedBy: "&") - var returnParams: [String: String] = [:] - if let params { - for param in params { - let keyValue = param.components(separatedBy: "=") - if let key = keyValue.first, let value = keyValue.last { - returnParams[key] = value - } - } - } - return returnParams - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Branches.swift b/CodeEdit/Features/Git/Client/GitClient+Branches.swift deleted file mode 100644 index ac92e6b421..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Branches.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// GitClient+Branches.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation - -extension GitClient { - /// Get branches - /// - Returns: Array of branches - func getBranches() async throws -> [GitBranch] { - let command = "branch --format \"%(refname:short)|%(refname)|%(upstream:short) %(upstream:track)\" -a" - - return try await run(command) - .components(separatedBy: "\n") - .filter { $0 != "" && !$0.contains("HEAD") } - .compactMap { line in - guard let branchPart = line.components(separatedBy: " ").first else { return nil } - let branchComponents = branchPart.components(separatedBy: "|") - let name = branchComponents[0] - let upstream = branchComponents[safe: 2] - - let trackInfoString = line - .dropFirst(branchPart.count) - .trimmingCharacters(in: .whitespacesWithoutNewlines) - let trackInfo = parseBranchTrackInfo(from: trackInfoString) - - return .init( - name: name, - longName: branchComponents[safe: 1] ?? name, - upstream: upstream?.isEmpty == true ? nil : upstream, - ahead: trackInfo.ahead, - behind: trackInfo.behind - ) - } - } - - /// Get current branch - func getCurrentBranch() async throws -> GitBranch? { - let branchName = try await run("branch --show-current").trimmingCharacters(in: .whitespacesAndNewlines) - let output = try await run( - "for-each-ref --format=\"%(refname)|%(upstream:short) %(upstream:track)\" refs/heads/\(branchName)" - ) - .trimmingCharacters(in: .whitespacesAndNewlines) - - guard let branchPart = output.components(separatedBy: " ").first else { return nil } - let branchComponents = branchPart.components(separatedBy: "|") - let upstream = branchComponents[safe: 1] - - let trackInfoString = output - .dropFirst(branchPart.count) - .trimmingCharacters(in: .whitespacesWithoutNewlines) - let trackInfo = parseBranchTrackInfo(from: trackInfoString) - - return .init( - name: branchName, - longName: branchComponents[0], - upstream: upstream?.isEmpty == true ? nil : upstream, - ahead: trackInfo.ahead, - behind: trackInfo.behind - ) - } - - /// Delete branch - func deleteBranch(_ branch: GitBranch) async throws { - if !branch.isLocal { - return - } - - _ = try await run("branch -d \(branch.name)") - } - - /// Create new branch - func newBranch(name: String, from: GitBranch) async throws { - if !from.isLocal { - return - } - - _ = try await run("checkout -b \(name) \(from.name)") - } - - /// Rename branch - /// - Parameter from: Name of the branch to rename - /// - Parameter to: New name for branch - func renameBranch(oldName: String, newName: String) async throws { - _ = try await run("branch -m \(oldName) \(newName)") - } - - /// Checkout branch - /// - Parameter branch: Branch to checkout - func checkoutBranch(_ branch: GitBranch, forceLocal: Bool = false) async throws { - var command = "checkout " - - // If branch is remote, try to create local branch - if branch.isRemote { - let localName = branch.name.replacingOccurrences(of: "origin/", with: "") - command += forceLocal ? localName : "-b " + localName + " " + branch.name - } else { - command += branch.name - } - - do { - let output = try await run(command) - if !output.contains("Switched to branch") && !output.contains("Switched to a new branch") { - throw GitClientError.outputError(output) - } - } catch { - // If branch is remote and command failed because branch already exists - // try to switch to local branch - if let error = error as? GitClientError, - branch.isRemote, - error.localizedDescription.contains("already exists") { - try await checkoutBranch(branch, forceLocal: true) - } - } - } - - private func parseBranchTrackInfo(from infoString: String) -> (ahead: Int, behind: Int) { - let pattern = "\\[ahead (\\d+)(?:, behind (\\d+))?\\]|\\[behind (\\d+)\\]" - // Create a regular expression object - guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { - fatalError("Invalid regular expression pattern") - } - var ahead = 0 - var behind = 0 - // Match the input string with the regular expression - if let match = regex.firstMatch( - in: infoString, - options: [], - range: NSRange(location: 0, length: infoString.utf16.count) - ) { - // Extract the captured groups - if let aheadRange = Range(match.range(at: 1), in: infoString), - let aheadValue = Int(infoString[aheadRange]) { - ahead = aheadValue - } - if let behindRange = Range(match.range(at: 2), in: infoString), - let behindValue = Int(infoString[behindRange]) { - behind = behindValue - } - if let behindRange = Range(match.range(at: 3), in: infoString), - let behindValue = Int(infoString[behindRange]) { - behind = behindValue - } - } - return (ahead, behind) - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Clone.swift b/CodeEdit/Features/Git/Client/GitClient+Clone.swift deleted file mode 100644 index 09f3f3ad8d..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Clone.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// GitClient+Clone.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation -import Combine - -extension GitClient { - struct CloneProgress { - let progress: Double - let state: GitCloneProgressState - } - - enum GitCloneProgressState { - case initialState - case counting - case compressing - case receiving - case resolving - - var label: String { - switch self { - case .initialState: "Cloning" - case .counting: "Counting" - case .compressing: "Compressing" - case .receiving: "Receiving" - case .resolving: "Resolving" - } - } - } - - /// Clone repository - /// - Parameters: - /// - remoteUrl: URL of remote repository - /// - localPath: Local path to clone - /// - Returns: Stream of progress - func cloneRepository( - remoteUrl: URL, - localPath: URL - ) -> AsyncThrowingMapSequence { - let command = "clone \(remoteUrl.absoluteString) \(localPath.relativePath.escapedWhiteSpaces()) --progress" - - return self.runLive(command) - .map { line in - // Inspired by VS Code https://github.com/microsoft/vscode/blob/main/extensions/git/src/git.ts - // Parsing git clone output (for patterns look at cloneMatchTypes) and calculating total progress - // Each step has own base progress and multiplier - // Total progress is baseProgress + (progress from output * multiplier) - // For example current outout is Counting objects: 10%, baseProgress is 0, multiplier is 0.1 - // So total progress is 0 + (10 * 0.1) = 1% - for cloneMatchType in self.cloneMatchTypes { - if let progress = self.matchAndCalculateProgress( - line, - cloneMatchType.pattern, - baseProgress: cloneMatchType.baseProgress, - multiplier: cloneMatchType.multiplier - ) { - return .init(progress: progress, state: cloneMatchType.state) - } - } - - return .init(progress: 0, state: .initialState) - } - } - - fileprivate struct CloneMatchType { - let pattern: String - let baseProgress: Double - let multiplier: Double - let state: GitCloneProgressState - } - - fileprivate var cloneMatchTypes: [CloneMatchType] { - [ - .init(pattern: "Counting objects:\\s*(\\d+)%", baseProgress: 0, multiplier: 0.1, state: .counting), - .init(pattern: "Compressing objects:\\s*(\\d+)%", baseProgress: 10, multiplier: 0.1, state: .compressing), - .init(pattern: "Receiving objects:\\s*(\\d+)%", baseProgress: 20, multiplier: 0.4, state: .receiving), - .init(pattern: "Resolving deltas:\\s*(\\d+)%", baseProgress: 60, multiplier: 0.4, state: .resolving), - ] - } - - /// Match pattern in output line and calculate progress - fileprivate func matchAndCalculateProgress( - _ line: String, - _ pattern: String, - baseProgress: Double, - multiplier: Double - ) -> Double? { - let match = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) - .firstMatch(in: line, range: NSRange(line.startIndex..., in: line)) - - if let match, - let range = Range(match.range(at: 1), in: line), - let progress = Int(line[range]) { - return baseProgress + Double(progress) * multiplier - } - return nil - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Commit.swift b/CodeEdit/Features/Git/Client/GitClient+Commit.swift deleted file mode 100644 index a5edc4652f..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Commit.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// GitClient+Commit.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation -import RegexBuilder - -extension GitClient { - /// Commit files - /// - Parameters: - /// - message: Commit message - func commit(message: String, details: String?) async throws { - let message = message.replacingOccurrences(of: #"""#, with: #"\""#) - let command: String - - if let msgDetails = details { - command = "commit --message=\"\(message + (msgDetails.isEmpty ? "" : ("\n\n" + msgDetails)))\"" - } else { - command = "commit --message=\"\(message)\"" - } - - _ = try await run(command) - } - - /// Add file to git - /// - Parameter file: File to add - func add(_ files: [CEWorkspaceFile]) async throws { - _ = try await run("add \(files.map { $0.url.relativePath }.joined(separator: " "))") - } - - /// Add file to git - /// - Parameter file: File to add - func reset(_ files: [CEWorkspaceFile]) async throws { - _ = try await run("reset \(files.map { $0.url.relativePath }.joined(separator: " "))") - } - - /// Returns tuple of unsynced commits both ahead and behind - func numberOfUnsyncedCommits() async throws -> (ahead: Int, behind: Int) { - let output = try await run("status -sb --porcelain=v2").trimmingCharacters(in: .whitespacesAndNewlines) - return try parseUnsyncedCommitsOutput(from: output) - } - - func getCommitChangedFiles(commitSHA: String) async throws -> [GitChangedFile] { - do { - let output = try await run("diff-tree --no-commit-id --name-status -r \(commitSHA)") - let data = output - .trimmingCharacters(in: .newlines) - .components(separatedBy: "\n") - return try data.compactMap { line in - let components = line.split(separator: "\t") - guard components.count == 2 else { return nil } - let changeType = String(components[0]) - let pathName = String(components[1]) - - guard let url = URL(string: pathName ) else { - throw GitClientError.failedToDecodeURL - } - - let gitType: GitType? = .init(rawValue: changeType) - let fullLink = self.directoryURL.appending(path: url.relativePath) - - return GitChangedFile( - changeType: gitType, - fileLink: fullLink - ) - } - } catch { - print("Error: \(error)") - return [] - } - } - - private func parseUnsyncedCommitsOutput(from string: String) throws -> (ahead: Int, behind: Int) { - let components = string.components(separatedBy: .newlines) - guard var abLine = components.first(where: { $0.starts(with: "# branch.ab") }) else { - // We're using --porcelain, this shouldn't happen - return (ahead: 0, behind: 0) - } - abLine = String(abLine.dropFirst("# branch.ab ".count)) - let regex = Regex { - One("+") - Capture { - OneOrMore(.digit) - } transform: { Int($0) } - One(" -") - Capture { - OneOrMore(.digit) - } transform: { Int($0) } - } - guard let match = try regex.firstMatch(in: abLine), - let ahead = match.output.1, - let behind = match.output.2 else { - return (ahead: 0, behind: 0) - } - return (ahead: ahead, behind: behind) - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift b/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift deleted file mode 100644 index 3d8681b584..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// GitClient+CommitHistory.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation - -extension GitClient { - /// Gets the commit history log for the specified branch or file - /// - Parameters: - /// - branchName: Name of the branch - /// - maxCount: Maximum amount of entries to get - /// - fileLocalPath: Optional path of file to get history for - /// - Returns: Array of git commits - func getCommitHistory( - branchName: String? = nil, - maxCount: Int? = nil, - fileLocalPath: String? = nil - ) async throws -> [GitCommit] { - var branchNameString = "" - var maxCountString = "" - let fileLocalPath = fileLocalPath?.escapedWhiteSpaces() ?? "" - if let branchName { branchNameString = "--first-parent \(branchName)" } - if let maxCount { maxCountString = "-n \(maxCount)" } - let dateFormatter = DateFormatter() - - // Can't use `Locale.current`, since it'd give a nil date outside the US - dateFormatter.locale = Locale(identifier: Locale.current.identifier) - dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss Z" - - let output = try await run( - "log --pretty=%h¦%H¦%s¦%aN¦%ae¦%cn¦%ce¦%aD¦ \(maxCountString) \(branchNameString) \(fileLocalPath)" - .trimmingCharacters(in: .whitespacesAndNewlines) - ) - - let remote = try? await run("ls-remote --get-url") - let remoteURL: URL? = if let remote { - URL(string: remote.trimmingCharacters(in: .whitespacesAndNewlines)) - } else { - nil - } - - return output - .split(separator: "\n") - .map { line -> GitCommit in - let parameters = line.components(separatedBy: "¦") - return GitCommit( - hash: parameters[safe: 0] ?? "", - commitHash: parameters[safe: 1] ?? "", - message: parameters[safe: 2] ?? "", - author: parameters[safe: 3] ?? "", - authorEmail: parameters[safe: 4] ?? "", - committer: parameters[safe: 5] ?? "", - committerEmail: parameters[safe: 6] ?? "", - remoteURL: remoteURL, - date: dateFormatter.date(from: parameters[safe: 7] ?? "") ?? Date() - ) - } - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Fetch.swift b/CodeEdit/Features/Git/Client/GitClient+Fetch.swift deleted file mode 100644 index 05964b7921..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Fetch.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// GitClient+Fetch.swift -// CodeEdit -// -// Created by Austin Condiff on 11/18/23. -// - -import Foundation - -extension GitClient { - /// Fetch changes to remote - func fetchFromRemote() async throws { - let command = "fetch" - - _ = try await self.run(command) - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Initiate.swift b/CodeEdit/Features/Git/Client/GitClient+Initiate.swift deleted file mode 100644 index 2d87d325b9..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Initiate.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// GitClient+Initiate.swift -// CodeEdit -// -// Created by Austin Condiff on 11/16/23. -// - -import Foundation - -extension GitClient { - /// Initiate Git repository - func initiate() async throws { - _ = try await run("init") - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Pull.swift b/CodeEdit/Features/Git/Client/GitClient+Pull.swift deleted file mode 100644 index bdf75be42a..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Pull.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// GitClient+Pull.swift -// CodeEdit -// -// Created by Austin Condiff on 11/18/23. -// - -import Foundation - -extension GitClient { - /// Pull changes from remote - func pullFromRemote() async throws { - let command = "pull" - - _ = try await self.run(command) - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Push.swift b/CodeEdit/Features/Git/Client/GitClient+Push.swift deleted file mode 100644 index 6da980e44b..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Push.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// GitClient+Push.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation - -extension GitClient { - /// Push changes to remote - func pushToRemote(upstream: String? = nil) async throws { - var command = "push" - if let upstream { - command += " --set-upstream origin \(upstream)" - } - - let output = try await self.run(command) - - if output.contains("rejected") { - throw GitClientError.outputError(output) - } - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Remote.swift b/CodeEdit/Features/Git/Client/GitClient+Remote.swift deleted file mode 100644 index b3f1b67d63..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Remote.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// GitClient+Remote.swift -// CodeEdit -// -// Created by Austin Condiff on 11/17/23. -// - -import Foundation - -extension GitClient { - /// Gets all remotes - /// - Parameter name: Name for remote - /// - Parameter location: URL string for remote location - func getRemotes() async throws -> [GitRemote] { - let command = "remote -v" - let output = try await run(command) - let remotes = parseGitRemotes(from: output) - - return remotes - } - - /// Add existing remote to local git - /// - Parameter name: Name for remote - /// - Parameter location: URL string for remote location - func addRemote(name: String, location: String) async throws { - _ = try await run("remote add \(name) \(location)") - } - - /// Remove remote from local git - /// - Parameter name: Name for remote to remove - func removeRemote(name: String) async throws { - _ = try await run("remote rm \(name)") - } -} - -func parseGitRemotes(from output: String) -> [GitRemote] { - var remotes: [String: (fetch: String?, push: String?)] = [:] - - output.split(separator: "\n").forEach { line in - let components = line.split { $0 == " " || $0 == "\t" } - guard components.count == 3 else { return } - - let name = String(components[0]) - let location = String(components[1]) - let type = components[2].contains("(fetch)") ? "fetch" : "push" - - if var remote = remotes[name] { - if type == "fetch" { - remote.fetch = location - } else { - remote.push = location - } - remotes[name] = remote - } else { - if type == "fetch" { - remotes[name] = (fetch: location, push: nil) - } else { - remotes[name] = (fetch: nil, push: location) - } - } - } - - return remotes.compactMap { name, locations in - if let fetchLocation = locations.fetch, let pushLocation = locations.push { - return GitRemote(name: name, pushLocation: pushLocation, fetchLocation: fetchLocation) - } - return nil - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Stash.swift b/CodeEdit/Features/Git/Client/GitClient+Stash.swift deleted file mode 100644 index 69a0a82116..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Stash.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// GitClient+Stash.swift -// CodeEdit -// -// Created by Austin Condiff on 11/20/23. -// - -import Foundation - -extension GitClient { - /// Add uncommited changes to stash - func stash(message: String?) async throws { - let command = message != nil ? "stash save --message=\"\(message ?? "")\"" : "stash" - - _ = try await self.run(command) - } - - /// Pops the latest entry from stash onto HEAD - func stashPop() async throws { - let command = "stash pop" - - _ = try await self.run(command) - } - - /// Lists all of the entries in stash - func stashList() async throws -> [GitStashEntry] { - let command = "stash list --date=local" - let output = try await run(command) - let stashEntries = parseGitStashEntries(output) - - return stashEntries - } - - /// Apply stash - func applyStashEntry(_ index: Int?) async throws { - if let idx = index { - _ = try await run("stash apply stash@{\(idx)}") - } else { - _ = try await run("stash apply") - } - } - - /// Delete stash - func deleteStashEntry(_ index: Int) async throws { - _ = try await run("stash drop stash@{\(index)}") - } -} - -func parseGitStashEntries(_ input: String) -> [GitStashEntry] { - var entries: [GitStashEntry] = [] - - let trimmedInput = input.trimmingCharacters(in: .whitespacesAndNewlines) - let lines = trimmedInput.split(separator: "\n", omittingEmptySubsequences: false) - - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.current - dateFormatter.dateFormat = "EEE MMM d HH:mm:ss yyyy" - - for (index, line) in lines.enumerated() { - let components = line.split(separator: ": ", maxSplits: 2, omittingEmptySubsequences: true) - guard components.count >= 3 else { continue } - - let dateString = String(components[0].replacingOccurrences(of: "stash@{", with: "").dropLast()) - guard let date = dateFormatter.date(from: dateString) else { continue } - - // Re-join the remaining parts of the message (if there are colons in the message) - let message = components[2...].joined(separator: ": ").trimmingCharacters(in: .whitespaces) - - let entry = GitStashEntry(index: index, message: message, date: date) - entries.append(entry) - } - - return entries -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Status.swift b/CodeEdit/Features/Git/Client/GitClient+Status.swift deleted file mode 100644 index 8380351a90..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Status.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// GitClient+Status.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation - -extension GitClient { - /// Get changed files - func getChangedFiles() async throws -> [GitChangedFile] { - let output = try await run("status -s --porcelain -u") - - return try output - .split(whereSeparator: \.isNewline) - .map { line -> GitChangedFile in - let paramData = line.trimmingCharacters(in: .whitespacesAndNewlines) - let parameters = paramData.components(separatedBy: " ") - - let urlIndex = parameters.count > 2 ? 2 : 1 - - guard let url = URL(string: parameters[safe: urlIndex] ?? String(describing: URLError.badURL)) else { - throw GitClientError.failedToDecodeURL - } - - let gitType: GitType? = .init(rawValue: parameters[safe: 0] ?? "") - let fullLink = self.directoryURL.appending(path: url.relativePath) - - return GitChangedFile( - changeType: gitType, - fileLink: fullLink - ) - } - } - - /// Get staged files - func getStagedFiles() async throws -> [GitChangedFile] { - let output = try await run("diff --name-status --cached") - - return try output - .split(whereSeparator: \.isNewline) - .map { line -> GitChangedFile in - let paramData = line.trimmingCharacters(in: .whitespacesAndNewlines) - let parameters = paramData.components(separatedBy: "\t") - let urlIndex = parameters.count > 2 ? 2 : 1 - - guard let url = URL(string: parameters[safe: urlIndex] ?? String(describing: URLError.badURL)) else { - throw GitClientError.failedToDecodeURL - } - - let gitType: GitType? = .init(rawValue: parameters[safe: 0] ?? "") - let fullLink = self.directoryURL.appending(path: url.relativePath) - - return GitChangedFile( - changeType: gitType, - fileLink: fullLink - ) - } - } - - /// Discard changes for file - func discardChanges(for file: URL) async throws { - _ = try await run("restore \(file.relativePath)") - } - - /// Discard unstaged changes - func discardAllChanges() async throws { - _ = try await run("restore .") - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient+Validate.swift b/CodeEdit/Features/Git/Client/GitClient+Validate.swift deleted file mode 100644 index 75d01660c5..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient+Validate.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// GitClient+Validate.swift -// CodeEdit -// -// Created by Austin Condiff on 11/29/23. -// - -import Foundation - -extension GitClient { - /// Determines if the current directory is a valid git repository. - /// - /// Runs `git rev-parse --is-inside-work-tree`. - /// - /// - Returns: True, if git finds a valid repository. - func validate() async -> Bool { - do { - let output = try await run("rev-parse --is-inside-work-tree") - return output.trimmingCharacters(in: .whitespacesAndNewlines) == "true" - } catch { - return false - } - } -} diff --git a/CodeEdit/Features/Git/Client/GitClient.swift b/CodeEdit/Features/Git/Client/GitClient.swift deleted file mode 100644 index a8f68dec97..0000000000 --- a/CodeEdit/Features/Git/Client/GitClient.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// GitClient.swift -// CodeEdit -// -// Created by Matthijs Eikelenboom on 26/11/2022. -// - -import Combine -import Foundation - -class GitClient { - enum GitClientError: Error { - case outputError(String) - case notGitRepository - case failedToDecodeURL - - var description: String { - switch self { - case .outputError(let string): string - case .notGitRepository: "Not a git repository" - case .failedToDecodeURL: "Failed to decode URL" - } - } - } - - internal let directoryURL: URL - internal let shellClient: ShellClient - - init(directoryURL: URL, shellClient: ShellClient) { - self.directoryURL = directoryURL - self.shellClient = shellClient - } - - /// Runs a git command, it will prepend the command with `cd ;git`, - /// If you need to run "git checkout", pass "checkout" as the command parameter - internal func run(_ command: String) async throws -> String { - let output = try shellClient.run(generateCommand(command)) - return try processCommonErrors(output) - } - - internal typealias LiveCommandStream = AsyncThrowingMapSequence, String> - - /// Runs a git command in same way as `run`, but returns a async stream of the output - internal func runLive(_ command: String) -> LiveCommandStream { - return runLive(customCommand: generateCommand(command)) - } - - /// Here you can run a custom command, this is needed for git clone - internal func runLive(customCommand: String) -> LiveCommandStream { - return shellClient - .runAsync(customCommand) - .map { output in - return try self.processCommonErrors(output) - } - } - - private func generateCommand(_ command: String) -> String { - "cd \(directoryURL.relativePath.escapedWhiteSpaces());git \(command)" - } - - private func processCommonErrors(_ output: String) throws -> String { - if output.contains("fatal: not a git repository") { - throw GitClientError.notGitRepository - } - - if output.hasPrefix("fatal:") { - throw GitClientError.outputError(output) - } - - return output - } -} - -internal extension Collection { - /// Returns the element at the specified index if it is within bounds, otherwise nil. - subscript (safe index: Index) -> Element? { - indices.contains(index) ? self[index] : nil - } -} - -internal extension String { - func escapedWhiteSpaces() -> String { - self.replacingOccurrences(of: " ", with: "\\ ") - } -} diff --git a/CodeEdit/Features/Git/Client/Models/GitBranch.swift b/CodeEdit/Features/Git/Client/Models/GitBranch.swift deleted file mode 100644 index 338108c454..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitBranch.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// GitBranch.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import Foundation - -struct GitBranch: Hashable { - let name: String - let longName: String - let upstream: String? - let ahead: Int - let behind: Int - - /// Is local branch - var isLocal: Bool { - return longName.hasPrefix("refs/heads/") - } - - /// Is remote branch - var isRemote: Bool { - return longName.hasPrefix("refs/remotes/") - } -} diff --git a/CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift b/CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift deleted file mode 100644 index 6c05097f6b..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// GitBranchesGroup.swift -// CodeEdit -// -// Created by Federico Zivolo on 22/01/24. -// - -import Foundation - -struct GitBranchesGroup: Hashable { - let name: String - var branches: [GitBranch] - var shouldNest: Bool { - branches.first?.name.hasPrefix(name + "/") ?? false - } -} diff --git a/CodeEdit/Features/Git/Client/Models/GitChangedFile.swift b/CodeEdit/Features/Git/Client/Models/GitChangedFile.swift deleted file mode 100644 index 9c86d5e41e..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitChangedFile.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// ChangedFile.swift -// -// -// Created by Nanashi Li on 2022/05/20. -// - -import Foundation -import SwiftUI - -struct GitChangedFile { - /// Change type is to tell us whether the type is a new file, modified or deleted - let changeType: GitType? - - /// Link of the file - let fileLink: URL -} diff --git a/CodeEdit/Features/Git/Client/Models/GitCommit.swift b/CodeEdit/Features/Git/Client/Models/GitCommit.swift deleted file mode 100644 index 65b376fe62..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitCommit.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// GitCommit.swift -// CodeEditModules/Git -// -// Created by Marco Carnevali on 27/03/22. -// - -import Foundation.NSDate - -/// Model class to help map commit history log data -struct GitCommit: Equatable, Hashable, Identifiable { - var id = UUID() - let hash: String - let commitHash: String - let message: String - let author: String - let authorEmail: String - let committer: String - let committerEmail: String - let remoteURL: URL? - let date: Date - - var commitBaseURL: URL? { - if let remoteURL { - if remoteURL.absoluteString.contains("github") { - return parsedRemoteUrl(domain: "https://github.com", remote: remoteURL) - } - if remoteURL.absoluteString.contains("bitbucket") { - return parsedRemoteUrl(domain: "https://bitbucket.org", remote: remoteURL) - } - if remoteURL.absoluteString.contains("gitlab") { - return parsedRemoteUrl(domain: "https://gitlab.com", remote: remoteURL) - } - // TODO: Implement other git clients other than github, bitbucket here - } - return nil - } - - private func parsedRemoteUrl(domain: String, remote: URL) -> URL { - // There are 2 types of remotes - https and ssh. While https has URL in its name, ssh doesn't. - // Following code takes remote name in format profileName/repoName and prepends according domain - var formattedRemote = remote - if formattedRemote.absoluteString.starts(with: "git@") { - let parts = formattedRemote.absoluteString.components(separatedBy: ":") - formattedRemote = URL.init(fileURLWithPath: "\(domain)/\(parts[parts.count - 1])") - } - - return formattedRemote.deletingPathExtension().appendingPathComponent("commit") - } - - var remoteString: String { - if let remoteURL { - if remoteURL.absoluteString.contains("github") { - return "GitHub" - } - if remoteURL.absoluteString.contains("bitbucket") { - return "BitBucket" - } - if remoteURL.absoluteString.contains("gitlab") { - return "GitLab" - } - } - return "Remote" - } -} diff --git a/CodeEdit/Features/Git/Client/Models/GitRemote.swift b/CodeEdit/Features/Git/Client/Models/GitRemote.swift deleted file mode 100644 index f2205776f7..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitRemote.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// GitRemote.swift -// CodeEdit -// -// Created by Austin Condiff on 11/17/23. -// - -import Foundation - -struct GitRemote: Hashable { - let name: String - let pushLocation: String - let fetchLocation: String -} diff --git a/CodeEdit/Features/Git/Client/Models/GitStashEntry.swift b/CodeEdit/Features/Git/Client/Models/GitStashEntry.swift deleted file mode 100644 index cbec68520d..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitStashEntry.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// GitStashEntry.swift -// CodeEdit -// -// Created by Austin Condiff on 11/20/23. -// - -import Foundation - -struct GitStashEntry: Hashable { - let index: Int - let message: String - let date: Date -} diff --git a/CodeEdit/Features/Git/Client/Models/GitType.swift b/CodeEdit/Features/Git/Client/Models/GitType.swift deleted file mode 100644 index 85216fa991..0000000000 --- a/CodeEdit/Features/Git/Client/Models/GitType.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// GitType.swift -// -// -// Created by Nanashi Li on 2022/05/20. -// - -import Foundation - -enum GitType: String, Codable { - case modified = "M" - case untracked = "??" - case fileTypeChange = "T" - case added = "A" - case deleted = "D" - case renamed = "R" - case copied = "C" - case updatedUnmerged = "U" - - var description: String { - switch self { - case .modified: return "M" - case .untracked: return "U" - case .fileTypeChange: return "T" - case .added: return "A" - case .deleted: return "D" - case .renamed: return "R" - case .copied: return "C" - case .updatedUnmerged: return "U" - } - } -} diff --git a/CodeEdit/Features/Git/Clone/GitCheckoutBranchView.swift b/CodeEdit/Features/Git/Clone/GitCheckoutBranchView.swift deleted file mode 100644 index c3a4c3b001..0000000000 --- a/CodeEdit/Features/Git/Clone/GitCheckoutBranchView.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// GitCheckoutBranchView.swift -// CodeEditModules/Git -// -// Created by Aleksi Puttonen on 14.4.2022. -// - -import Foundation -import SwiftUI - -struct GitCheckoutBranchView: View { - @Environment(\.dismiss) - private var dismiss - - @StateObject private var viewModel: GitCheckoutBranchViewModel - private var openDocument: (URL) -> Void - - init( - repoLocalPath: URL, - openDocument: @escaping (URL) -> Void - ) { - _viewModel = .init(wrappedValue: GitCheckoutBranchViewModel(repoPath: repoLocalPath)) - self.openDocument = openDocument - } - var body: some View { - VStack(spacing: 8) { - HStack { - Image(nsImage: NSApp.applicationIconImage) - .resizable() - .frame(width: 64, height: 64) - .padding(.bottom, 50) - VStack(alignment: .leading) { - Text("Checkout branch") - .bold() - .padding(.bottom, 2) - Text("Select a branch to checkout") - .font(.system(size: 11)) - .foregroundColor(.secondary) - .alignmentGuide(.trailing) { context in - context[.trailing] - } - Picker("", selection: $viewModel.selectedBranch, content: { - ForEach(viewModel.branches, id: \.self) { branch in - Text(branch.name.replacingOccurrences(of: "origin/", with: "")) - .tag(branch as GitBranch?) - } - }) - .labelsHidden() - - HStack { - Button("Cancel") { - dismiss() - } - Button("Checkout") { - Task { - await viewModel.checkoutBranch() - await MainActor.run { - dismiss() - openDocument(viewModel.repoPath) - } - } - } - .keyboardShortcut(.defaultAction) - } - .alignmentGuide(.trailing) { context in - context[.trailing] - } - .offset(x: 145) - } - } - .padding(.top, 20) - .padding(.horizontal, 20) - .padding(.bottom, 16) - .frame(width: 400) - .task { - await viewModel.loadBranches() - } - } - } -} diff --git a/CodeEdit/Features/Git/Clone/GitCloneView.swift b/CodeEdit/Features/Git/Clone/GitCloneView.swift deleted file mode 100644 index d7a0307f30..0000000000 --- a/CodeEdit/Features/Git/Clone/GitCloneView.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// GitCloneView.swift -// CodeEditModules/Git -// -// Created by Aleksi Puttonen on 23.3.2022. -// - -import SwiftUI -import Foundation -import Combine - -struct GitCloneView: View { - @Environment(\.dismiss) - private var dismiss - - @StateObject private var viewModel: GitCloneViewModel = .init() - - private let openBranchView: (URL) -> Void - private let openDocument: (URL) -> Void - - init( - openBranchView: @escaping (URL) -> Void, - openDocument: @escaping (URL) -> Void - ) { - self.openBranchView = openBranchView - self.openDocument = openDocument - } - - var body: some View { - VStack(spacing: 8) { - HStack { - Image(nsImage: NSApp.applicationIconImage) - .resizable() - .frame(width: 64, height: 64) - .padding(.bottom, 50) - VStack(alignment: .leading) { - Text("Clone a repository") - .bold() - .padding(.bottom, 2) - Text("Enter a git repository URL:") - .font(.system(size: 11)) - .foregroundColor(.secondary) - .alignmentGuide(.trailing) { context in - context[.trailing] - } - TextField("Git Repository URL", text: $viewModel.repoUrlStr) - .lineLimit(1) - .padding(.bottom, 15) - .frame(width: 300) - - HStack { - Button("Cancel") { - dismiss() - } - Button("Clone") { - cloneRepository() - } - .keyboardShortcut(.defaultAction) - .disabled(!viewModel.isValidUrl(url: viewModel.repoUrlStr)) - } - .offset(x: 185) - .alignmentGuide(.leading) { context in - context[.leading] - } - } - } - .padding(.top, 20) - .padding(.horizontal, 20) - .padding(.bottom, 16) - .onAppear { - viewModel.checkClipboard() - } - .sheet(isPresented: $viewModel.isCloning) { - NavigationStack { - VStack { - ProgressView( - viewModel.cloningProgress.state.label, - value: viewModel.cloningProgress.progress, - total: 100 - ) - } - } - .toolbar { - ToolbarItem { - Button("Cancel Cloning") { - viewModel.cloningTask?.cancel() - viewModel.cloningTask = nil - viewModel.isCloning = false - } - } - } - .padding() - .frame(width: 350) - } - } - } - - func cloneRepository() { - viewModel.cloneRepository { localPath in - dismiss() - - guard let gitClient = viewModel.gitClient else { return } - - Task { - let branches = ((try? await gitClient.getBranches()) ?? []) - .filter({ $0.isRemote }) - if branches.count > 1 { - openBranchView(localPath) - return - } - - openDocument(localPath) - } - } - } -} diff --git a/CodeEdit/Features/Git/Clone/ViewModels/GitCheckoutBranchViewModel.swift b/CodeEdit/Features/Git/Clone/ViewModels/GitCheckoutBranchViewModel.swift deleted file mode 100644 index df72ef2246..0000000000 --- a/CodeEdit/Features/Git/Clone/ViewModels/GitCheckoutBranchViewModel.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// GitCheckoutBranchView.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/17/23. -// - -import Foundation - -class GitCheckoutBranchViewModel: ObservableObject { - @Published var selectedBranch: GitBranch? - @Published var branches: [GitBranch] = [] - - let repoPath: URL - private let gitClient: GitClient - - init(repoPath: URL) { - self.repoPath = repoPath - gitClient = .init(directoryURL: repoPath, shellClient: .live()) - } - - func loadBranches() async { - let branches = ((try? await gitClient.getBranches()) ?? []) - .filter({ $0.isRemote }) - - await MainActor.run { - self.branches = branches - if selectedBranch == nil { - selectedBranch = branches.first - } - } - } - - func checkoutBranch() async { - guard let selectedBranch else { return } - - try? await gitClient.checkoutBranch(selectedBranch) - } -} diff --git a/CodeEdit/Features/Git/Clone/ViewModels/GitCloneViewModel.swift b/CodeEdit/Features/Git/Clone/ViewModels/GitCloneViewModel.swift deleted file mode 100644 index 4c1dd789da..0000000000 --- a/CodeEdit/Features/Git/Clone/ViewModels/GitCloneViewModel.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// GitCloneViewModel.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/17/23. -// - -import Foundation -import AppKit - -class GitCloneViewModel: ObservableObject { - @Published var repoUrlStr = "" - @Published var isCloning: Bool = false - @Published var cloningProgress: GitClient.CloneProgress = .init(progress: 0, state: .initialState) - - var gitClient: GitClient? - var cloningTask: Task? - - /// Check if url is valid - /// - Parameter url: Url to check - /// - Returns: True if url is valid - func isValidUrl(url: String) -> Bool { - // Doing the same kind of check that Xcode does when cloning - let url = url.lowercased() - if url.starts(with: "http://") && url.count > 7 { - return true - } else if url.starts(with: "https://") && url.count > 8 { - return true - } else if url.starts(with: "git@") && url.count > 4 { - return true - } - return false - } - - /// Check if clipboard contains git url - func checkClipboard() { - if let url = NSPasteboard.general.pasteboardItems?.first?.string(forType: .string) { - if isValidUrl(url: url) { - self.repoUrlStr = url - } - } - } - - /// Clone repository - func cloneRepository(completionHandler: @escaping (URL) -> Void) { - if repoUrlStr == "" { - showAlert( - alertMsg: "Url cannot be empty", - infoText: "You must specify a repository to clone" - ) - return - } - - // Parsing repo name - guard let remoteUrl = URL(string: repoUrlStr) else { - return - } - - var repoName = remoteUrl.lastPathComponent - - // Strip .git from name if it has it. - // Cloning repository without .git also works - if repoName.contains(".git") { - repoName.removeLast(4) - } - - guard let localPath = getPath(saveName: repoName) else { - return - } - - var isDir: ObjCBool = true - if FileManager.default.fileExists(atPath: localPath.relativePath, isDirectory: &isDir) { - showAlert(alertMsg: "Error", infoText: "Directory already exists") - return - } - - do { - try FileManager.default.createDirectory( - atPath: localPath.relativePath, - withIntermediateDirectories: true, - attributes: nil - ) - } catch { - showAlert(alertMsg: "Failed to create folder", infoText: "\(error)") - return - } - - gitClient = GitClient(directoryURL: localPath, shellClient: .live()) - - self.cloningTask = Task(priority: .background) { - await processCloning( - remoteUrl: remoteUrl, - localPath: localPath, - completionHandler: completionHandler - ) - } - } - - /// Process cloning - /// - Parameters: - /// - remoteUrl: Path to remote repository - /// - localPath: Path to local folder - /// - completionHandler: Completion handler if cloning is successful - private func processCloning( - remoteUrl: URL, - localPath: URL, - completionHandler: @escaping (URL) -> Void - ) async { - guard let gitClient else { return } - - await setIsCloning(true) - - do { - for try await progress in gitClient.cloneRepository(remoteUrl: remoteUrl, localPath: localPath) { - await MainActor.run { - self.cloningProgress = progress - } - } - - if Task.isCancelled { - await MainActor.run { - deleteTemporaryFolder(localPath: localPath) - } - return - } - - completionHandler(localPath) - } catch { - await MainActor.run { - if let error = error as? GitClient.GitClientError { - showAlert(alertMsg: "Failed to clone", infoText: error.description) - } else { - showAlert(alertMsg: "Failed to clone", infoText: error.localizedDescription) - } - deleteTemporaryFolder(localPath: localPath) - } - } - - await setIsCloning(false) - } - - private func deleteTemporaryFolder(localPath: URL) { - do { - try FileManager.default.removeItem(atPath: localPath.relativePath) - } catch { - showAlert(alertMsg: "Failed to delete folder", infoText: "\(error)") - return - } - } - - @MainActor - private func setIsCloning(_ newValue: Bool) { - self.isCloning = newValue - } - - private func getPath(saveName: String) -> URL? { - let dialog = NSSavePanel() - dialog.showsResizeIndicator = true - dialog.showsHiddenFiles = false - dialog.showsTagField = false - dialog.prompt = "Clone" - dialog.nameFieldStringValue = saveName - dialog.nameFieldLabel = "Clone as" - dialog.title = "Clone" - - guard dialog.runModal() == NSApplication.ModalResponse.OK, - let result = dialog.url else { - return nil - } - - return result - } - - private func showAlert(alertMsg: String, infoText: String) { - let alert = NSAlert() - alert.messageText = alertMsg - alert.informativeText = infoText - alert.addButton(withTitle: "OK") - alert.alertStyle = .warning - alert.runModal() - } -} diff --git a/CodeEdit/Features/Git/SourceControlManager.swift b/CodeEdit/Features/Git/SourceControlManager.swift deleted file mode 100644 index 56fff5e94c..0000000000 --- a/CodeEdit/Features/Git/SourceControlManager.swift +++ /dev/null @@ -1,345 +0,0 @@ -// -// SourceControlModel.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import Foundation -import AppKit - -/// This model handle the fetching and adding of changes etc... -final class SourceControlManager: ObservableObject { - let gitClient: GitClient - - /// The base URL of the workspace - let workspaceURL: URL - - let editorManager: EditorManager - weak var fileManager: CEWorkspaceFileManager? - - /// A list of changed files - @Published var changedFiles: [CEWorkspaceFile] = [] - - /// Current branch - @Published var currentBranch: GitBranch? - - /// All branches, local and remote - @Published var branches: [GitBranch] = [] - - /// All remotes - @Published var remotes: [GitRemote] = [] - - /// All stash ebtrues - @Published var stashEntries: [GitStashEntry] = [] - - /// Number of unsynced commits with remote in current branch - @Published var numberOfUnsyncedCommits: (ahead: Int, behind: Int) = (ahead: 0, behind: 0) - - @Published var isGitRepository: Bool = false - - init( - workspaceURL: URL, - editorManager: EditorManager - ) { - self.workspaceURL = workspaceURL - self.editorManager = editorManager - gitClient = GitClient(directoryURL: workspaceURL, shellClient: currentWorld.shellClient) - } - - /// Refresh all changed files and refresh status in file manager - func refreshAllChangedFiles() async { - do { - var fileDictionary = [URL: CEWorkspaceFile]() - - // Process changed files - for item in try await gitClient.getChangedFiles() { - fileDictionary[item.fileLink] = CEWorkspaceFile( - url: item.fileLink, - changeType: item.changeType, - staged: false - ) - } - - // Update staged status - for item in try await gitClient.getStagedFiles() { - fileDictionary[item.fileLink]?.staged = true - } - - // TODO: Profile - let changedFiles = Array(fileDictionary.values.sorted()) - - await setChangedFiles(changedFiles) - await refreshStatusInFileManager() - } catch { - await setChangedFiles([]) - } - } - - /// Get all changed files for a commit - func getCommitChangedFiles(commitSHA: String) async -> [CEWorkspaceFile] { - do { - var fileDictionary = [URL: CEWorkspaceFile]() - - // Process changed files - for item in try await gitClient.getCommitChangedFiles(commitSHA: commitSHA) { - fileDictionary[item.fileLink] = CEWorkspaceFile( - url: item.fileLink, - changeType: item.changeType - ) - } - - // TODO: Profile - let changedFiles = Array(fileDictionary.values.sorted()) - - return changedFiles - } catch { - return [] - } - } - - /// Set changed files on main actor - @MainActor - private func setChangedFiles(_ files: [CEWorkspaceFile]) { - self.changedFiles = files - } - - /// Refresh git status for files in project navigator - @MainActor - private func refreshStatusInFileManager() { - guard let fileManager = fileManager else { - return - } - - var updatedStatusFor: Set = [] - // Refresh status of file manager files - for changedFile in changedFiles { - guard let file = fileManager.flattenedFileItems[changedFile.id] else { - continue - } - if file.gitStatus != changedFile.gitStatus { - file.gitStatus = changedFile.gitStatus - updatedStatusFor.insert(file) - } - } - for (_, file) in fileManager.flattenedFileItems - where !updatedStatusFor.contains(file) && file.gitStatus != nil { - file.gitStatus = nil - updatedStatusFor.insert(file) - } - - if updatedStatusFor.isEmpty { - return - } - - fileManager.notifyObservers(updatedItems: updatedStatusFor) - } - - /// Fetch from remote - func fetch() async throws { - try await gitClient.fetchFromRemote() - await self.refreshNumberOfUnsyncedCommits() - } - - /// Refresh current branch - func refreshCurrentBranch() async { - let currentBranch = try? await gitClient.getCurrentBranch() - await MainActor.run { - self.currentBranch = currentBranch - } - } - - /// Refresh branches - func refreshBranches() async { - let branches = (try? await gitClient.getBranches()) ?? [] - await MainActor.run { - self.branches = branches - } - } - - /// Checkout branch - func checkoutBranch(branch: GitBranch) async throws { - try await gitClient.checkoutBranch(branch) - await refreshBranches() - await refreshCurrentBranch() - } - - /// Create new branch, can be created only from local branch - func newBranch(name: String, from: GitBranch) async throws { - if !from.isLocal { - return - } - - try await gitClient.newBranch(name: name, from: from) - await refreshBranches() - await refreshCurrentBranch() - } - - /// Rename branch - func renameBranch(oldName: String, newName: String) async throws { - try await gitClient.renameBranch(oldName: oldName, newName: newName) - await refreshBranches() - } - - /// Delete branch if it's local and not current - func deleteBranch(branch: GitBranch) async throws { - if !branch.isLocal || branch == currentBranch { - return - } - - try await gitClient.deleteBranch(branch) - await refreshBranches() - } - - /// Delete stash entry - func deleteStashEntry(stashEntry: GitStashEntry) async throws { - try await gitClient.deleteStashEntry(stashEntry.index) - try await refreshStashEntries() - } - - /// Apply stash entry - func applyStashEntry(stashEntry: GitStashEntry?) async throws { - try await gitClient.applyStashEntry(stashEntry?.index) - try await refreshStashEntries() - await refreshAllChangedFiles() - } - - /// Stash changes - func stashChanges(message: String?) async throws { - try await gitClient.stash(message: message) - try await refreshStashEntries() - await refreshAllChangedFiles() - } - - /// Delete remote - func deleteRemote(remote: GitRemote) async throws { - try await gitClient.removeRemote(name: remote.name) - try await refreshRemotes() - } - - /// Discard changes for file - func discardChanges(for file: CEWorkspaceFile) { - Task { - do { - try await gitClient.discardChanges(for: file.url) - // TODO: Refresh content of active and unmodified document, - // requires CodeEditSourceEditor changes - } catch { - await showAlertForError(title: "Failed to discard changes", error: error) - } - } - } - - /// Discard changes for repository - func discardAllChanges() { - Task { - do { - try await gitClient.discardAllChanges() - // TODO: Refresh content of active and unmodified document, - // requires CodeEditSourceEditor changes - } catch { - await showAlertForError(title: "Failed to discard changes", error: error) - } - } - } - - /// Commit files selected by user - func commit(message: String, details: String? = nil) async throws { - try await gitClient.commit(message: message, details: details) - - await self.refreshAllChangedFiles() - await self.refreshNumberOfUnsyncedCommits() - } - - func add(_ files: [CEWorkspaceFile]) async throws { - try await gitClient.add(files) - } - - func reset(_ files: [CEWorkspaceFile]) async throws { - try await gitClient.reset(files) - } - - /// Refresh number of unsynced commits - func refreshNumberOfUnsyncedCommits() async { - let numberOfUnpushedCommits = (try? await gitClient.numberOfUnsyncedCommits()) ?? (ahead: 0, behind: 0) - - await MainActor.run { - self.numberOfUnsyncedCommits = numberOfUnpushedCommits - } - } - - /// Add existing remote to git - func addRemote(name: String, location: String) async throws { - try await gitClient.addRemote(name: name, location: location) - } - - /// Get all remotes - func refreshRemotes() async throws { - let remotes = (try? await gitClient.getRemotes()) ?? [] - await MainActor.run { - self.remotes = remotes - } - } - - func refreshStashEntries() async throws { - let stashEntries = (try? await gitClient.stashList()) ?? [] - await MainActor.run { - self.stashEntries = stashEntries - } - } - - /// Pull changes from remote - func pull() async throws { - try await gitClient.pullFromRemote() - - await self.refreshNumberOfUnsyncedCommits() - } - - /// Push changes to remote - func push() async throws { - guard let currentBranch else { return } - - if currentBranch.upstream == nil { - try await gitClient.pushToRemote(upstream: currentBranch.name) - await refreshCurrentBranch() - } else { - try await gitClient.pushToRemote() - } - - await self.refreshNumberOfUnsyncedCommits() - } - - /// Initiate repository - func initiate() async throws { - try await gitClient.initiate() - } - - /// Validate repository - func validate() async throws { - let isGitRepository = await gitClient.validate() - await MainActor.run { - self.isGitRepository = isGitRepository - } - } - - /// Show alert for error - func showAlertForError(title: String, error: Error) async { - if let error = error as? GitClient.GitClientError { - await showAlert(title: title, message: error.description) - return - } - - await showAlert(title: title, message: error.localizedDescription) - } - - private func showAlert(title: String, message: String) async { - await MainActor.run { - let alert = NSAlert() - alert.messageText = title - alert.informativeText = message - alert.addButton(withTitle: "OK") - alert.alertStyle = .warning - alert.runModal() - } - } -} diff --git a/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift b/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift deleted file mode 100644 index b8d2c1c724..0000000000 --- a/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift +++ /dev/null @@ -1,242 +0,0 @@ -// -// FileInspectorView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/03/24. -// -import SwiftUI -import CodeEditLanguages - -struct FileInspectorView: View { - @EnvironmentObject private var workspace: WorkspaceDocument - - @EnvironmentObject private var editorManager: EditorManager - - @AppSettings(\.textEditing) - private var textEditing - - @State private var file: CEWorkspaceFile? - - @State private var fileName: String = "" - - // File settings overrides - - @State private var language: CodeLanguage? - - @State var indentOption: SettingsData.TextEditingSettings.IndentOption = .init(indentType: .tab) - - @State var defaultTabWidth: Int = 0 - - @State var wrapLines: Bool = false - - func updateFileOptions(_ textEditingOverride: SettingsData.TextEditingSettings? = nil) { - let textEditingSettings = textEditingOverride ?? textEditing - indentOption = file?.fileDocument?.indentOption ?? textEditingSettings.indentOption - defaultTabWidth = file?.fileDocument?.defaultTabWidth ?? textEditingSettings.defaultTabWidth - wrapLines = file?.fileDocument?.wrapLines ?? textEditingSettings.wrapLinesToEditorWidth - } - - func updateInspectorSource() { - file = editorManager.activeEditor.selectedTab?.file - fileName = file?.name ?? "" - language = file?.fileDocument?.language - updateFileOptions() - } - - var body: some View { - Group { - if file != nil { - Form { - Section("Identity and Type") { - fileNameField - fileType - } - Section { - location - } - Section("Text Settings") { - indentUsing - widthOptions - wrapLinesToggle - } - } - } else { - NoSelectionInspectorView() - } - } - .onAppear { - updateInspectorSource() - } - .onReceive(editorManager.activeEditor.objectWillChange) { _ in - updateInspectorSource() - } - .onChange(of: editorManager.activeEditor) { _ in - updateInspectorSource() - } - .onChange(of: editorManager.activeEditor.selectedTab) { _ in - updateInspectorSource() - } - .onChange(of: textEditing) { newValue in - updateFileOptions(newValue) - } - } - - @ViewBuilder private var fileNameField: some View { - if let file { - TextField("Name", text: $fileName) - .background( - file.validateFileName(for: fileName) ? Color.clear : Color(errorRed) - ) - .onSubmit { - if file.validateFileName(for: fileName) { - let destinationURL = file.url - .deletingLastPathComponent() - .appendingPathComponent(fileName) - if !file.isFolder { - editorManager.editorLayout.closeAllTabs(of: file) - } - DispatchQueue.main.async { - workspace.workspaceFileManager?.move(file: file, to: destinationURL) - let newItem = CEWorkspaceFile(url: destinationURL) - newItem.parent = file.parent - if !newItem.isFolder { - editorManager.openTab(item: newItem) - } - } - } else { - fileName = file.labelFileName() - } - } - } - } - - @ViewBuilder private var fileType: some View { - Picker( - "Type", - selection: $language - ) { - Text("Default - Detected").tag(nil as CodeLanguage?) - Divider() - ForEach(CodeLanguage.allLanguages, id: \.id) { language in - Text(language.id.rawValue.capitalized).tag(language as CodeLanguage?) - } - } - .onChange(of: language) { newValue in - file?.fileDocument?.language = newValue - } - } - - private var location: some View { - Group { - if let file { - LabeledContent("Location") { - Button("Choose...") { - guard let newURL = chooseNewFileLocation() else { - return - } - if !file.isFolder { - editorManager.editorLayout.closeAllTabs(of: file) - } - // This is ugly but if the tab is opened at the same time as closing the others, it doesn't open - // And if the files are re-built at the same time as the tab is opened, it causes a memory error - DispatchQueue.main.async { - workspace.workspaceFileManager?.move(file: file, to: newURL) - // If the parent directory doesn't exist in the workspace, don't open it in a tab. - if let newParent = workspace.workspaceFileManager?.getFile( - newURL.deletingLastPathComponent().path - ) { - let newItem = CEWorkspaceFile(url: newURL) - newItem.parent = newParent - if !file.isFolder { - editorManager.openTab(item: newItem) - } - DispatchQueue.main.async { - _ = try? workspace.workspaceFileManager?.rebuildFiles(fromItem: newParent) - } - } - } - } - } - ExternalLink(showInFinder: true, destination: file.url) { - Text(file.url.path(percentEncoded: false)) - .font(.footnote) - .foregroundColor(.secondary) - } - } - } - } - - private var indentUsing: some View { - Picker("Indent using", selection: $indentOption.indentType) { - Text("Spaces").tag(SettingsData.TextEditingSettings.IndentOption.IndentType.spaces) - Text("Tabs").tag(SettingsData.TextEditingSettings.IndentOption.IndentType.tab) - } - .onChange(of: indentOption) { newValue in - file?.fileDocument?.indentOption = newValue == textEditing.indentOption ? nil : newValue - } - } - - private var widthOptions: some View { - LabeledContent("Widths") { - HStack(spacing: 5) { - VStack(alignment: .center, spacing: 0) { - Stepper( - "", - value: Binding( - get: { Double(defaultTabWidth) }, - set: { defaultTabWidth = Int($0) } - ), - in: 1...16, - step: 1, - format: .number - ) - .labelsHidden() - Text("Tab") - .foregroundColor(.primary) - .font(.footnote) - } - .help("The visual width of tab characters") - VStack(alignment: .center, spacing: 0) { - Stepper( - "", - value: Binding( - get: { Double(indentOption.spaceCount) }, - set: { indentOption.spaceCount = Int($0) } - ), - in: 1...10, - step: 1, - format: .number - ) - .labelsHidden() - Text("Indent") - .foregroundColor(.primary) - .font(.footnote) - } - .help("The number of spaces to insert when the tab key is pressed.") - } - } - .onChange(of: defaultTabWidth) { newValue in - file?.fileDocument?.defaultTabWidth = newValue == textEditing.defaultTabWidth ? nil : newValue - } - } - - private var wrapLinesToggle: some View { - Toggle("Wrap lines", isOn: $wrapLines) - .onChange(of: wrapLines) { newValue in - file?.fileDocument?.wrapLines = newValue == textEditing.wrapLinesToEditorWidth ? nil : newValue - } - } - - private func chooseNewFileLocation() -> URL? { - guard let file else { return nil } - let dialogue = NSSavePanel() - dialogue.title = "Save File" - dialogue.directoryURL = file.url.deletingLastPathComponent() - dialogue.nameFieldStringValue = file.name - if dialogue.runModal() == .OK { - return dialogue.url - } else { - return nil - } - } -} diff --git a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorItemView.swift b/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorItemView.swift deleted file mode 100644 index fa5b99c33f..0000000000 --- a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorItemView.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// HistoryInspectorItemView.swift -// CodeEdit -// -// Created by Austin Condiff on 12/27/23. -// - -import SwiftUI - -struct HistoryInspectorItemView: View { - var commit: GitCommit - - @Binding var selection: GitCommit? - - private var showPopup: Binding { - Binding { - selection == commit - } set: { newValue in - if newValue { - selection = commit - } else { - selection = nil - } - } - } - - var body: some View { - CommitListItemView(commit: commit) - .popover(isPresented: showPopup, arrowEdge: .leading) { - HistoryPopoverView(commit: commit) - } - } -} diff --git a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorModel.swift b/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorModel.swift deleted file mode 100644 index 728a3d6c07..0000000000 --- a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorModel.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// HistoryInspectorModel.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/04/18. -// - -import Foundation - -final class HistoryInspectorModel: ObservableObject { - private(set) var sourceControlManager: SourceControlManager? - - /// The base URL of the workspace - private(set) var workspaceURL: URL? - - /// The base URL of the workspace - private(set) var fileURL: String? - - /// The selected branch from the GitClient - @Published var commitHistory: [GitCommit] = [] - - func setWorkspace(sourceControlManager: SourceControlManager?) async { - self.sourceControlManager = sourceControlManager - await updateCommitHistory() - } - - func setFile(url: String?) async { - if fileURL != url { - fileURL = url - await updateCommitHistory() - } - } - - func updateCommitHistory() async { - guard let sourceControlManager, let fileURL else { - await setCommitHistory([]) - return - } - - do { - let commitHistory = try await sourceControlManager - .gitClient - .getCommitHistory(maxCount: 40, fileLocalPath: fileURL) - await setCommitHistory(commitHistory) - } catch { - await setCommitHistory([]) - } - } - - @MainActor - private func setCommitHistory(_ history: [GitCommit]) { - self.commitHistory = history - } -} diff --git a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorView.swift b/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorView.swift deleted file mode 100644 index 9bc871f50f..0000000000 --- a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryInspectorView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// HistoryInspectorView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/03/24. -// -import SwiftUI - -struct HistoryInspectorView: View { - @EnvironmentObject private var workspace: WorkspaceDocument - - @EnvironmentObject private var editorManager: EditorManager - - @ObservedObject private var model: HistoryInspectorModel - - @State var selection: GitCommit? - - /// Initialize with GitClient - /// - Parameter gitClient: a GitClient - init() { - self.model = .init() - } - - var body: some View { - Group { - if model.sourceControlManager != nil { - VStack { - if model.commitHistory.isEmpty { - CEContentUnavailableView("No History") - } else { - List(selection: $selection) { - ForEach(model.commitHistory) { commit in - HistoryInspectorItemView(commit: commit, selection: $selection) - .tag(commit) - .listRowSeparator(.hidden) - } - } - } - } - } else { - NoSelectionInspectorView() - } - } - .onReceive(editorManager.activeEditor.objectWillChange) { _ in - Task { - await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path()) - } - } - .onChange(of: editorManager.activeEditor) { _ in - Task { - await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path()) - } - } - .onChange(of: editorManager.activeEditor.selectedTab) { _ in - Task { - await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path()) - } - } - .task { - await model.setWorkspace(sourceControlManager: workspace.sourceControlManager) - await model.setFile(url: editorManager.activeEditor.selectedTab?.file.url.path) - } - } -} diff --git a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryPopoverView.swift b/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryPopoverView.swift deleted file mode 100644 index 1913af4536..0000000000 --- a/CodeEdit/Features/InspectorArea/HistoryInspector/HistoryPopoverView.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// HistoryPopoverView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/04/18. -// - -import SwiftUI - -struct HistoryPopoverView: View { - - private var commit: GitCommit - - init(commit: GitCommit) { - self.commit = commit - } - - var body: some View { - VStack { - CommitDetailsHeaderView(commit: commit) - .padding(.horizontal) - - Divider() - .padding(.horizontal) - - VStack(alignment: .leading, spacing: 0) { - // TODO: Implementation Needed - ActionButton("Show Commit", systemImage: "clock") {} - .disabled(true) - // TODO: Implementation Needed - ActionButton("Open in Code Review", systemImage: "arrow.left.arrow.right") {} - .disabled(true) - ActionButton("Email \(commit.author)", systemImage: "envelope") { - let service = NSSharingService(named: NSSharingService.Name.composeEmail) - service?.recipients = [commit.authorEmail] - service?.perform(withItems: []) - } - } - .padding(.horizontal, 6) - } - .padding(.top) - .padding(.bottom, 5) - .frame(width: 310) - } - - private struct ActionButton: View { - - private var title: String - private var image: String - private var action: () -> Void - - @State private var isHovering: Bool = false - - @Environment(\.isEnabled) - private var isEnabled - - init(_ title: String, systemImage: String, action: @escaping () -> Void) { - self.title = title - self.image = systemImage - self.action = action - } - - var body: some View { - Button { - action() - } label: { - Label(title: { - Text(title) - .lineLimit(1) - .truncationMode(.middle) - }, icon: { - Image(systemName: image) - .frame(width: 16, alignment: .center) - }) - .frame(maxWidth: .infinity, alignment: .leading) - .foregroundColor(isHovering && isEnabled ? .white : .primary) - .contentShape(Rectangle()) - } - .buttonStyle(.plain) - .padding(.horizontal, 10) - .padding(.vertical, 3) - .background( - EffectView.selectionBackground(isHovering && isEnabled) - ) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .onHover { hovering in - isHovering = hovering - } - } - } -} diff --git a/CodeEdit/Features/InspectorArea/Models/InspectorTab.swift b/CodeEdit/Features/InspectorArea/Models/InspectorTab.swift deleted file mode 100644 index 62bcf53d60..0000000000 --- a/CodeEdit/Features/InspectorArea/Models/InspectorTab.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// InspectorTab.swift -// CodeEdit -// -// Created by Wouter Hennen on 02/06/2023. -// - -import SwiftUI -import CodeEditKit -import ExtensionFoundation - -enum InspectorTab: AreaTab { - case file - case gitHistory - case uiExtension(endpoint: AppExtensionIdentity, data: ResolvedSidebar.SidebarStore) - - var systemImage: String { - switch self { - case .file: - return "doc" - case .gitHistory: - return "clock" - case .uiExtension(_, let data): - return data.icon ?? "e.square" - } - } - - var id: String { - if case .uiExtension(let endpoint, let data) = self { - return endpoint.bundleIdentifier + data.sceneID - } - return title - } - - var title: String { - switch self { - case .file: - return "File Inspector" - case .gitHistory: - return "History Inspector" - case .uiExtension(_, let data): - return data.help ?? data.sceneID - } - } - - var body: some View { - switch self { - case .file: - FileInspectorView() - case .gitHistory: - HistoryInspectorView() - case let .uiExtension(endpoint, data): - ExtensionSceneView(with: endpoint, sceneID: data.sceneID) - } - } -} diff --git a/CodeEdit/Features/InspectorArea/ViewModels/InspectorAreaViewModel.swift b/CodeEdit/Features/InspectorArea/ViewModels/InspectorAreaViewModel.swift deleted file mode 100644 index a36f8f0490..0000000000 --- a/CodeEdit/Features/InspectorArea/ViewModels/InspectorAreaViewModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// InspectorAreaViewModel.swift -// CodeEdit -// -// Created by Abe Malla on 9/23/23. -// - -import Foundation - -class InspectorAreaViewModel: ObservableObject { - @Published var selectedTab: InspectorTab? = .file - /// The tab bar items in the Inspector - @Published var tabItems: [InspectorTab] = [] - - func setInspectorTab(tab newTab: InspectorTab) { - selectedTab = newTab - } -} diff --git a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift deleted file mode 100644 index 2708e784e1..0000000000 --- a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// InspectorAreaView.swift -// CodeEdit -// -// Created by Austin Condiff on 3/21/22. -// - -import SwiftUI - -struct InspectorAreaView: View { - @EnvironmentObject private var workspace: WorkspaceDocument - - @ObservedObject private var extensionManager = ExtensionManager.shared - @ObservedObject public var viewModel: InspectorAreaViewModel - - @EnvironmentObject private var editorManager: EditorManager - - @AppSettings(\.general.inspectorTabBarPosition) - var sidebarPosition: SettingsData.SidebarTabBarPosition - - @State private var selection: InspectorTab? = .file - - init(viewModel: InspectorAreaViewModel) { - self.viewModel = viewModel - - viewModel.tabItems = [.file, .gitHistory] - viewModel.tabItems += extensionManager - .extensions - .map { ext in - ext.availableFeatures.compactMap { - if case .sidebarItem(let data) = $0, data.kind == .inspector { - return InspectorTab.uiExtension(endpoint: ext.endpoint, data: data) - } - return nil - } - } - .joined() - } - - func getExtension(_ id: String) -> ExtensionInfo? { - return extensionManager.extensions.first( - where: { $0.endpoint.bundleIdentifier == id } - ) - } - - var body: some View { - VStack { - if let selection { - selection - } else { - NoSelectionInspectorView() - } - } - .clipShape(Rectangle()) - .frame( - minWidth: CodeEditWindowController.minSidebarWidth, - idealWidth: 300, - minHeight: 0, - maxHeight: .infinity, - alignment: .top - ) - .safeAreaInset(edge: .trailing, spacing: 0) { - if sidebarPosition == .side { - HStack(spacing: 0) { - Divider() - AreaTabBar(items: $viewModel.tabItems, selection: $selection, position: sidebarPosition) - } - } - } - .safeAreaInset(edge: .top, spacing: 0) { - if sidebarPosition == .top { - VStack(spacing: 0) { - Divider() - AreaTabBar(items: $viewModel.tabItems, selection: $selection, position: sidebarPosition) - Divider() - } - } else { - Divider() - } - } - .formStyle(.grouped) - } -} diff --git a/CodeEdit/Features/InspectorArea/Views/InspectorField.swift b/CodeEdit/Features/InspectorArea/Views/InspectorField.swift deleted file mode 100644 index 13c0e5cee3..0000000000 --- a/CodeEdit/Features/InspectorArea/Views/InspectorField.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// InspectorField.swift -// CodeEdit -// -// Created by Austin Condiff on 1/12/23. -// - -import SwiftUI - -struct InspectorField: View { - var label: String - let content: Content - - init(_ label: String, @ViewBuilder _ content: () -> Content) { - self.label = label - self.content = content() - } - - var body: some View { - HStack(alignment: .top, spacing: 5) { - Text(label) - .foregroundColor(.primary) - .fontWeight(.regular) - .font(.system(size: 10)) - .padding(.top, 3) - .frame(maxWidth: 72, alignment: .trailing) - VStack(alignment: .leading) { - content - } - .frame(maxWidth: .infinity) - } - } -} - -struct InspectorField_Previews: PreviewProvider { - static var previews: some View { - InspectorField("Section Label") { - Text("Preview") - } - } -} diff --git a/CodeEdit/Features/InspectorArea/Views/InspectorSection.swift b/CodeEdit/Features/InspectorArea/Views/InspectorSection.swift deleted file mode 100644 index 9d5d3ec776..0000000000 --- a/CodeEdit/Features/InspectorArea/Views/InspectorSection.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// InspectorSection.swift -// CodeEdit -// -// Created by Austin Condiff on 1/12/23. -// - -import SwiftUI - -struct InspectorSection: View { - var label: String - let content: Content - - init(_ label: String, @ViewBuilder _ content: () -> Content) { - self.label = label - self.content = content() - } - - var body: some View { - VStack(alignment: .leading, spacing: 11) { - Text(label) - .foregroundColor(.secondary) - .fontWeight(.bold) - .font(.system(size: 12)) - .padding(.leading, -1) - VStack(alignment: .trailing, spacing: 5) { - content - Divider() - } - } - } -} - -struct InspectorSection_Previews: PreviewProvider { - static var previews: some View { - InspectorSection("Section Label") { - Text("Preview") - } - } -} diff --git a/CodeEdit/Features/InspectorArea/Views/NoSelectionInspectorView.swift b/CodeEdit/Features/InspectorArea/Views/NoSelectionInspectorView.swift deleted file mode 100644 index 6e5d4f7ad3..0000000000 --- a/CodeEdit/Features/InspectorArea/Views/NoSelectionInspectorView.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// NoSelectionView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/04/18. -// - -import SwiftUI - -struct NoSelectionInspectorView: View { - var body: some View { - CEContentUnavailableView("No Selection") - } -} diff --git a/CodeEdit/Features/Keybindings/CommandManager.swift b/CodeEdit/Features/Keybindings/CommandManager.swift deleted file mode 100644 index 394f58494d..0000000000 --- a/CodeEdit/Features/Keybindings/CommandManager.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// CommandManager.swift -// -// Created by Alex on 23.05.2022. -// - -import Foundation - -/** -The object of this class intended to be a hearth of command palette. This object only exists as singleton. - In Order to access its instance use `CommandManager.shared` - -``` - /* To add or execute command see snipper below */ -let mgr = CommandManager.shared -let wrap = CommandClosureWrapper.init(closure: { - print("testing closure") -}) - -mgr.addCommand(name: "test", command: wrap) -mgr.executeCommand("test") - ``` - */ - -final class CommandManager: ObservableObject { - @Published private var commandsList: [String: Command] - - private init() { - commandsList = [:] - } - - static let shared: CommandManager = .init() - - func addCommand(name: String, title: String, id: String, command: CommandClosureWrapper) { - let command = Command.init(id: name, title: title, closureWrapper: command) - commandsList[id] = command - } - - var commands: [Command] { - return commandsList.map { $0.value } - } - - func executeCommand(_ id: String) { - commandsList[id]?.closureWrapper.call() - } -} - -/// Command struct uses as a wrapper for command. Used by command palette to call selected commands. -struct Command: Identifiable, Hashable { - - static func == (lhs: Command, rhs: Command) -> Bool { - return lhs.id == rhs.id - } - - static func < (lhs: Command, rhs: Command) -> Bool { - return false - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } - - let id: String - let title: String - let closureWrapper: CommandClosureWrapper -} - -/// A simple wrapper for command closure -struct CommandClosureWrapper { - - /// A typealias of interface used for command closure declaration - typealias WorkspaceClientClosure = () -> Void - - let workspaceClientClosure: WorkspaceClientClosure? - - /// Initializer for closure wrapper - /// - Parameter closure: Function that contains all logic to run command. - init(closure: @escaping WorkspaceClientClosure) { - self.workspaceClientClosure = closure - } - - func call() { - workspaceClientClosure?() - } -} diff --git a/CodeEdit/Features/Keybindings/KeybindingManager.swift b/CodeEdit/Features/Keybindings/KeybindingManager.swift deleted file mode 100644 index 8c3b1bfebc..0000000000 --- a/CodeEdit/Features/Keybindings/KeybindingManager.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// KeybindingManager.swift -// -// Created by Alex on 09.05.2022. -// - -import Foundation -import SwiftUI - -final class KeybindingManager { - /// Array which contains all available keyboard shortcuts - var keyboardShortcuts = [String: KeyboardShortcutWrapper]() - - private init() { - loadKeybindings() - } - - /// Static method to access singleton - static let shared: KeybindingManager = .init() - - // We need this fallback shortcut because optional shortcuts available only from 12.3, while we have target of 12.0x - var fallbackShortcut = KeyboardShortcutWrapper( - name: "?", - description: "Test", - context: "Fallback", - keybinding: "?", - modifier: "shift", - id: "fallback" - ) - - /// Adds new shortcut - func addNewShortcut(shortcut: KeyboardShortcutWrapper, name: String) { - keyboardShortcuts[name] = shortcut - } - - private func loadKeybindings() { - - let bindingsURL = Bundle.main.url(forResource: "default_keybindings.json", withExtension: nil) - if let json = try? Data(contentsOf: bindingsURL!) { - do { - let prefs = try JSONDecoder().decode([KeyboardShortcutWrapper].self, from: json) - for pref in prefs { - addNewShortcut(shortcut: pref, name: pref.id) - } - } catch { - print("error:\(error)") - } - } - return - } - - /// Get shortcut by name - /// - Parameter name: shortcut name - /// - Returns: KeyboardShortcutWrapper - func named(with name: String) -> KeyboardShortcutWrapper { - let foundElement = keyboardShortcuts[name] - return foundElement != nil ? foundElement! : fallbackShortcut - } - -} - -/// Wrapper for KeyboardShortcut. It contains name, keybindings. -struct KeyboardShortcutWrapper: Codable, Hashable { - var keyboardShortcut: KeyboardShortcut { - return KeyboardShortcut.init(.init(Character(keybinding)), modifiers: parsedModifier) - } - - var parsedModifier: EventModifiers { - switch modifier { - case "command": - return EventModifiers.command - case "shift": - return EventModifiers.shift - case "option": - return EventModifiers.option - case "control": - return EventModifiers.control - default: - return EventModifiers.command - } - } - var name: String - var description: String - var context: String - var keybinding: String - var modifier: String - var id: String - - enum CodingKeys: String, CodingKey { - case name - case description - case context - case keybinding - case modifier - case id - } - - init(name: String, description: String, context: String, keybinding: String, modifier: String, id: String) { - self.name = name - self.description = description - self.context = context - self.keybinding = keybinding - self.modifier = modifier - self.id = id - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - name = try container.decode(String.self, forKey: .name) - description = try container.decode(String.self, forKey: .description) - context = try container.decode(String.self, forKey: .context) - keybinding = try container.decode(String.self, forKey: .keybinding) - modifier = try container.decode(String.self, forKey: .modifier) - id = try container.decode(String.self, forKey: .id) - } -} diff --git a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift deleted file mode 100644 index c0a1329c14..0000000000 --- a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// ModifierKeysObserver.swift -// CodeEdit -// -// Created by Wouter Hennen on 04/03/2023. -// - -import SwiftUI -import Combine - -struct EventModifierEnvironmentKey: EnvironmentKey { - static var defaultValue: NSEvent.ModifierFlags = [] -} - -extension EnvironmentValues { - var modifierKeys: EventModifierEnvironmentKey.Value { - get { self[EventModifierEnvironmentKey.self] } - set { self[EventModifierEnvironmentKey.self] = newValue } - } -} - -extension NSEvent { - static func publisher(scope: Publisher.Scope, matching: EventTypeMask) -> Publisher { - return Publisher(scope: scope, matching: matching) - } - - public struct Publisher: Combine.Publisher { - public enum Scope { - case local, global - } - - public typealias Output = NSEvent - - public typealias Failure = Never - - let scope: Scope - let matching: EventTypeMask - - public func receive(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input { - let subscription = Subscription(scope: scope, matching: matching, subscriber: subscriber) - subscriber.receive(subscription: subscription) - } - } -} - -private extension NSEvent.Publisher { - final class Subscription where S.Input == NSEvent, S.Failure == Never { - fileprivate let lock = NSLock() - fileprivate var demand = Subscribers.Demand.none - private var monitor: Any? - - fileprivate let subscriberLock = NSRecursiveLock() - - init(scope: Scope, matching: NSEvent.EventTypeMask, subscriber: S) { - switch scope { - case .local: - self.monitor = NSEvent.addLocalMonitorForEvents(matching: matching) { [weak self] (event) -> NSEvent? in - self?.didReceive(event: event, subscriber: subscriber) - return event - } - - case .global: - self.monitor = NSEvent.addGlobalMonitorForEvents(matching: matching) { [weak self] in - self?.didReceive(event: $0, subscriber: subscriber) - } - } - - } - - deinit { - if let monitor { - NSEvent.removeMonitor(monitor) - } - } - - func didReceive(event: NSEvent, subscriber: S) { - let val = { () -> Subscribers.Demand in - lock.lock() - defer { lock.unlock() } - let before = demand - if demand > 0 { - demand -= 1 - } - return before - }() - - guard val > 0 else { return } - - let newDemand = subscriber.receive(event) - - lock.lock() - demand += newDemand - lock.unlock() - } - } -} - -extension NSEvent.Publisher.Subscription: Combine.Subscription { - func request(_ demand: Subscribers.Demand) { - lock.lock() - defer { lock.unlock() } - self.demand += demand - } - - func cancel() { - lock.lock() - defer { lock.unlock() } - guard let monitor else { return } - - self.monitor = nil - NSEvent.removeMonitor(monitor) - } -} diff --git a/CodeEdit/Features/Keybindings/default_keybindings.json b/CodeEdit/Features/Keybindings/default_keybindings.json deleted file mode 100644 index 11980cffd5..0000000000 --- a/CodeEdit/Features/Keybindings/default_keybindings.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "id": "copy", "name": "copy", "description": "Copy command", "context": "Global", "modifier": "command", "keybinding": "c" - }, - { - "id": "paste", "name": "paste", "description": "Copy command", "context": "Global", "modifier": "command", "keybinding": "c" - } - -] diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindModePicker.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindModePicker.swift deleted file mode 100644 index 308a15af5e..0000000000 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindModePicker.swift +++ /dev/null @@ -1,210 +0,0 @@ -// -// FindModePicker.swift -// CodeEdit -// -// Created by Austin Condiff on 12/7/23. -// - -import SwiftUI -import Combine - -struct FindModePicker: View { - var modes: [SearchModeModel] - private let onSelect: (SearchModeModel) -> Void - @Binding var selection: SearchModeModel - - private let isLastItem: Bool - - @Environment(\.colorScheme) - var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - @EnvironmentObject var workspace: WorkspaceDocument - - @State var position: NSPoint? - @State var isHovering: Bool = false - @State private var button: NSPopUpButton? - - init( - modes: [SearchModeModel], - selection: Binding, - onSelect: @escaping (SearchModeModel) -> Void, - isLastItem: Bool - ) { - self.modes = modes - self._selection = selection - self.onSelect = onSelect - self.isLastItem = isLastItem - } - - var body: some View { - NSPopUpButtonView(selection: $selection, isOn: selection != modes.first) { - let button = NSPopUpButton() - button.menu = FindModeMenu( - modes: modes, - onSelect: onSelect - ) - button.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) - button.isBordered = false - (button.cell as? NSPopUpButtonCell)?.arrowPosition = .noArrow - DispatchQueue.main.async { - self.button = button - } - return button - } - .fixedSize() - .frame(height: 21) - .padding(.trailing, 11) - .background { - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(isHovering ? 0.05 : 0) - .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4))) - HStack { - Spacer() - if isHovering { - chevronUpDown - .padding(.trailing, 4) - } else if !isLastItem { - chevron - .padding(.trailing, 3) - } - } - } - .padding(.vertical, 3) - .onHover { hover in - isHovering = hover - } - .onTapGesture { - button?.performClick(nil) - } - .opacity(activeState != .inactive ? 1 : 0.75) - } - - private var chevron: some View { - Image(systemName: "chevron.compact.right") - .font(.system(size: 9, weight: activeState != .inactive ? .medium : .bold, design: .default)) - .foregroundStyle(.secondary) - .scaleEffect(x: 1.30, y: 1.0, anchor: .center) - .imageScale(.large) - } - - private var chevronUpDown: some View { - VStack(spacing: 1) { - Image(systemName: "chevron.up") - Image(systemName: "chevron.down") - } - .font(.system(size: 6, weight: .bold, design: .default)) - .padding(.top, 0.5) - } - - struct NSPopUpButtonView: NSViewRepresentable where ItemType: Equatable { - @Binding var selection: ItemType - var isOn: Bool - var popupCreator: () -> NSPopUpButton - - typealias NSViewType = NSPopUpButton - - func makeNSView(context: NSViewRepresentableContext) -> NSPopUpButton { - let newPopupButton = popupCreator() - setPopUpFromSelection(newPopupButton, selection: selection) - if let menu = newPopupButton.menu { - context.coordinator.registerForChanges(in: menu) - } - return newPopupButton - } - - func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext) { - setPopUpFromSelection(nsView, selection: selection) - nsView.contentTintColor = isOn ? .controlAccentColor : .controlTextColor - } - - func setPopUpFromSelection(_ button: NSPopUpButton, selection: ItemType) { - let itemsList = button.itemArray - let matchedMenuItem = itemsList.filter { - ($0.representedObject as? ItemType) == selection - }.first - if matchedMenuItem != nil { - button.select(matchedMenuItem) - } - } - - func makeCoordinator() -> Coordinator { - return Coordinator(self) - } - - class Coordinator: NSObject { - var parent: NSPopUpButtonView - - var cancellable: AnyCancellable? - - init(_ parent: NSPopUpButtonView) { - self.parent = parent - super.init() - } - - func registerForChanges(in menu: NSMenu) { - cancellable = NotificationCenter.default - .publisher(for: NSMenu.didSendActionNotification, object: menu) - .sink { [weak self] notification in - if let menuItem = notification.userInfo?["MenuItem"] as? NSMenuItem, - let selection = menuItem as? ItemType { - self?.parent.selection = selection - } - } - } - } - } -} - -final class FindModeMenu: NSMenu, NSMenuDelegate { - private let modes: [SearchModeModel] - private let onSelect: (SearchModeModel) -> Void - - init( - modes: [SearchModeModel], - onSelect: @escaping (SearchModeModel) -> Void - ) { - self.modes = modes - self.onSelect = onSelect - super.init(title: "") - delegate = self - modes.forEach { mode in - let menuItem = FindModeMenuItem(mode: mode, onSelect: onSelect) - menuItem.onStateImage = nil - self.addItem(menuItem) - } - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -final class FindModeMenuItem: NSMenuItem { - private let mode: SearchModeModel - private let onSelect: (SearchModeModel) -> Void - - init( - mode: SearchModeModel, - onSelect: @escaping (SearchModeModel) -> Void - ) { - self.mode = mode - self.onSelect = onSelect - super.init(title: mode.title, action: #selector(handleSelect), keyEquivalent: "") - target = self - representedObject = mode - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc - func handleSelect() { - onSelect(mode) - } -} diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorForm.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorForm.swift deleted file mode 100644 index c1f748b587..0000000000 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorForm.swift +++ /dev/null @@ -1,279 +0,0 @@ -// -// SearchModeSelector.swift -// CodeEdit -// -// Created by Ziyuan Zhao on 2022/3/21. -// - -import SwiftUI - -struct FindNavigatorForm: View { - @ObservedObject private var state: WorkspaceDocument.SearchState - - @State private var selectedMode: [SearchModeModel] { - didSet { - // sync the variables, as selectedMode is an array - // and cannot be synced directly with @ObservedObject - state.selectedMode = selectedMode - } - } - - @State private var searchText: String = "" - @State private var replaceText: String = "" - @State private var includesText: String = "" - @State private var excludesText: String = "" - @State private var scoped: Bool = false - @State private var caseSensitive: Bool = false - @State private var preserveCase: Bool = false - @State private var scopedToOpenEditors: Bool = false - @State private var excludeSettings: Bool = true - - init(state: WorkspaceDocument.SearchState) { - self.state = state - selectedMode = state.selectedMode - } - - private func getMenuList(_ index: Int) -> [SearchModeModel] { - index == 0 ? SearchModeModel.SearchModes : selectedMode[index - 1].children - } - - private func onSelectMenuItem(_ index: Int, searchMode: SearchModeModel) { - var newSelectedMode: [SearchModeModel] = [] - - switch index { - case 0: - newSelectedMode.append(searchMode) - self.updateSelectedMode(searchMode, searchModel: &newSelectedMode) - self.selectedMode = newSelectedMode - case 1: - if let firstMode = selectedMode.first { - newSelectedMode.append(contentsOf: [firstMode, searchMode]) - if let thirdMode = searchMode.children.first { - if let selectedThirdMode = selectedMode.third, searchMode.children.contains(selectedThirdMode) { - newSelectedMode.append(selectedThirdMode) - } else { - newSelectedMode.append(thirdMode) - } - } - } - self.selectedMode = newSelectedMode - case 2: - if let firstMode = selectedMode.first, let secondMode = selectedMode.second { - newSelectedMode.append(contentsOf: [firstMode, secondMode, searchMode]) - } - self.selectedMode = newSelectedMode - default: - return - } - } - - private func updateSelectedMode(_ searchMode: SearchModeModel, searchModel: inout [SearchModeModel]) { - if let secondMode = searchMode.children.first { - if let selectedSecondMode = selectedMode.second, searchMode.children.contains(selectedSecondMode) { - searchModel.append(contentsOf: selectedMode.dropFirst()) - } else { - searchModel.append(secondMode) - if let thirdMode = secondMode.children.first, let selectedThirdMode = selectedMode.third { - if secondMode.children.contains(selectedThirdMode) { - searchModel.append(selectedThirdMode) - } else { - searchModel.append(thirdMode) - } - } - } - } - } - - private var chevron: some View { - Image(systemName: "chevron.compact.right") - .foregroundStyle(.tertiary) - .imageScale(.large) - } - - var body: some View { - VStack { - HStack { - HStack(spacing: 0) { - ForEach(0.. 1 ? self[1] : nil - } - - var third: Element? { - self.count > 2 ? self[2] : nil - } -} diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorIndexBar.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorIndexBar.swift deleted file mode 100644 index c7c903d359..0000000000 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorIndexBar.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// FindNavigatorIndexBar.swift -// CodeEdit -// -// Created by Khan Winter on 12/4/23. -// - -import SwiftUI - -struct FindNavigatorIndexBar: View { - @ObservedObject private var state: WorkspaceDocument.SearchState - @State private var progress: Double = 0.0 - @State private var shouldShow: Bool = false - - init(state: WorkspaceDocument.SearchState) { - self.state = state - } - - var body: some View { - Group { - if shouldShow { - HStack(alignment: .center) { - ProgressView(value: progress, total: 1.0) { - EmptyView() - } currentValueLabel: { - HStack { - Text("Indexing \(Int(progress * 100))%") - .font(.system(size: 10)) - .animation(.none) - } - } - // swiftlint:disable:next line_length - .help("Indexing current workspace files for search. Searches performed while indexing may return incomplete results.") - } - .transition(.asymmetric(insertion: .identity, removal: .move(edge: .top).combined(with: .opacity))) - } - } - .onAppear { - updateWithNewStatus(state.indexStatus) - } - .onReceive(state.$indexStatus) { newStatus in - updateWithNewStatus(newStatus) - } - } - - /// Updates the bar with a new status update. - /// - Parameter status: The new status. - private func updateWithNewStatus(_ status: WorkspaceDocument.SearchState.IndexStatus) { - switch status { - case .none: - self.progress = 0.0 - shouldShow = false - case .indexing(let progress): - if shouldShow { - withAnimation { - self.progress = progress - } - } else { - shouldShow = true - self.progress = progress - } - case .done: - self.progress = 1.0 - withAnimation(.default.delay(0.75)) { - shouldShow = false - } - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift deleted file mode 100644 index 501686b721..0000000000 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift +++ /dev/null @@ -1,240 +0,0 @@ -// -// FindNavigatorListViewController.swift -// CodeEdit -// -// Created by Khan Winter on 7/7/22. -// - -import SwiftUI - -final class FindNavigatorListViewController: NSViewController { - - public var workspace: WorkspaceDocument - public var selectedItem: Any? - - private var searchItems: [SearchResultModel] = [] - private var scrollView: NSScrollView! - private var outlineView: NSOutlineView! - private let prefs = Settings.shared.preferences - private var collapsedRows: Set = [] - - var rowHeight: Double = 22 { - didSet { - outlineView?.reloadData() - } - } - - /// Setup the `scrollView` and `outlineView` - override func loadView() { - self.scrollView = NSScrollView() - self.view = scrollView - - self.outlineView = NSOutlineView() - self.outlineView.dataSource = self - self.outlineView.delegate = self - self.outlineView.headerView = nil - self.outlineView.lineBreakMode = .byTruncatingTail - - let column = NSTableColumn(identifier: .init(rawValue: "Cell")) - column.title = "Cell" - outlineView.addTableColumn(column) - - self.scrollView.documentView = outlineView - self.scrollView.contentView.automaticallyAdjustsContentInsets = false - self.scrollView.contentView.contentInsets = .init(top: 0, left: 0, bottom: 0, right: 0) - } - - init(workspace: WorkspaceDocument) { - self.workspace = workspace - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init?(coder: NSCoder) not implemented by FindNavigatorListViewController") - } - - override var acceptsFirstResponder: Bool { true } - - /// Sets the search items for the view without loading anything. - /// - Parameter searchItems: The search items to set. - public func setSearchResults(_ searchItems: [SearchResultModel]) { - self.searchItems = searchItems - } - - /// Updates the view with new search results and updates the UI. - /// - Parameter searchItems: The search items to set. - public func updateNewSearchResults(_ searchItems: [SearchResultModel]) { - self.searchItems = searchItems - outlineView.reloadData() - outlineView.expandItem(nil, expandChildren: true) - - if let selectedItem { - selectSearchResult(selectedItem) - } - } - - override func keyUp(with event: NSEvent) { - if event.charactersIgnoringModifiers == String(NSEvent.SpecialKey.delete.unicodeScalar) { - deleteSelectedItem() - } - super.keyUp(with: event) - } - - /// Removes the selected item, called in response to an action like the backspace - /// character - private func deleteSelectedItem() { - let selectedRow = outlineView.selectedRow - guard selectedRow >= 0, - let selectedItem = outlineView.item(atRow: selectedRow) else { return } - - if selectedItem is SearchResultMatchModel { - guard let parent = outlineView.parent(forItem: selectedItem) else { return } - - // Remove the item from the search results - let parentIndex = outlineView.childIndex(forItem: parent) - let childIndex = outlineView.childIndex(forItem: selectedItem) - searchItems[parentIndex].lineMatches.remove(at: childIndex) - - // If this was the last child, we need to remove the parent or we'll - // hit an exception - if searchItems[parentIndex].lineMatches.isEmpty { - searchItems.remove(at: parentIndex) - outlineView.removeItems(at: IndexSet([parentIndex]), inParent: nil) - } else { - outlineView.removeItems(at: IndexSet([childIndex]), inParent: parent) - } - } else { - let index = outlineView.childIndex(forItem: selectedItem) - searchItems.remove(at: index) - outlineView.removeItems(at: IndexSet([index]), inParent: nil) - } - - outlineView.selectRowIndexes(IndexSet([selectedRow]), byExtendingSelection: false) - } - - public func selectSearchResult(_ selectedItem: Any) { - let index = outlineView.row(forItem: selectedItem) - guard index >= 0 && index != outlineView.selectedRow else { return } - outlineView.selectRowIndexes(IndexSet([index]), byExtendingSelection: false) - } -} - -// MARK: - NSOutlineViewDataSource - -extension FindNavigatorListViewController: NSOutlineViewDataSource { - - func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { - if let item = item as? SearchResultModel { - return item.lineMatches.count - } - return searchItems.count - } - - func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { - if let item = item as? SearchResultModel { - return item.lineMatches[index] - } - return searchItems[index] - } - - func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { - if item is SearchResultModel { - return true - } - return false - } - -} - -// MARK: - NSOutlineViewDelegate - -extension FindNavigatorListViewController: NSOutlineViewDelegate { - - func outlineView( - _ outlineView: NSOutlineView, - shouldShowCellExpansionFor tableColumn: NSTableColumn?, - item: Any - ) -> Bool { - return item as? SearchResultModel != nil - } - - func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool { - true - } - - func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { - guard let tableColumn else { return nil } - if let item = item as? SearchResultMatchModel { - let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: CGFloat.greatestFiniteMagnitude) - return FindNavigatorListMatchCell(frame: frameRect, matchItem: item) - } else { - let frameRect = NSRect( - x: 0, - y: 0, - width: tableColumn.width, - height: prefs.general.projectNavigatorSize.rowHeight - ) - let view = ProjectNavigatorTableViewCell( - frame: frameRect, - item: (item as? SearchResultModel)?.file, - isEditable: false - ) - // We're using a medium label for file names b/c it makes it easier to - // distinguish quickly which results are from which files. - view.label.font = .systemFont(ofSize: 13, weight: .medium) - return view - } - } - - func outlineViewSelectionDidChange(_ notification: Notification) { - guard let outlineView = notification.object as? NSOutlineView else { - return - } - - let selectedIndex = outlineView.selectedRow - - if let item = outlineView.item(atRow: selectedIndex) as? SearchResultMatchModel { - let selectedMatch = self.selectedItem as? SearchResultMatchModel - if selectedItem == nil || selectedMatch != item { - self.selectedItem = item - workspace.editorManager.openTab(item: item.file) - } - } else if let item = outlineView.item(atRow: selectedIndex) as? SearchResultModel { - let selectedFile = self.selectedItem as? SearchResultModel - if selectedItem == nil || selectedFile != item { - self.selectedItem = item - workspace.editorManager.openTab(item: item.file) - } - } - } - - func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { - if let item = item as? SearchResultMatchModel { - let tempView = NSTextField(wrappingLabelWithString: item.attributedLabel().string) - tempView.allowsDefaultTighteningForTruncation = false - tempView.cell?.truncatesLastVisibleLine = true - tempView.cell?.wraps = true - tempView.maximumNumberOfLines = 3 - tempView.attributedStringValue = item.attributedLabel() - tempView.layout() - let width = outlineView.frame.width - outlineView.indentationPerLevel*2 - 24 - return tempView.sizeThatFits( - NSSize(width: width, height: CGFloat.greatestFiniteMagnitude) - ).height + 8 - } else { - return rowHeight - } - } - - func outlineViewColumnDidResize(_ notification: Notification) { - let indexes = IndexSet(integersIn: 0.. FindNavigatorListViewController { - let controller = FindNavigatorListViewController(workspace: workspace) - controller.setSearchResults(workspace.searchState?.searchResult ?? []) - controller.rowHeight = projectNavigatorSize.rowHeight - context.coordinator.controller = controller - return controller - } - - func updateNSViewController(_ nsViewController: FindNavigatorListViewController, context: Context) { - nsViewController.updateNewSearchResults( - workspace.searchState?.searchResult ?? [] - ) - if nsViewController.rowHeight != projectNavigatorSize.rowHeight { - nsViewController.rowHeight = projectNavigatorSize.rowHeight - } - return - } - - func makeCoordinator() -> Coordinator { - Coordinator( - state: workspace.searchState, - controller: nil - ) - } - - class Coordinator: NSObject { - init(state: WorkspaceDocument.SearchState?, controller: FindNavigatorListViewController?) { - self.controller = controller - super.init() - self.listener = state? - .$searchResult - .sink(receiveValue: { [weak self] searchResults in - self?.controller?.updateNewSearchResults(searchResults) - }) - } - - var listener: AnyCancellable? - var controller: FindNavigatorListViewController? - - deinit { - controller = nil - listener?.cancel() - listener = nil - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift deleted file mode 100644 index 36d173b304..0000000000 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SourceControlToolbarBottom.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI - -struct FindNavigatorToolbarBottom: View { - @State private var text = "" - - var body: some View { - HStack(spacing: 2) { - PaneTextField( - "Filter", - text: $text, - leadingAccessories: { - Image( - systemName: text.isEmpty - ? "line.3.horizontal.decrease.circle" - : "line.3.horizontal.decrease.circle.fill" - ) - .foregroundStyle( - text.isEmpty - ? Color(nsColor: .secondaryLabelColor) - : Color(nsColor: .controlAccentColor) - ) - .padding(.leading, 4) - }, - clearable: true - ) - } - .frame(height: 28, alignment: .center) - .frame(maxWidth: .infinity) - .padding(.horizontal, 5) - .overlay(alignment: .top) { - Divider() - .opacity(0) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift deleted file mode 100644 index 1b956043cc..0000000000 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// FindNavigatorView.swift -// CodeEdit -// -// Created by Ziyuan Zhao on 2022/3/20. -// - -import SwiftUI - -struct FindNavigatorView: View { - @EnvironmentObject private var workspace: WorkspaceDocument - - private var state: WorkspaceDocument.SearchState { - workspace.searchState ?? .init(workspace) - } - - @State private var foundFilesCount: Int = 0 - @State private var searchResultCount: Int = 0 - @State private var findNavigatorStatus: WorkspaceDocument.SearchState.FindNavigatorStatus = .none - @State private var findResultMessage: String? - - var body: some View { - VStack { - VStack { - FindNavigatorForm(state: state) - FindNavigatorIndexBar(state: state) - } - .padding(.horizontal, 10) - .padding(.vertical, 5) - - Divider() - - if findNavigatorStatus == .found { - HStack(alignment: .center) { - Text("\(self.searchResultCount) results in \(self.foundFilesCount) files") - .font(.system(size: 10)) - } - - Divider() - } - - switch self.findNavigatorStatus { - case .none: - Spacer() - case .searching: - VStack { - ProgressView() - .padding() - - Text("Searching") - .foregroundStyle(.tertiary) - .font(.title3) - } - .frame(maxHeight: .infinity) - case .replacing: - VStack { - ProgressView() - .padding() - - Text("Replacing") - .foregroundStyle(.tertiary) - .font(.title3) - } - .frame(maxHeight: .infinity) - case .found: - if self.searchResultCount == 0 { - CEContentUnavailableView( - "No Results", - description: "No Results for \"\(state.searchQuery)\" in Project", - systemImage: "exclamationmark.magnifyingglass" - ) - } else { - FindNavigatorResultList() - } - case .replaced(let updatedFiles): - CEContentUnavailableView( - "Replaced", - description: "Successfully replaced terms across \(updatedFiles) files", - systemImage: "checkmark.circle.fill" - ) - case .failed(let errorMessage): - CEContentUnavailableView( - "An Error Occurred", - description: "\(errorMessage)", - systemImage: "xmark.octagon.fill" - ) - } - } - .safeAreaInset(edge: .bottom, spacing: 0) { - FindNavigatorToolbarBottom() - } - .onReceive(state.objectWillChange) { _ in - self.searchResultCount = state.searchResultsCount - self.foundFilesCount = state.searchResult.count - } - .onReceive(state.$findNavigatorStatus, perform: { value in - self.findNavigatorStatus = value - }) - } -} diff --git a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift b/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift deleted file mode 100644 index 63839d9a04..0000000000 --- a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// NavigatorTab.swift -// CodeEdit -// -// Created by Wouter Hennen on 02/06/2023. -// - -import SwiftUI -import CodeEditKit -import ExtensionFoundation - -enum NavigatorTab: AreaTab { - case project - case sourceControl - case search - case uiExtension(endpoint: AppExtensionIdentity, data: ResolvedSidebar.SidebarStore) - - var systemImage: String { - switch self { - case .project: - return "folder" - case .sourceControl: - return "vault" - case .search: - return "magnifyingglass" - case .uiExtension(_, let data): - return data.icon ?? "e.square" - } - } - - var id: String { - if case .uiExtension(let endpoint, let data) = self { - return endpoint.bundleIdentifier + data.sceneID - } - return title - } - - var title: String { - switch self { - case .project: - return "Project" - case .sourceControl: - return "Version Control" - case .search: - return "Search" - case .uiExtension(_, let data): - return data.help ?? data.sceneID - } - } - - var body: some View { - switch self { - case .project: - ProjectNavigatorView() - case .sourceControl: - SourceControlNavigatorView() - case .search: - FindNavigatorView() - case let .uiExtension(endpoint, data): - ExtensionSceneView(with: endpoint, sceneID: data.sceneID) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/OutlineView/FileSystemTableViewCell.swift b/CodeEdit/Features/NavigatorArea/OutlineView/FileSystemTableViewCell.swift deleted file mode 100644 index 2998168009..0000000000 --- a/CodeEdit/Features/NavigatorArea/OutlineView/FileSystemTableViewCell.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// FileSystemOutlineView.swift -// CodeEdit -// -// Created by TAY KAI QUAN on 14/8/22. -// - -import SwiftUI - -class FileSystemTableViewCell: StandardTableViewCell { - - var fileItem: CEWorkspaceFile! - - var changeLabelLargeWidth: NSLayoutConstraint! - var changeLabelSmallWidth: NSLayoutConstraint! - - private let prefs = Settings.shared.preferences.general - - /// Initializes the `OutlineTableViewCell` with an `icon` and `label` - /// Both the icon and label will be colored, and sized based on the user's preferences. - /// - Parameters: - /// - frameRect: The frame of the cell. - /// - item: The file item the cell represents. - /// - isEditable: Set to true if the user should be able to edit the file name. - init(frame frameRect: NSRect, item: CEWorkspaceFile?, isEditable: Bool = true) { - super.init(frame: frameRect, isEditable: isEditable) - - if let item = item { - addIcon(item: item) - } - addModel() - } - - override func configLabel(label: NSTextField, isEditable: Bool) { - super.configLabel(label: label, isEditable: isEditable) - label.delegate = self - } - - func addIcon(item: CEWorkspaceFile) { - fileItem = item - icon.image = item.nsIcon - icon.contentTintColor = color(for: item) - toolTip = item.labelFileName() - label.stringValue = item.labelFileName() - } - - func addModel() { - secondaryLabel.stringValue = fileItem.gitStatus?.description ?? "" - if secondaryLabel.stringValue == "?" { secondaryLabel.stringValue = "A" } - } - - /// *Not Implemented* - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - fatalError(""" - init(frame: ) isn't implemented on `OutlineTableViewCell`. - Please use `.init(frame: NSRect, item: FileSystemClient.FileItem?) - """) - } - - /// *Not Implemented* - required init?(coder: NSCoder) { - fatalError(""" - init?(coder: NSCoder) isn't implemented on `OutlineTableViewCell`. - Please use `.init(frame: NSRect, item: FileSystemClient.FileItem?) - """) - } - - /// Returns the font size for the current row height. Defaults to `13.0` - private var fontSize: Double { - switch self.frame.height { - case 20: return 11 - case 22: return 13 - case 24: return 14 - default: return 13 - } - } - - /// Get the appropriate color for the items icon depending on the users preferences. - /// - Parameter item: The `FileItem` to get the color for - /// - Returns: A `NSColor` for the given `FileItem`. - func color(for item: CEWorkspaceFile) -> NSColor { - if !item.isFolder && prefs.fileIconStyle == .color { - return NSColor(item.iconColor) - } else { - return NSColor(named: "FolderBlue")! - } - } -} - -let errorRed = NSColor(red: 1, green: 0, blue: 0, alpha: 0.2) -extension FileSystemTableViewCell: NSTextFieldDelegate { - func controlTextDidChange(_ obj: Notification) { - label.backgroundColor = fileItem.validateFileName(for: label?.stringValue ?? "") ? .none : errorRed - } - func controlTextDidEndEditing(_ obj: Notification) { - label.backgroundColor = fileItem.validateFileName(for: label?.stringValue ?? "") ? .none : errorRed - if fileItem.validateFileName(for: label?.stringValue ?? "") { - let newURL = fileItem.url.deletingLastPathComponent().appendingPathComponent(label?.stringValue ?? "") - workspace?.workspaceFileManager?.move(file: fileItem, to: newURL) - } else { - label?.stringValue = fileItem.labelFileName() - } - } -} - -extension String { - var isValidFilename: Bool { - let regex = "[^:]" - let testString = NSPredicate(format: "SELF MATCHES %@", regex) - return !testString.evaluate(with: self) - } -} diff --git a/CodeEdit/Features/NavigatorArea/OutlineView/StandardTableViewCell.swift b/CodeEdit/Features/NavigatorArea/OutlineView/StandardTableViewCell.swift deleted file mode 100644 index e4611b308a..0000000000 --- a/CodeEdit/Features/NavigatorArea/OutlineView/StandardTableViewCell.swift +++ /dev/null @@ -1,216 +0,0 @@ -// -// StandardTableViewCell.swift -// CodeEdit -// -// Created by TAY KAI QUAN on 17/8/22. -// - -import SwiftUI - -class StandardTableViewCell: NSTableCellView { - - var label: NSTextField! - var secondaryLabel: NSTextField! - var icon: NSImageView! - - var workspace: WorkspaceDocument? - - var secondaryLabelRightAlignmed: Bool = true { - didSet { - resizeSubviews(withOldSize: .zero) - } - } - - private let prefs = Settings.shared.preferences.general - - /// Initializes the `TableViewCell` with an `icon` and `label` - /// Both the icon and label will be colored, and sized based on the user's preferences. - /// - Parameters: - /// - frameRect: The frame of the cell. - /// - item: The file item the cell represents. - /// - isEditable: Set to true if the user should be able to edit the file name. - init(frame frameRect: NSRect, isEditable: Bool = true) { - super.init(frame: frameRect) - setupViews(frame: frameRect, isEditable: isEditable) - } - - // Default init, assumes isEditable to be false - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - setupViews(frame: frameRect, isEditable: false) - } - - private func setupViews(frame frameRect: NSRect, isEditable: Bool) { - // Create the label - label = createLabel() - configLabel(label: self.label, isEditable: isEditable) - self.textField = label - - // Create the secondary label - secondaryLabel = createSecondaryLabel() - configSecondaryLabel(secondaryLabel: secondaryLabel) - - // Create the icon - icon = createIcon() - configIcon(icon: icon) - addSubview(icon) - imageView = icon - - // add constraints - createConstraints(frame: frameRect) - addSubview(label) - addSubview(secondaryLabel) - addSubview(icon) - } - - // MARK: Create and config stuff - func createLabel() -> NSTextField { - return SpecialSelectTextField(frame: .zero) - } - - func configLabel(label: NSTextField, isEditable: Bool) { - label.translatesAutoresizingMaskIntoConstraints = false - label.drawsBackground = false - label.isBordered = false - label.isEditable = isEditable - label.isSelectable = isEditable - label.layer?.cornerRadius = 10.0 - label.font = .labelFont(ofSize: fontSize) - label.lineBreakMode = .byTruncatingMiddle - } - - func createSecondaryLabel() -> NSTextField { - return NSTextField(frame: .zero) - } - - func configSecondaryLabel(secondaryLabel: NSTextField) { - secondaryLabel.translatesAutoresizingMaskIntoConstraints = false - secondaryLabel.drawsBackground = false - secondaryLabel.isBordered = false - secondaryLabel.isEditable = false - secondaryLabel.isSelectable = false - secondaryLabel.layer?.cornerRadius = 10.0 - secondaryLabel.font = .systemFont(ofSize: fontSize-2, weight: .bold) - secondaryLabel.alignment = .center - secondaryLabel.textColor = NSColor(Color.secondary) - } - - func createIcon() -> NSImageView { - return NSImageView(frame: .zero) - } - - func configIcon(icon: NSImageView) { - icon.translatesAutoresizingMaskIntoConstraints = false - icon.symbolConfiguration = .init(pointSize: fontSize, weight: .regular, scale: .medium) - } - - func createConstraints(frame frameRect: NSRect) { - resizeSubviews(withOldSize: .zero) - } - - let iconWidth: CGFloat = 22 - override func resizeSubviews(withOldSize oldSize: NSSize) { - super.resizeSubviews(withOldSize: oldSize) - - icon.frame = NSRect( - x: 2, - y: 4, - width: iconWidth, - height: frame.height - ) - // center align the image - if let alignmentRect = icon.image?.alignmentRect { - icon.frame = NSRect( - x: (iconWidth - alignmentRect.width) / 2, - y: 4, - width: alignmentRect.width, - height: frame.height - ) - } - - // right align the secondary label - if secondaryLabelRightAlignmed { - let secondLabelWidth = secondaryLabel.frame.size.width - let newSize = secondaryLabel.sizeThatFits( - CGSize(width: secondLabelWidth, height: CGFloat.greatestFiniteMagnitude) - ) - // somehow, a width of 0 makes it resize properly. - secondaryLabel.frame = NSRect( - x: frame.width - newSize.width, - y: 3.5, - width: 0, - height: newSize.height - ) - - label.frame = NSRect( - x: iconWidth + 2, - y: 3.5, - width: secondaryLabel.frame.minX - icon.frame.maxX - 5, - height: 25 - ) - - // put the secondary label right after the primary label - } else { - let mainLabelWidth = label.frame.size.width - let newSize = label.sizeThatFits(CGSize(width: mainLabelWidth, height: CGFloat.greatestFiniteMagnitude)) - label.frame = NSRect( - x: iconWidth + 2, - y: 2.5, - width: newSize.width, - height: 25 - ) - secondaryLabel.frame = NSRect( - x: label.frame.maxX + 2, - y: 2.5, - width: frame.width - label.frame.maxX - 2, - height: 25 - ) - } - } - - /// *Not Implemented* - required init?(coder: NSCoder) { - fatalError(""" - init?(coder: NSCoder) isn't implemented on `StandardTableViewCell`. - Please use `.init(frame: NSRect, isEditable: Bool) - """) - } - - /// Returns the font size for the current row height. Defaults to `13.0` - private var fontSize: Double { - switch self.frame.height { - case 20: return 11 - case 22: return 13 - case 24: return 14 - default: return 13 - } - } - - class SpecialSelectTextField: NSTextField { - override func becomeFirstResponder() -> Bool { - let range = NSRange( - location: 0, - length: stringValue.distance( - from: stringValue.startIndex, - to: stringValue.lastIndex(of: ".") ?? stringValue.endIndex - ) - ) - selectText(self) - let editor = currentEditor() - editor?.selectedRange = range - return true - } - - override func textDidBeginEditing(_ notification: Notification) { - super.textDidBeginEditing(notification) - wantsLayer = true - layer?.backgroundColor = NSColor.textBackgroundColor.cgColor - } - - override func textDidEndEditing(_ notification: Notification) { - super.textDidEndEditing(notification) - wantsLayer = false - layer?.backgroundColor = nil - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/OutlineView/TextTableViewCell.swift b/CodeEdit/Features/NavigatorArea/OutlineView/TextTableViewCell.swift deleted file mode 100644 index d18e1cb1b2..0000000000 --- a/CodeEdit/Features/NavigatorArea/OutlineView/TextTableViewCell.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// TextTableViewCell.swift -// CodeEdit -// -// Created by TAY KAI QUAN on 11/9/22. -// - -import SwiftUI - -class TextTableViewCell: NSTableCellView { - - var label: NSTextField! - - init(frame frameRect: NSRect, isEditable: Bool = true, startingText: String = "") { - super.init(frame: frameRect) - setupViews(frame: frameRect, isEditable: isEditable) - self.label.stringValue = startingText - } - - // Default init, assumes isEditable to be false - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - setupViews(frame: frameRect, isEditable: false) - } - - private func setupViews(frame frameRect: NSRect, isEditable: Bool) { - // Create the label - label = createLabel() - configLabel(label: self.label, isEditable: isEditable) - self.textField = label - - addSubview(label) - createConstraints(frame: frameRect) - } - - // MARK: Create and config stuff - func createLabel() -> NSTextField { - return NSTextField(frame: .zero) - } - - func configLabel(label: NSTextField, isEditable: Bool) { - label.translatesAutoresizingMaskIntoConstraints = false - label.drawsBackground = false - label.isBordered = false - label.isEditable = isEditable - label.isSelectable = isEditable - label.layer?.cornerRadius = 10.0 - label.font = .boldSystemFont(ofSize: fontSize) - label.lineBreakMode = .byTruncatingMiddle - label.textColor = NSColor.textColor - label.alphaValue = 0.7 - } - - func createConstraints(frame frameRect: NSRect) { - resizeSubviews(withOldSize: .zero) - } - - override func resizeSubviews(withOldSize oldSize: NSSize) { - super.resizeSubviews(withOldSize: oldSize) - label.frame = NSRect( - x: 2, - y: 2.5, - width: frame.width - 4, - height: 25 - ) - } - - /// Returns the font size for the current row height. Defaults to `13.0` - private var fontSize: Double { - switch self.frame.height { - case 20: return 11 - case 22: return 13 - case 24: return 14 - default: return 13 - } - } - - /// *Not Implemented* - required init(coder: NSCoder) { - fatalError(""" - init?(coder: NSCoder) isn't implemented on `TextTableViewCell`. - Please use `.init(frame: NSRect, isEditable: Bool) - """) - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift deleted file mode 100644 index 1cbe27af16..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift +++ /dev/null @@ -1,273 +0,0 @@ -// -// OutlineMenu.swift -// CodeEdit -// -// Created by Lukas Pistrol on 07.04.22. -// - -import SwiftUI -import UniformTypeIdentifiers - -/// A subclass of `NSMenu` implementing the contextual menu for the project navigator -final class ProjectNavigatorMenu: NSMenu { - - /// The item to show the contextual menu for - var item: CEWorkspaceFile? - - /// The workspace, for opening the item - var workspace: WorkspaceDocument? - - var outlineView: NSOutlineView - - init(sender: NSOutlineView) { - outlineView = sender - super.init(title: "Options") - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - /// Creates a `NSMenuItem` depending on the given arguments - /// - Parameters: - /// - title: The title of the menu item - /// - action: A `Selector` or `nil` of the action to perform. - /// - key: A `keyEquivalent` of the menu item. Defaults to an empty `String` - /// - Returns: A `NSMenuItem` which has the target `self` - private func menuItem(_ title: String, action: Selector?, key: String = "") -> NSMenuItem { - let mItem = NSMenuItem(title: title, action: action, keyEquivalent: key) - mItem.target = self - - return mItem - } - - /// Setup the menu and disables certain items when `isFile` is false - /// - Parameter isFile: A flag indicating that the item is a file instead of a directory - private func setupMenu() { - guard let item else { return } - let showInFinder = menuItem("Show in Finder", action: #selector(showInFinder)) - - let openInTab = menuItem("Open in Tab", action: #selector(openInTab)) - let openInNewWindow = menuItem("Open in New Window", action: nil) - let openExternalEditor = menuItem("Open with External Editor", action: #selector(openWithExternalEditor)) - let openAs = menuItem("Open As", action: nil) - - let showFileInspector = menuItem("Show File Inspector", action: nil) - - let newFile = menuItem("New File...", action: #selector(newFile)) - let newFolder = menuItem("New Folder", action: #selector(newFolder)) - - let rename = menuItem("Rename", action: #selector(renameFile)) - let delete = menuItem("Delete", action: - item.url != workspace?.workspaceFileManager?.folderUrl - ? #selector(delete) : nil) - - let duplicate = menuItem("Duplicate \(item.isFolder ? "Folder" : "File")", action: #selector(duplicate)) - - let sortByName = menuItem("Sort by Name", action: nil) - sortByName.isEnabled = item.isFolder - - let sortByType = menuItem("Sort by Type", action: nil) - sortByType.isEnabled = item.isFolder - - let sourceControl = menuItem("Source Control", action: nil) - - items = [ - showInFinder, - NSMenuItem.separator(), - openInTab, - openInNewWindow, - openExternalEditor, - openAs, - NSMenuItem.separator(), - showFileInspector, - NSMenuItem.separator(), - newFile, - newFolder, - NSMenuItem.separator(), - rename, - delete, - duplicate, - NSMenuItem.separator(), - sortByName, - sortByType, - NSMenuItem.separator(), - sourceControl, - ] - - setSubmenu(openAsMenu(item: item), for: openAs) - setSubmenu(sourceControlMenu(item: item), for: sourceControl) - } - - /// Submenu for **Open As** menu item. - private func openAsMenu(item: CEWorkspaceFile) -> NSMenu { - let openAsMenu = NSMenu(title: "Open As") - func getMenusItems() -> ([NSMenuItem], [NSMenuItem]) { - // Use UTType to distinguish between bundle file and user-browsable directory - // The isDirectory property is not accurate on this. - guard let type = item.contentType else { return ([.none()], []) } - if type.conforms(to: .folder) { - return ([.none()], []) - } - var primaryItems = [NSMenuItem]() - if type.conforms(to: .sourceCode) { - primaryItems.append(.sourceCode()) - } - if type.conforms(to: .propertyList) { - primaryItems.append(.propertyList()) - } - if type.conforms(to: UTType(filenameExtension: "xcassets")!) { - primaryItems.append(NSMenuItem(title: "Asset Catalog Document", action: nil, keyEquivalent: "")) - } - if type.conforms(to: UTType(filenameExtension: "xib")!) { - primaryItems.append(NSMenuItem(title: "Interface Builder XIB Document", action: nil, keyEquivalent: "")) - } - if type.conforms(to: UTType(filenameExtension: "xcodeproj")!) { - primaryItems.append(NSMenuItem(title: "Xcode Project", action: nil, keyEquivalent: "")) - } - var secondaryItems = [NSMenuItem]() - if type.conforms(to: .text) { - secondaryItems.append(.asciiPropertyList()) - secondaryItems.append(.hex()) - } - - // FIXME: Update the quickLook condition - if type.conforms(to: .data) { - secondaryItems.append(.quickLook()) - } - - return (primaryItems, secondaryItems) - } - let (primaryItems, secondaryItems) = getMenusItems() - for item in primaryItems { - openAsMenu.addItem(item) - } - if !secondaryItems.isEmpty { - openAsMenu.addItem(.separator()) - } - for item in secondaryItems { - openAsMenu.addItem(item) - } - return openAsMenu - } - - /// Submenu for **Source Control** menu item. - private func sourceControlMenu(item: CEWorkspaceFile) -> NSMenu { - let sourceControlMenu = NSMenu(title: "Source Control") - sourceControlMenu.addItem( - withTitle: "Commit \"\(String(describing: item.fileName))\"...", - action: nil, - keyEquivalent: "" - ) - sourceControlMenu.addItem(.separator()) - sourceControlMenu.addItem(withTitle: "Discard Changes...", action: nil, keyEquivalent: "") - sourceControlMenu.addItem(.separator()) - sourceControlMenu.addItem(withTitle: "Add Selected Files", action: nil, keyEquivalent: "") - sourceControlMenu.addItem(withTitle: "Mark Selected Files as Resolved", action: nil, keyEquivalent: "") - - return sourceControlMenu - } - - /// Updates the menu for the selected item and hides it if no item is provided. - override func update() { - removeAllItems() - setupMenu() - } - - /// Action that opens **Finder** at the items location. - @objc - private func showInFinder() { - item?.showInFinder() - } - - /// Action that opens the item, identical to clicking it. - @objc - private func openInTab() { - if let item { - workspace?.editorManager.openTab(item: item) - } - } - - /// Action that opens in an external editor - @objc - private func openWithExternalEditor() { - item?.openWithExternalEditor() - } - - // TODO: allow custom file names - /// Action that creates a new untitled file - @objc - private func newFile() { - guard let item else { return } - workspace?.workspaceFileManager?.addFile(fileName: "untitled", toFile: item) - outlineView.expandItem(item.isFolder ? item : item.parent) - } - - // TODO: allow custom folder names - /// Action that creates a new untitled folder - @objc - private func newFolder() { - guard let item else { return } - workspace?.workspaceFileManager?.addFolder(folderName: "untitled", toFile: item) - outlineView.expandItem(item) - outlineView.expandItem(item.isFolder ? item : item.parent) - } - - /// Opens the rename file dialogue on the cell this was presented from. - @objc - private func renameFile() { - let row = outlineView.row(forItem: item) - guard row > 0, - let cell = outlineView.view( - atColumn: 0, - row: row, - makeIfNecessary: false - ) as? ProjectNavigatorTableViewCell else { - return - } - outlineView.window?.makeFirstResponder(cell.textField) - } - - /// Action that deletes the item. - @objc - private func delete() { - guard let item else { return } - workspace?.workspaceFileManager?.delete(file: item) - } - - /// Action that duplicates the item - @objc - private func duplicate() { - guard let item else { return } - workspace?.workspaceFileManager?.duplicate(file: item) - } -} - -extension NSMenuItem { - fileprivate static func none() -> NSMenuItem { - let item = NSMenuItem(title: "", action: nil, keyEquivalent: "") - item.isEnabled = false - return item - } - - fileprivate static func sourceCode() -> NSMenuItem { - NSMenuItem(title: "Source Code", action: nil, keyEquivalent: "") - } - - fileprivate static func propertyList() -> NSMenuItem { - NSMenuItem(title: "Property List", action: nil, keyEquivalent: "") - } - - fileprivate static func asciiPropertyList() -> NSMenuItem { - NSMenuItem(title: "ASCII Property List", action: nil, keyEquivalent: "") - } - - fileprivate static func hex() -> NSMenuItem { - NSMenuItem(title: "Hex", action: nil, keyEquivalent: "") - } - - fileprivate static func quickLook() -> NSMenuItem { - NSMenuItem(title: "Quick Look", action: nil, keyEquivalent: "") - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift deleted file mode 100644 index 851f0c5962..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorOutlineView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// OutlineView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 05.04.22. -// - -import SwiftUI -import Combine - -/// Wraps an ``OutlineViewController`` inside a `NSViewControllerRepresentable` -struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { - - @EnvironmentObject var workspace: WorkspaceDocument - - @StateObject var prefs: Settings = .shared - - typealias NSViewControllerType = ProjectNavigatorViewController - - func makeNSViewController(context: Context) -> ProjectNavigatorViewController { - let controller = ProjectNavigatorViewController() - controller.workspace = workspace - controller.iconColor = prefs.preferences.general.fileIconStyle - workspace.workspaceFileManager?.addObserver(context.coordinator) - - context.coordinator.controller = controller - - return controller - } - - func updateNSViewController(_ nsViewController: ProjectNavigatorViewController, context: Context) { - nsViewController.iconColor = prefs.preferences.general.fileIconStyle - nsViewController.rowHeight = prefs.preferences.general.projectNavigatorSize.rowHeight - nsViewController.fileExtensionsVisibility = prefs.preferences.general.fileExtensionsVisibility - nsViewController.shownFileExtensions = prefs.preferences.general.shownFileExtensions - nsViewController.hiddenFileExtensions = prefs.preferences.general.hiddenFileExtensions - /// if the window becomes active from background, it will restore the selection to outline view. - nsViewController.updateSelection(itemID: workspace.editorManager.activeEditor.selectedTab?.file.id) - return - } - - func makeCoordinator() -> Coordinator { - Coordinator(workspace) - } - - class Coordinator: NSObject, CEWorkspaceFileManagerObserver { - init(_ workspace: WorkspaceDocument) { - self.workspace = workspace - super.init() - - workspace.listenerModel.$highlightedFileItem - .sink(receiveValue: { [weak self] fileItem in - guard let fileItem else { - return - } - self?.controller?.reveal(fileItem) - }) - .store(in: &cancellables) - workspace.editorManager.tabBarTabIdSubject - .sink { [weak self] itemID in - self?.controller?.updateSelection(itemID: itemID) - } - .store(in: &cancellables) - } - - var cancellables: Set = [] - var workspace: WorkspaceDocument - var controller: ProjectNavigatorViewController? - - func fileManagerUpdated(updatedItems: Set) { - guard let outlineView = controller?.outlineView else { return } - - for item in updatedItems { - outlineView.reloadItem(item, reloadChildren: true) - } - - controller?.updateSelection(itemID: workspace.editorManager.activeEditor.selectedTab?.file.id) - } - - deinit { - workspace.workspaceFileManager?.removeObserver(self) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift deleted file mode 100644 index 538e414921..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// OutlineTableViewCell.swift -// CodeEdit -// -// Created by Lukas Pistrol on 07.04.22. -// - -import SwiftUI - -protocol OutlineTableViewCellDelegate: AnyObject { - func moveFile(file: CEWorkspaceFile, to destination: URL) - func copyFile(file: CEWorkspaceFile, to destination: URL) -} - -/// A `NSTableCellView` showing an ``icon`` and a ``label`` -final class ProjectNavigatorTableViewCell: FileSystemTableViewCell { - private var delegate: OutlineTableViewCellDelegate? - - /// Initializes the `OutlineTableViewCell` with an `icon` and `label` - /// Both the icon and label will be colored, and sized based on the user's preferences. - /// - Parameters: - /// - frameRect: The frame of the cell. - /// - item: The file item the cell represents. - /// - isEditable: Set to true if the user should be able to edit the file name. - init( - frame frameRect: NSRect, - item: CEWorkspaceFile?, - isEditable: Bool = true, - delegate: OutlineTableViewCellDelegate? = nil - ) { - super.init(frame: frameRect, item: item, isEditable: isEditable) - - self.delegate = delegate - } - - /// *Not Implemented* - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - fatalError(""" - init(frame: ) isn't implemented on `OutlineTableViewCell`. - Please use `.init(frame: NSRect, item: WorkspaceClient.FileItem?) - """) - } - - /// *Not Implemented* - required init?(coder: NSCoder) { - fatalError(""" - init?(coder: NSCoder) isn't implemented on `OutlineTableViewCell`. - Please use `.init(frame: NSRect, item: WorkspaceClient.FileItem?) - """) - } - - override func controlTextDidEndEditing(_ obj: Notification) { - label.backgroundColor = fileItem.validateFileName(for: label?.stringValue ?? "") ? .none : errorRed - if fileItem.validateFileName(for: label?.stringValue ?? "") { - let destinationURL = fileItem.url - .deletingLastPathComponent() - .appendingPathComponent(label?.stringValue ?? "") - delegate?.moveFile(file: fileItem, to: destinationURL) - } else { - label?.stringValue = fileItem.labelFileName() - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSMenuDelegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSMenuDelegate.swift deleted file mode 100644 index faa70275c4..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSMenuDelegate.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// ProjectNavigatorViewController+NSMenuDelegate.swift -// CodeEdit -// -// Created by Dscyre Scotti on 6/23/23. -// - -import SwiftUI - -// MARK: - NSMenuDelegate -extension ProjectNavigatorViewController: NSMenuDelegate { - - /// Once a menu gets requested by a `right click` setup the menu - /// - /// If the right click happened outside a row this will result in no menu being shown. - /// - Parameter menu: The menu that got requested - func menuNeedsUpdate(_ menu: NSMenu) { - let row = outlineView.clickedRow - guard let menu = menu as? ProjectNavigatorMenu else { return } - - if row == -1 { - menu.item = nil - } else { - if let item = outlineView.item(atRow: row) as? CEWorkspaceFile { - menu.item = item - menu.workspace = workspace - } else { - menu.item = nil - } - } - menu.update() - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift deleted file mode 100644 index fbb3edd38f..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// OutlintViewController+OutlineTableViewCellDelegate.swift -// CodeEdit -// -// Created by Ziyuan Zhao on 2023/2/5. -// - -import Foundation - -// MARK: - OutlineTableViewCellDelegate - -extension ProjectNavigatorViewController: OutlineTableViewCellDelegate { - func moveFile(file: CEWorkspaceFile, to destination: URL) { - if !file.isFolder { - workspace?.editorManager.editorLayout.closeAllTabs(of: file) - } - workspace?.workspaceFileManager?.move(file: file, to: destination) - if !file.isFolder { - workspace?.editorManager.openTab(item: .init(url: destination)) - } - } - - func copyFile(file: CEWorkspaceFile, to destination: URL) { - workspace?.workspaceFileManager?.copy(file: file, to: destination) - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift deleted file mode 100644 index ef12c300c6..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ /dev/null @@ -1,396 +0,0 @@ -// -// OutlineViewController.swift -// CodeEdit -// -// Created by Lukas Pistrol on 07.04.22. -// - -import AppKit -import SwiftUI - -/// A `NSViewController` that handles the **ProjectNavigatorView** in the **NavigatorArea**. -/// -/// Adds a ``outlineView`` inside a ``scrollView`` which shows the folder structure of the -/// currently open project. -final class ProjectNavigatorViewController: NSViewController { - - var scrollView: NSScrollView! - var outlineView: NSOutlineView! - - /// Gets the folder structure - /// - /// Also creates a top level item "root" which represents the projects root directory and automatically expands it. - private var content: [CEWorkspaceFile] { - guard let folderURL = workspace?.workspaceFileManager?.folderUrl else { return [] } - guard let root = workspace?.workspaceFileManager?.getFile(folderURL.path) else { return [] } - return [root] - } - - var workspace: WorkspaceDocument? - - var iconColor: SettingsData.FileIconStyle = .color - - var fileExtensionsVisibility: SettingsData.FileExtensionsVisibility = .showAll - var shownFileExtensions: SettingsData.FileExtensions = .default - var hiddenFileExtensions: SettingsData.FileExtensions = .default - - var rowHeight: Double = 22 { - willSet { - if newValue != rowHeight { - outlineView.rowHeight = newValue - outlineView.reloadData() - } - } - } - - /// This helps determine whether or not to send an `openTab` when the selection changes. - /// Used b/c the state may update when the selection changes, but we don't necessarily want - /// to open the file a second time. - private var shouldSendSelectionUpdate: Bool = true - - /// Setup the ``scrollView`` and ``outlineView`` - override func loadView() { - self.scrollView = NSScrollView() - self.scrollView.hasVerticalScroller = true - self.view = scrollView - - self.outlineView = NSOutlineView() - self.outlineView.dataSource = self - self.outlineView.delegate = self - self.outlineView.autosaveExpandedItems = true - self.outlineView.autosaveName = workspace?.workspaceFileManager?.folderUrl.path ?? "" - self.outlineView.headerView = nil - self.outlineView.menu = ProjectNavigatorMenu(sender: self.outlineView) - self.outlineView.menu?.delegate = self - self.outlineView.doubleAction = #selector(onItemDoubleClicked) - - let column = NSTableColumn(identifier: .init(rawValue: "Cell")) - column.title = "Cell" - outlineView.addTableColumn(column) - - outlineView.setDraggingSourceOperationMask(.move, forLocal: false) - outlineView.registerForDraggedTypes([.fileURL]) - - scrollView.documentView = outlineView - scrollView.contentView.automaticallyAdjustsContentInsets = false - scrollView.contentView.contentInsets = .init(top: 10, left: 0, bottom: 0, right: 0) - scrollView.scrollerStyle = .overlay - scrollView.hasVerticalScroller = true - scrollView.hasHorizontalScroller = false - scrollView.autohidesScrollers = true - - outlineView.expandItem(outlineView.item(atRow: 0)) - } - - init() { - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError() - } - - /// Forces to reveal the selected file through the command regardless of the auto reveal setting - @objc - func revealFile(_ sender: Any) { - updateSelection(itemID: workspace?.editorManager.activeEditor.selectedTab?.file.id, forcesReveal: true) - } - - /// Updates the selection of the ``outlineView`` whenever it changes. - /// - /// Most importantly when the `id` changes from an external view. - /// - Parameter itemID: The id of the file or folder. - /// - Parameter forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file. - func updateSelection(itemID: String?, forcesReveal: Bool = false) { - guard let itemID else { - outlineView.deselectRow(outlineView.selectedRow) - return - } - select(by: .codeEditor(itemID), forcesReveal: forcesReveal) - } - - /// Expand or collapse the folder on double click - @objc - private func onItemDoubleClicked() { - guard let item = outlineView.item(atRow: outlineView.clickedRow) as? CEWorkspaceFile else { return } - - if item.isFolder { - if outlineView.isItemExpanded(item) { - outlineView.collapseItem(item) - } else { - outlineView.expandItem(item) - } - } else if Settings[\.navigation].navigationStyle == .openInTabs { - workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: false) - } - } - - /// Get the appropriate color for the items icon depending on the users preferences. - /// - Parameter item: The `FileItem` to get the color for - /// - Returns: A `NSColor` for the given `FileItem`. - private func color(for item: CEWorkspaceFile) -> NSColor { - if !item.isFolder && iconColor == .color { - return NSColor(item.iconColor) - } else { - return .secondaryLabelColor - } - } - - // TODO: File filtering -} - -// MARK: - NSOutlineViewDataSource - -extension ProjectNavigatorViewController: NSOutlineViewDataSource { - func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { - if let item = item as? CEWorkspaceFile { - return item.isFolder ? workspace?.workspaceFileManager?.childrenOfFile(item)?.count ?? 0 : 0 - } - return content.count - } - - func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { - if let item = item as? CEWorkspaceFile, - let children = workspace?.workspaceFileManager?.childrenOfFile(item) { - return children[index] - } - return content[index] - } - - func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { - if let item = item as? CEWorkspaceFile { - return item.isFolder - } - return false - } - - /// write dragged file(s) to pasteboard - func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? { - guard let fileItem = item as? CEWorkspaceFile else { return nil } - return fileItem.url as NSURL - } - - /// declare valid drop target - func outlineView( - _ outlineView: NSOutlineView, - validateDrop info: NSDraggingInfo, - proposedItem item: Any?, - proposedChildIndex index: Int - ) -> NSDragOperation { - guard let fileItem = item as? CEWorkspaceFile else { return [] } - // -1 index indicates that we are hovering over a row in outline view (folder or file) - if index == -1 { - if !fileItem.isFolder { - outlineView.setDropItem(fileItem.parent, dropChildIndex: index) - } - return info.draggingSourceOperationMask == .copy ? .copy : .move - } - return [] - } - - /// handle successful or unsuccessful drop - func outlineView( - _ outlineView: NSOutlineView, - acceptDrop info: NSDraggingInfo, - item: Any?, - childIndex index: Int - ) -> Bool { - guard let pasteboardItems = info.draggingPasteboard.readObjects(forClasses: [NSURL.self]) else { return false } - let fileItemURLS = pasteboardItems.compactMap { $0 as? URL } - - guard let fileItemDestination = item as? CEWorkspaceFile else { return false } - let destParentURL = fileItemDestination.url - - for fileItemURL in fileItemURLS { - let destURL = destParentURL.appendingPathComponent(fileItemURL.lastPathComponent) - // cancel dropping file item on self or in parent directory - if fileItemURL == destURL || fileItemURL == destParentURL { - return false - } - - // Needs to come before call to .removeItem or else race condition occurs - var srcFileItem: CEWorkspaceFile? = workspace?.workspaceFileManager?.getFile(fileItemURL.path) - // If srcFileItem is nil, fileItemUrl is an external file url. - if srcFileItem == nil { - srcFileItem = CEWorkspaceFile(url: URL(fileURLWithPath: fileItemURL.path)) - } - - guard let srcFileItem else { - return false - } - - if CEWorkspaceFile.fileManager.fileExists(atPath: destURL.path) { - let shouldReplace = replaceFileDialog(fileName: fileItemURL.lastPathComponent) - guard shouldReplace else { - return false - } - do { - try CEWorkspaceFile.fileManager.removeItem(at: destURL) - } catch { - fatalError(error.localizedDescription) - } - } - if info.draggingSourceOperationMask == .copy { - self.copyFile(file: srcFileItem, to: destURL) - } else { - self.moveFile(file: srcFileItem, to: destURL) - } - } - return true - } - - func replaceFileDialog(fileName: String) -> Bool { - let alert = NSAlert() - alert.messageText = """ - A file or folder with the name \(fileName) already exists in the destination folder. Do you want to replace it? - """ - alert.informativeText = "This action is irreversible!" - alert.alertStyle = .warning - alert.addButton(withTitle: "Replace") - alert.addButton(withTitle: "Cancel") - return alert.runModal() == .alertFirstButtonReturn - } -} - -// MARK: - NSOutlineViewDelegate -extension ProjectNavigatorViewController: NSOutlineViewDelegate { - func outlineView( - _ outlineView: NSOutlineView, - shouldShowCellExpansionFor tableColumn: NSTableColumn?, - item: Any - ) -> Bool { - true - } - - func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool { - true - } - - func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { - guard let tableColumn else { return nil } - - let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: rowHeight) - - return ProjectNavigatorTableViewCell(frame: frameRect, item: item as? CEWorkspaceFile, delegate: self) - } - - func outlineViewSelectionDidChange(_ notification: Notification) { - guard let outlineView = notification.object as? NSOutlineView else { - return - } - - let selectedIndex = outlineView.selectedRow - - guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return } - - if !item.isFolder && shouldSendSelectionUpdate { - DispatchQueue.main.async { - self.workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: true) - } - } - } - - func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { - rowHeight // This can be changed to 20 to match Xcode's row height. - } - - func outlineViewItemDidExpand(_ notification: Notification) { - guard - let id = workspace?.editorManager.activeEditor.selectedTab?.file.id, - let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true) - else { - return - } - /// update outline selection only if the parent of selected item match with expanded item - guard item.parent === notification.userInfo?["NSObject"] as? CEWorkspaceFile else { - return - } - /// select active file under collapsed folder only if its parent is expanding - if outlineView.isItemExpanded(item.parent) { - updateSelection(itemID: item.id) - } - } - - func outlineViewItemDidCollapse(_ notification: Notification) {} - - func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? { - guard let id = object as? CEWorkspaceFile.ID, - let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true) else { return nil } - return item - } - - func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? { - guard let item = item as? CEWorkspaceFile else { return nil } - return item.id - } - - /// Finds and selects an ``Item`` from an array of ``Item`` and their `children` based on the `id`. - /// - Parameters: - /// - id: the id of the item item - /// - collection: the array to search for - /// - forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file. - private func select(by id: EditorTabID, forcesReveal: Bool) { - guard case .codeEditor(let path) = id, - let item = workspace?.workspaceFileManager?.getFile(path, createIfNotFound: true) else { - return - } - // If the user has set "Reveal file on selection change" to on or it is forced to reveal, - // we need to reveal the item before selecting the row. - if Settings.shared.preferences.general.revealFileOnFocusChange || forcesReveal { - reveal(item) - } - let row = outlineView.row(forItem: item) - if row == -1 { - outlineView.deselectRow(outlineView.selectedRow) - } - shouldSendSelectionUpdate = false - outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false) - shouldSendSelectionUpdate = true - } - - /// Reveals the given `fileItem` in the outline view by expanding all the parent directories of the file. - /// If the file is not found, it will present an alert saying so. - /// - Parameter fileItem: The file to reveal. - public func reveal(_ fileItem: CEWorkspaceFile) { - if let parent = fileItem.parent { - expandParent(item: parent) - } - let row = outlineView.row(forItem: fileItem) - outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false) - - if row < 0 { - let alert = NSAlert() - alert.messageText = NSLocalizedString( - "Could not find file", - comment: "Could not find file" - ) - alert.runModal() - return - } else { - let visibleRect = scrollView.contentView.visibleRect - let visibleRows = outlineView.rows(in: visibleRect) - guard !visibleRows.contains(row) else { - /// in case that the selected file is not fully visible (some parts are out of the visible rect), - /// `scrollRowToVisible(_:)` method brings the file where it can be fully visible. - outlineView.scrollRowToVisible(row) - return - } - let rowRect = outlineView.rect(ofRow: row) - let centerY = rowRect.midY - (visibleRect.height / 2) - let center = NSPoint(x: 0, y: centerY) - /// `scroll(_:)` method alone doesn't bring the selected file to the center in some cases. - /// calling `scrollRowToVisible(_:)` method before it makes the file reveal in the center more correctly. - outlineView.scrollRowToVisible(row) - outlineView.scroll(center) - } - } - - /// Method for recursively expanding a file's parent directories. - /// - Parameter item: - private func expandParent(item: CEWorkspaceFile) { - if let parent = item.parent as CEWorkspaceFile? { - expandParent(item: parent) - } - outlineView.expandItem(item) - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift deleted file mode 100644 index ea6eaf458a..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// ProjectNavigatorToolbarBottom.swift -// CodeEdit -// -// Created by TAY KAI QUAN on 23/7/22. -// - -import SwiftUI - -struct ProjectNavigatorToolbarBottom: View { - @Environment(\.controlActiveState) - private var activeState - - @Environment(\.colorScheme) - private var colorScheme - - @EnvironmentObject var workspace: WorkspaceDocument - @EnvironmentObject var editorManager: EditorManager - - @State var filter: String = "" - @State var recentsFilter: Bool = false - @State var sourceControlFilter: Bool = false - - var body: some View { - HStack(spacing: 5) { - addNewFileButton - PaneTextField( - "Filter", - text: $filter, - leadingAccessories: { - FilterDropDownIconButton(menu: { - Button { - workspace.sortFoldersOnTop.toggle() - } label: { - Text(workspace.sortFoldersOnTop ? "Alphabetically" : "Folders on top") - } - }, isOn: !filter.isEmpty) - .padding(.leading, 4) - .foregroundStyle( - filter.isEmpty - ? Color(nsColor: .secondaryLabelColor) - : Color(nsColor: .controlAccentColor) - ) - }, - trailingAccessories: { - HStack(spacing: 0) { - Toggle(isOn: $recentsFilter) { - Image(systemName: "clock") - } - Toggle(isOn: $sourceControlFilter) { - Image(systemName: "plusminus.circle") - } - } - .toggleStyle(.icon(font: .system(size: 14), size: CGSize(width: 18, height: 20))) - .padding(.trailing, 2.5) - }, - clearable: true, - hasValue: !filter.isEmpty || recentsFilter || sourceControlFilter - ) - // .onChange(of: filter, perform: { - // TODO: Filter Workspace Files - // workspace.filter = $0 - // }) - } - .padding(.horizontal, 5) - .frame(height: 28, alignment: .center) - .frame(maxWidth: .infinity) - .overlay(alignment: .top) { - Divider() - } - } - - /// Retrieves the active tab URL from the underlying editor instance, if theres no - /// active tab, fallbacks to the workspace's root directory - private func activeTabURL() -> URL { - if let selectedTab = editorManager.activeEditor.selectedTab { - if selectedTab.file.isFolder { - return selectedTab.file.url - } - - // If the current active tab belongs to a file, pop the filename from - // the path URL to retrieve the folder URL - let activeTabFileURL = selectedTab.file.url - - if URLComponents(url: activeTabFileURL, resolvingAgainstBaseURL: false) != nil { - var pathComponents = activeTabFileURL.pathComponents - pathComponents.removeLast() - - let fileURL = NSURL.fileURL(withPathComponents: pathComponents)! as URL - return fileURL - } - } - - return workspace.workspaceFileManager.unsafelyUnwrapped.folderUrl - } - - private var addNewFileButton: some View { - Menu { - Button("Add File") { - let filePathURL = activeTabURL() - guard let rootFile = workspace.workspaceFileManager?.getFile(filePathURL.path) else { return } - workspace.workspaceFileManager?.addFile(fileName: "untitled", toFile: rootFile) - } - Button("Add Folder") { - let filePathURL = activeTabURL() - guard let rootFile = workspace.workspaceFileManager?.getFile(filePathURL.path) else { return } - workspace.workspaceFileManager?.addFolder(folderName: "untitled", toFile: rootFile) - } - } label: {} - .background { - Image(systemName: "plus") - } - .menuStyle(.borderlessButton) - .menuIndicator(.hidden) - .frame(maxWidth: 18, alignment: .center) - .opacity(activeState == .inactive ? 0.45 : 1) - } - - /// We clear the text and remove the first responder which removes the cursor - /// when the user clears the filter. - private var clearFilterButton: some View { - Button { - filter = "" - NSApp.keyWindow?.makeFirstResponder(nil) - } label: { - Image(systemName: "xmark.circle.fill") - .symbolRenderingMode(.hierarchical) - } - .buttonStyle(.plain) - .opacity(activeState == .inactive ? 0.45 : 1) - } -} - -struct FilterDropDownIconButton: View { - @Environment(\.controlActiveState) - private var activeState - - var menu: () -> MenuView - - var isOn: Bool? - - var body: some View { - Menu { menu() } label: {} - .background { - if isOn == true { - Image("line.3.horizontal.decrease.chevron.filled") - .foregroundStyle(.tint) - } else { - Image("line.3.horizontal.decrease.chevron") - } - } - .menuStyle(.borderlessButton) - .menuIndicator(.hidden) - .frame(width: 26, height: 13) - .clipShape(.rect(cornerRadius: 6.5)) - } -} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift deleted file mode 100644 index 23536c06e0..0000000000 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ProjectNavigatorView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 25.03.22. -// - -import SwiftUI - -/// # Project Navigator - Sidebar -/// -/// A list that functions as a project navigator, showing collapsible folders -/// and files. -/// -/// When selecting a file it will open in the editor. -/// -struct ProjectNavigatorView: View { - var body: some View { - ProjectNavigatorOutlineView() - .safeAreaInset(edge: .bottom, spacing: 0) { - ProjectNavigatorToolbarBottom() - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangedFileView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangedFileView.swift deleted file mode 100644 index 399f73f39c..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangedFileView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// SourceControlNavigatorChangedFileView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI - -struct SourceControlNavigatorChangedFileView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - @Binding var changedFile: CEWorkspaceFile - @State var staged: Bool - - init(changedFile: Binding) { - self._changedFile = changedFile - _staged = State(initialValue: changedFile.wrappedValue.staged ?? false) - } - - var folder: String? { - let rootPath = sourceControlManager.gitClient.directoryURL.relativePath - let filePath = changedFile.url.relativePath - - // Should not happen, but just in case - if !filePath.hasPrefix(rootPath) { - return nil - } - - let relativePath = filePath - .dropFirst(rootPath.count + 1) // Drop root folder - .dropLast(changedFile.name.count + 1) // Drop file name - return relativePath.isEmpty ? nil : "\(relativePath)/" - } - - var body: some View { - HStack(spacing: 6) { - Toggle("", isOn: $staged) - .labelsHidden() - .onChange(of: staged) { newStaged in - Task { - if changedFile.staged != newStaged { - if newStaged { - try await sourceControlManager.add([changedFile]) - } else { - try await sourceControlManager.reset([changedFile]) - } - } - } - } - Label(title: { - Text(changedFile.name) - .lineLimit(1) - .truncationMode(.middle) - }, icon: { - Image(nsImage: changedFile.nsIcon) - .foregroundStyle(changedFile.iconColor) - }) - - Spacer() - Text(changedFile.gitStatus?.description ?? "") - .font(.system(size: 11, weight: .bold)) - .foregroundColor(.secondary) - .frame(minWidth: 10, alignment: .center) - } - .help("\(folder ?? "")\(changedFile.name)") - .onChange(of: changedFile.staged) { newStaged in - staged = newStaged ?? false - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesCommitView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesCommitView.swift deleted file mode 100644 index 40201fa29f..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesCommitView.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// SourceControlNavigatorChangesCommitView.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/19/23. -// - -import SwiftUI - -struct SourceControlNavigatorChangesCommitView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - @State private var message: String = "" - @State private var details: String = "" - @State private var ammend: Bool = false - @State private var showDetails: Bool = false - @State private var isCommiting: Bool = false - - var allFilesStaged: Bool { - sourceControlManager.changedFiles.allSatisfy { $0.staged ?? false } - } - - var anyFilesStaged: Bool { - sourceControlManager.changedFiles.contains { $0.staged ?? false } - } - - var body: some View { - VStack(spacing: 0) { - VStack(spacing: 0) { - PaneTextField( - "Commit message (required)", - text: $message, - axis: .vertical - ) - .lineLimit(1...3) - .safeAreaInset(edge: .bottom, spacing: 0) { - if showDetails { - VStack { - TextField( - "Detailed description", - text: $details, - axis: .vertical - ) - .textFieldStyle(.plain) - .controlSize(.small) - .lineLimit(3...5) - - } - .frame(maxWidth: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 3.5) - .overlay(alignment: .top) { - VStack { - Divider() - } - } - } - } - VStack(spacing: 0) { - if showDetails { - Toggle(isOn: $ammend) { - Text("Amend") - .frame(maxWidth: .infinity, alignment: .leading) - } - .toggleStyle(.switch) - .controlSize(.mini) - .padding(.top, 8) - .transition(.move(edge: .top).combined(with: .opacity)) - } - } - .frame(maxWidth: .infinity) - .clipped() - HStack(spacing: 8) { - Button { - Task { - if allFilesStaged { - try await sourceControlManager.reset(sourceControlManager.changedFiles) - } else { - try await sourceControlManager.add(sourceControlManager.changedFiles) - } - } - } label: { - Text(allFilesStaged ? "Unstage All" : "Stage All") - .frame(maxWidth: .infinity) - } - Menu(isCommiting ? "Committing..." : "Commit") { - Button("Commit and Push...") { - Task { - self.isCommiting = true - do { - try await sourceControlManager.commit(message: message, details: details) - self.message = "" - self.details = "" - } catch { - await sourceControlManager.showAlertForError( - title: "Failed to commit", - error: error - ) - } - do { - try await sourceControlManager.push() - } catch { - await sourceControlManager.showAlertForError(title: "Failed to push", error: error) - } - self.isCommiting = false - } - } - } primaryAction: { - Task { - self.isCommiting = true - do { - try await sourceControlManager.commit(message: message, details: details) - self.message = "" - self.details = "" - } catch { - await sourceControlManager.showAlertForError(title: "Failed to commit", error: error) - } - self.isCommiting = false - } - } - .disabled( - message.isEmpty || - !anyFilesStaged || - isCommiting - ) - } - .padding(.top, 8) - } - .transition(.move(edge: .top)) - .onChange(of: message) { _ in - withAnimation(.easeInOut(duration: 0.25)) { - showDetails = !message.isEmpty - } - } - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift deleted file mode 100644 index 387516e0cb..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// SourceControlNavigatorChangesList.swift -// CodeEdit -// -// Created by Austin Condiff on 11/18/23. -// - -import SwiftUI - -struct SourceControlNavigatorChangesList: View { - @EnvironmentObject var workspace: WorkspaceDocument - @EnvironmentObject var sourceControlManager: SourceControlManager - - @State var selection = Set() - - var body: some View { - List($sourceControlManager.changedFiles, id: \.self, selection: $selection) { $file in - SourceControlNavigatorChangedFileView(changedFile: $file) - .listRowSeparator(.hidden) - .padding(.vertical, -1) - } - .environment(\.defaultMinListRowHeight, 22) - .contextMenu( - forSelectionType: CEWorkspaceFile.self, - menu: { selectedFiles in - if !selectedFiles.isEmpty, - selectedFiles.count == 1, - let file = selectedFiles.first { - Group { - Button("View in Finder") { - file.showInFinder() - } - Button("Reveal in Project Navigator") {} - .disabled(true) // TODO: Implementation Needed - Divider() - } - Group { - Button("Open in New Tab") { - DispatchQueue.main.async { - workspace.editorManager.activeEditor.openTab(file: file, asTemporary: true) - } - } - Button("Open in New Window") {} - .disabled(true) // TODO: Implementation Needed - } - if file.gitStatus == .modified { - Group { - Divider() - Button("Discard Changes in \(file.name)...") { - sourceControlManager.discardChanges(for: file) - } - Divider() - } - } - } else { - EmptyView() - } - }, - // double-click action - primaryAction: { selectedFiles in - if !selectedFiles.isEmpty, - selectedFiles.count == 1, - let file = selection.first { - DispatchQueue.main.async { - workspace.editorManager.activeEditor.openTab(file: file, asTemporary: false) - } - } - } - ) - .onChange(of: selection) { newSelection in - if !newSelection.isEmpty, - newSelection.count == 1, - let file = newSelection.first { - DispatchQueue.main.async { - workspace.editorManager.activeEditor.openTab(file: file, asTemporary: true) - } - } - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesView.swift deleted file mode 100644 index d8bb304491..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// SourceControlNavigatorChangesView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI - -struct SourceControlNavigatorChangesView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - - var hasRemotes: Bool { - !sourceControlManager.remotes.isEmpty - } - - var hasUnsyncedCommits: Bool { - sourceControlManager.numberOfUnsyncedCommits.ahead > 0 - || sourceControlManager.numberOfUnsyncedCommits.behind > 0 - } - - var hasCurrentBranch: Bool { - (sourceControlManager.currentBranch != nil && sourceControlManager.currentBranch?.upstream == nil) - } - - var hasChanges: Bool { - !sourceControlManager.changedFiles.isEmpty - } - - var body: some View { - VStack(alignment: .center, spacing: 0) { - if hasChanges || !hasRemotes || (!hasChanges && (hasUnsyncedCommits || hasCurrentBranch)) { - VStack(spacing: 8) { - Divided { - if hasChanges { - SourceControlNavigatorChangesCommitView() - } - if !hasRemotes { - SourceControlNavigatorNoRemotesView() - } - if !hasChanges && (hasUnsyncedCommits || hasCurrentBranch) { - SourceControlNavigatorSyncView(sourceControlManager: sourceControlManager) - } - } - } - .padding(.horizontal, 10) - .padding(.vertical, 8) - Divider() - } - if hasChanges { - SourceControlNavigatorChangesList() - } else { - CEContentUnavailableView("No Changes") - } - } - .frame(maxHeight: .infinity) - .task { - await sourceControlManager.refreshAllChangedFiles() - await sourceControlManager.refreshNumberOfUnsyncedCommits() - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorNoRemotesView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorNoRemotesView.swift deleted file mode 100644 index 41e35a9768..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorNoRemotesView.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// SourceControlNavigatorNoRemotesView.swift -// CodeEdit -// -// Created by Austin Condiff on 11/17/23. -// - -import SwiftUI - -struct SourceControlNavigatorNoRemotesView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - - @State private var addRemoteIsPresented: Bool = false - - var body: some View { - VStack(spacing: 0) { - HStack { - Label( - title: { - Text("No remotes") - }, icon: { - Image(systemName: "network") - .foregroundColor(.secondary) - } - ) - Spacer() - Button("Add") { - addRemoteIsPresented = true - } - .sheet(isPresented: $addRemoteIsPresented) { - SourceControlAddRemoteView() - } - } - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorSyncView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorSyncView.swift deleted file mode 100644 index fd32c0b896..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorSyncView.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// SourceControlNavigatorSyncView.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/20/23. -// - -import SwiftUI - -struct SourceControlNavigatorSyncView: View { - @ObservedObject var sourceControlManager: SourceControlManager - @State private var isLoading: Bool = false - - var body: some View { - HStack { - Label(title: { - Text( - formatUnsyncedlabel( - ahead: sourceControlManager.numberOfUnsyncedCommits.ahead, - behind: sourceControlManager.numberOfUnsyncedCommits.behind - ) - ) - }, icon: { - Image(systemName: "arrow.up.arrow.down") - .foregroundStyle(.secondary) - }) - Spacer() - if sourceControlManager.numberOfUnsyncedCommits.behind > 0 { - Button { - self.pull() - } label: { - if isLoading { - Text("Pulling...") - } else { - Text("Pull") - } - } - .disabled(isLoading) - } else if sourceControlManager.numberOfUnsyncedCommits.ahead > 0 { - Button { - self.push() - } label: { - if isLoading { - Text("Pushing...") - } else { - Text("Push") - } - } - .disabled(isLoading) - } - } - } - - func pull() { - Task(priority: .background) { - self.isLoading = true - do { - try await sourceControlManager.pull() - } catch { - await sourceControlManager.showAlertForError(title: "Failed to pull", error: error) - } - self.isLoading = false - } - } - - func push() { - Task(priority: .background) { - self.isLoading = true - do { - try await sourceControlManager.push() - } catch { - await sourceControlManager.showAlertForError(title: "Failed to push", error: error) - } - self.isLoading = false - } - } - - func formatUnsyncedlabel(ahead: Int?, behind: Int?) -> String { - var parts: [String] = [] - - if let ahead = ahead, ahead > 0 { - parts.append("\(ahead) ahead") - } - - if let behind = behind, behind > 0 { - parts.append("\(behind) behind") - } - - return parts.joined(separator: ", ") - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitChangedFileListItemView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitChangedFileListItemView.swift deleted file mode 100644 index b5a496097d..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitChangedFileListItemView.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// CommitChangedFileListItemView.swift -// CodeEdit -// -// Created by Austin Condiff on 12/27/23. -// - -import SwiftUI - -struct CommitChangedFileListItemView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - @Binding var changedFile: CEWorkspaceFile - - var folder: String? { - let rootPath = sourceControlManager.gitClient.directoryURL.relativePath - let filePath = changedFile.url.relativePath - - // Should not happen, but just in case - if !filePath.hasPrefix(rootPath) { - return nil - } - - let relativePath = filePath - .dropFirst(rootPath.count + 1) // Drop root folder - .dropLast(changedFile.name.count + 1) // Drop file name - return relativePath.isEmpty ? nil : "\(relativePath)/" - } - - var body: some View { - HStack(spacing: 6) { - Label(title: { - Text(changedFile.name) - .lineLimit(1) - .truncationMode(.middle) - }, icon: { - Image(systemName: changedFile.systemImage) - .foregroundStyle(changedFile.iconColor) - }) - - Spacer() - Text(changedFile.gitStatus?.description ?? "") - .font(.system(size: 11, weight: .bold)) - .foregroundColor(.secondary) - .frame(minWidth: 10, alignment: .center) - } - .help("\(folder ?? "")\(changedFile.name)") - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsHeaderView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsHeaderView.swift deleted file mode 100644 index 17a2712777..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsHeaderView.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// CommitDetailsHeaderView.swift -// CodeEdit -// -// Created by Austin Condiff on 12/27/23. -// - -import SwiftUI - -struct CommitDetailsHeaderView: View { - var commit: GitCommit - - private var defaultAvatar: some View { - Image(systemName: "person.crop.circle.fill") - .symbolRenderingMode(.hierarchical) - .resizable() - .foregroundColor(avatarColor) - .frame(width: 32, height: 32) - } - - private func commitDetails() -> String { - if commit.committerEmail == "noreply@github.com" { - return commit.message.trimmingCharacters(in: .whitespacesAndNewlines) - } else if commit.authorEmail != commit.committerEmail { - return commit.message.trimmingCharacters(in: .whitespacesAndNewlines) - } else { - return "\(commit.message)\n\n\(coAuthDetail())".trimmingCharacters(in: .whitespacesAndNewlines) - } - } - - private func coAuthDetail() -> String { - if commit.committerEmail == "noreply@github.com" { - return "" - } else if commit.authorEmail != commit.committerEmail { - return "Co-authored by: \(commit.committer)\n<\(commit.committerEmail)>" - } - return "" - } - - private func generateAvatarHash() -> String { - let hash = commit.authorEmail.md5(trim: true, caseSensitive: false) - return "\(hash)?d=404&s=64" // send 404 if no image available, image size 64x64 (32x32 @2x) - } - - private var avatarColor: Color { - let hash = generateAvatarHash().hash - switch hash % 12 { - case 0: return .red - case 1: return .orange - case 2: return .yellow - case 3: return .green - case 4: return .mint - case 5: return .teal - case 6: return .cyan - case 7: return .blue - case 8: return .indigo - case 9: return .purple - case 10: return .brown - case 11: return .pink - default: return .teal - } - } - - var body: some View { - VStack(alignment: .leading, spacing: 10) { - HStack(alignment: .top) { - AsyncImage(url: URL(string: "https://www.gravatar.com/avatar/\(generateAvatarHash())")) { phase in - if let image = phase.image { - image - .resizable() - .clipShape(Circle()) - .frame(width: 32, height: 32) - } else if phase.error != nil { - defaultAvatar - } else { - defaultAvatar - } - } - - VStack(alignment: .leading) { - Text(commit.author) - .fontWeight(.bold) - Text(commit.date.formatted(date: .abbreviated, time: .shortened)) - .font(.subheadline) - .foregroundStyle(.secondary) - } - - Spacer() - - Text(commit.hash) - .font(.subheadline) - .fontDesign(.monospaced) - .background( - RoundedRectangle(cornerRadius: 3) - .padding(.horizontal, -2.5) - .padding(.vertical, -1) - .foregroundColor(Color(nsColor: .quaternaryLabelColor)) - ) - .padding(.horizontal, 2.5) - } - Text(commitDetails()) - .frame(alignment: .leading) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsView.swift deleted file mode 100644 index 6043efe062..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitDetailsView.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// CommitDetailsView.swift -// CodeEdit -// -// Created by Austin Condiff on 12/27/23. -// - -import SwiftUI - -struct CommitDetailsView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - - @Binding var commit: GitCommit? - - @State var commitChanges: [CEWorkspaceFile] = [] - - @State var selection: CEWorkspaceFile? - - func updateCommitChanges() async throws { - if let commit = commit { - let changes = await sourceControlManager - .getCommitChangedFiles(commitSHA: commit.commitHash) - commitChanges = changes - } - } - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - HStack { - Button { - commit = nil - } label: { - Image(systemName: "chevron.backward") - } - .buttonStyle(SidebarButtonStyle()) - Text("Commit Details") - .font(.system(size: 13, weight: .bold)) - } - .padding(10) - Divider() - - if let commit = commit { - CommitDetailsHeaderView(commit: commit) - .padding(.horizontal, 16) - .padding(.vertical, 16) - Divider() - - if !commitChanges.isEmpty { - List(selection: $selection) { - ForEach($commitChanges, id: \.self) { $file in - CommitChangedFileListItemView(changedFile: $file) - .fixedSize(horizontal: false, vertical: true) - .listRowSeparator(.hidden) - .padding(.vertical, -1) - } - - } - .environment(\.defaultMinListRowHeight, 22) - } else { - CEContentUnavailableView("No Changes") - } - } else { - Spacer() - } - } - .onAppear { - Task { - try await updateCommitChanges() - } - } - } -} - -struct SidebarButtonStyle: ButtonStyle { - var isActive: Bool = false - - @Environment(\.colorScheme) - private var colorScheme - - @Environment(\.controlActiveState) - private var activeState - - @State var isHovering: Bool = false - - private var textOpacity: Double { - return activeState != .inactive ? 1 : 0.3 - } - - func makeBody(configuration: Self.Configuration) -> some View { - configuration.label - .font(.body) - .foregroundColor(configuration.isPressed ? .primary : .secondary) - .opacity(textOpacity) - .frame(height: 20) - .padding(.horizontal, 5) - .background( - RoundedRectangle(cornerSize: CGSize(width: 5, height: 5)) - .strokeBorder(.separator, lineWidth: 1) - .background( - RoundedRectangle(cornerSize: CGSize(width: 4, height: 4)) - .fill( - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(configuration.isPressed ? 0.10 : isHovering ? 0.05 : 0) - ) - .padding(1) - ) - .opacity(activeState != .inactive ? 1 : 0.3) - - ) - .onHover { hover in - isHovering = hover - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitListItemView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitListItemView.swift deleted file mode 100644 index 8a19f67e2a..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/CommitListItemView.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// CommitListItemView.swift -// CodeEdit -// -// Created by Austin Condiff on 12/27/2023. -// - -import SwiftUI - -struct CommitListItemView: View { - - var commit: GitCommit - - @Environment(\.openURL) - private var openCommit - - init(commit: GitCommit) { - self.commit = commit - } - - var body: some View { - HStack(alignment: .top) { - VStack(alignment: .leading, spacing: 0) { - Text(commit.author) - .fontWeight(.bold) - .font(.system(size: 11)) - Text(commit.message) - .font(.system(size: 11)) - .lineLimit(2) - } - Spacer() - VStack(alignment: .trailing, spacing: 5) { - Text(commit.hash) - .font(.system(size: 10, design: .monospaced)) - .background( - RoundedRectangle(cornerRadius: 3) - .padding(.vertical, -1) - .padding(.horizontal, -2.5) - .foregroundColor(Color(nsColor: .quaternaryLabelColor)) - ) - .padding(.trailing, 2.5) - Text(commit.date.relativeStringToNow()) - .font(.system(size: 11)) - .foregroundColor(.secondary) - } - .padding(.top, 1) - } - .padding(.vertical, 1) - .contentShape(Rectangle()) - .contextMenu { - Group { - Button("Copy Commit Message") { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(commit.message, forType: .string) - } - Button("Copy Identifier") { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(commit.commitHash, forType: .string) - } - Button("Email \(commit.author)...") { - let service = NSSharingService(named: NSSharingService.Name.composeEmail) - service?.recipients = [commit.authorEmail] - service?.perform(withItems: []) - } - Divider() - } - Group { - Button("Tag \(commit.hash)...") {} - .disabled(true) // TODO: Implementation Needed - Button("New Branch from \(commit.hash)...") {} - .disabled(true) // TODO: Implementation Needed - Button("Cherry-Pick \(commit.hash)...") {} - .disabled(true) // TODO: Implementation Needed - } - Group { - Divider() - if let commitRemoteURL = commit.commitBaseURL?.absoluteString { - Button("View on \(commit.remoteString)...") { - let commitURL = "\(commitRemoteURL)/\(commit.commitHash)" - openCommit(URL(string: commitURL)!) - } - Divider() - } - Button("Check Out \(commit.hash)...") {} - .disabled(true) // TODO: Implementation Needed - Divider() - Button("History Editor Help") {} - .disabled(true) // TODO: Implementation Needed - } - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/SourceControlNavigatorHistoryView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/SourceControlNavigatorHistoryView.swift deleted file mode 100644 index 2b54d39ced..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/History/Views/SourceControlNavigatorHistoryView.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// SourceControlNavigatorHistoryView.swift -// CodeEdit -// -// Created by Austin Condiff on 12/27/2023. -// - -import SwiftUI -import CodeEditSymbols - -struct SourceControlNavigatorHistoryView: View { - enum Status { - case loading - case ready - case error(error: Error) - } - - @EnvironmentObject var sourceControlManager: SourceControlManager - - @State var commitHistoryStatus: Status = .loading - @State var commitHistory: [GitCommit] = [] - - @State var selection: GitCommit? - - func updateCommitHistory() async { - do { - commitHistoryStatus = .loading - let commits = try await sourceControlManager - .gitClient - .getCommitHistory(branchName: sourceControlManager.currentBranch?.name) - await MainActor.run { - commitHistory = commits - commitHistoryStatus = .ready - } - } catch { - await MainActor.run { - commitHistory = [] - commitHistoryStatus = .error(error: error) - } - } - } - - var body: some View { - Group { - switch commitHistoryStatus { - case .loading: - VStack { - Spacer() - ProgressView { - Text("Loading History") - } - Spacer() - } - case .ready: - if commitHistory.isEmpty { - CEContentUnavailableView("No History") - } else { - ZStack { - List(selection: $selection) { - ForEach(commitHistory) { commit in - CommitListItemView(commit: commit) - .tag(commit) - .listRowSeparator(.hidden) - } - } - .opacity(selection == nil ? 1 : 0) - if selection != nil { - CommitDetailsView(commit: $selection) - } - } - } - case .error(let error): - VStack { - Spacer() - CEContentUnavailableView( - "Error Loading History", - description: error.localizedDescription, - systemImage: "exclamationmark.triangle" - ) { - Button { - Task { - await updateCommitHistory() - } - } label: { - Text("Retry") - } - } - Spacer() - } - } - } - .onReceive(sourceControlManager.$currentBranch) { _ in - Task { - await updateCommitHistory() - } - } - .task { - await updateCommitHistory() - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Models/RepoOutlineGroupItem.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Models/RepoOutlineGroupItem.swift deleted file mode 100644 index b78c0fea51..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Models/RepoOutlineGroupItem.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// RepoOutlineGroupItem.swift -// CodeEdit -// -// Created by Austin Condiff on 11/29/23. -// - -import SwiftUI - -struct RepoOutlineGroupItem: Hashable, Identifiable { - var id: String - var label: String - var description: String? - var systemImage: String? - var symbolImage: String? - var imageColor: Color? - var children: [RepoOutlineGroupItem]? - var branch: GitBranch? - var stashEntry: GitStashEntry? - var remote: GitRemote? -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift deleted file mode 100644 index caf40bc235..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// SourceControlNavigatorRepositoriesItem.swift -// CodeEdit -// -// Created by Austin Condiff on 11/29/23. -// - -import SwiftUI - -struct SourceControlNavigatorRepositoryItem: View { - let item: RepoOutlineGroupItem - - @Environment(\.controlActiveState) - var controlActiveState - - var body: some View { - if item.systemImage != nil || item.symbolImage != nil { - Label(title: { - Text(item.label) - .lineLimit(1) - .truncationMode(.middle) - if let description = item.description { - Text(description) - .lineLimit(1) - .foregroundStyle(.secondary) - .font(.system(size: 11)) - } - Spacer() - HStack(spacing: 5) { - if let behind = item.branch?.behind, behind > 0 { - HStack(spacing: 0) { - Image(systemName: "arrow.down") - .imageScale(.small) - Text("\(behind)") - .font(.system(size: 11)) - } - } - if let ahead = item.branch?.ahead, ahead > 0 { - HStack(spacing: 0) { - Image(systemName: "arrow.up") - .imageScale(.small) - Text("\(ahead)") - .font(.system(size: 11)) - } - } - } - }, icon: { - if item.symbolImage != nil { - Image(symbol: item.symbolImage ?? "") - .opacity(controlActiveState == .inactive ? 0.5 : 1) - .foregroundStyle(item.imageColor ?? .accentColor) - } else { - Image(systemName: item.systemImage ?? "") - .opacity(controlActiveState == .inactive ? 0.5 : 1) - .foregroundStyle(item.imageColor ?? .accentColor) - } - }) - .padding(.leading, 1) - .padding(.vertical, -1) - } else { - Text(item.label) - .padding(.leading, 2) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+contextMenu.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+contextMenu.swift deleted file mode 100644 index 2962f8ece3..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+contextMenu.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// SourceControlNavigatorRepositoriesView+contextMenu.swift -// CodeEdit -// -// Created by Austin Condiff on 11/29/23. -// - -import SwiftUI - -extension SourceControlNavigatorRepositoryView { - func handleDelete(_ item: RepoOutlineGroupItem) { - if item.branch != nil { - isPresentingConfirmDeleteBranch = true - branchToDelete = item.branch - } - if item.stashEntry != nil { - isPresentingConfirmDeleteStashEntry = true - stashEntryToDelete = item.stashEntry - } - if item.remote != nil { - isPresentingConfirmDeleteRemote = true - remoteToDelete = item.remote - } - } - - func handleCheckout(_ branch: GitBranch) { - Task { - do { - try await sourceControlManager.checkoutBranch(branch: branch) - } catch { - await sourceControlManager.showAlertForError(title: "Failed to checkout", error: error) - } - } - } - - @ViewBuilder - func contextMenu(for item: RepoOutlineGroupItem, branch: GitBranch) -> some View { - Button("Checkout") { - handleCheckout(branch) - } - .disabled(item.branch == nil || sourceControlManager.currentBranch == item.branch) - Divider() - Button( - item.branch == nil && item.id != "BranchesGroup" - ? "New Branch..." - : "New Branch from \"\(branch.name)\"..." - ) { - showNewBranch = true - fromBranch = item.branch - } - .disabled(item.branch == nil && item.id != "BranchesGroup") - Button( - item.branch == nil - ? "Rename Branch..." - : "Rename \"\(branch.name)\"..." - ) { - showRenameBranch = true - fromBranch = item.branch - } - .disabled(item.branch == nil) - Divider() - Button("Add Existing Remote...") { - addRemoteIsPresented = true - } - .disabled(item.id != "RemotesGroup") - Divider() - Button("Apply Stashed Changes...") { - applyStashedChangesIsPresented = true - stashEntryToApply = item.stashEntry - } - .disabled(item.stashEntry == nil) - Divider() - Button("Delete...") { - handleDelete(item) - } - .disabled( - (item.branch == nil - || item.branch?.isLocal == false - || sourceControlManager.currentBranch == item.branch) - && item.stashEntry == nil - && item.remote == nil - ) - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+outlineGroupData.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+outlineGroupData.swift deleted file mode 100644 index 7b698c315f..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView+outlineGroupData.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// SourceControlNavigatorRepositoriesView+outlineGroupData.swift -// CodeEdit -// -// Created by Austin Condiff on 11/29/23. -// - -import SwiftUI - -extension SourceControlNavigatorRepositoryView { - var outlineGroupData: [RepoOutlineGroupItem] { - [ - .init( - id: "BranchesGroup", - label: "Branches", - systemImage: "externaldrive.fill", - imageColor: Color(nsColor: .secondaryLabelColor), - children: sourceControlManager.branches.filter({ $0.isLocal }).map { branch in - .init( - id: "Branch\(branch.name)", - label: branch.name, - description: branch == sourceControlManager.currentBranch ? "(current)" : nil, - symbolImage: "branch", - imageColor: .blue, - branch: branch - ) - } - ), - .init( - id: "StashedChangesGroup", - label: "Stashed Changes", - systemImage: "tray.2.fill", - imageColor: Color(nsColor: .secondaryLabelColor), - children: sourceControlManager.stashEntries.map { stashEntry in - .init( - id: "StashEntry\(stashEntry.hashValue)", - label: stashEntry.message, - description: stashEntry.date.formatted( - Date.FormatStyle() - .year(.defaultDigits) - .month(.abbreviated) - .day(.twoDigits) - .hour(.defaultDigits(amPM: .abbreviated)) - .minute(.twoDigits) - ), - systemImage: "tray", - imageColor: .orange, - stashEntry: stashEntry - ) - } - ), - .init( - id: "RemotesGroup", - label: "Remotes", - systemImage: "network", - imageColor: Color(nsColor: .secondaryLabelColor), - children: sourceControlManager.remotes.map { remote in - .init( - id: "Remote\(remote.hashValue)", - label: remote.name, - symbolImage: "vault", - imageColor: .teal, - remote: remote - ) - } - ) - ] - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView.swift deleted file mode 100644 index dc1a813cab..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryView.swift +++ /dev/null @@ -1,192 +0,0 @@ -// -// SourceControlNavigatorRepositoryView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI -import CodeEditSymbols - -struct SourceControlNavigatorRepositoryView: View { - @Environment(\.controlActiveState) - var controlActiveState - - @EnvironmentObject var sourceControlManager: SourceControlManager - - @State var selection = Set() - @State var showNewBranch: Bool = false - @State var showRenameBranch: Bool = false - @State var fromBranch: GitBranch? - @State var expandedIds = [String: Bool]() - @State var addRemoteIsPresented: Bool = false - @State var applyStashedChangesIsPresented: Bool = false - @State var isPresentingConfirmDeleteBranch: Bool = false - @State var branchToDelete: GitBranch? - @State var isPresentingConfirmDeleteStashEntry: Bool = false - @State var stashEntryToApply: GitStashEntry? - @State var stashEntryToDelete: GitStashEntry? - @State var isPresentingConfirmDeleteRemote: Bool = false - @State var remoteToDelete: GitRemote? - @State var keepStashAfterApplying: Bool = true - - func findItem(by id: String, in items: [RepoOutlineGroupItem]) -> RepoOutlineGroupItem? { - for item in items { - if item.id == id { - return item - } else if let children = item.children, let found = findItem(by: id, in: children) { - return found - } - } - return nil - } - - var body: some View { - List(selection: $selection) { - ForEach(outlineGroupData, id: \.id) { item in - CEOutlineGroup( - item, - id: \.id, - defaultExpanded: true, - expandedIds: $expandedIds, - children: \.children, - content: { item in - SourceControlNavigatorRepositoryItem(item: item) - } - ) - .listRowSeparator(.hidden) - } - } - .environment(\.defaultMinListRowHeight, 22) - .contextMenu( - forSelectionType: RepoOutlineGroupItem.ID.self, - menu: { items in - if !items.isEmpty, - items.count == 1, - let item = findItem(by: items.first ?? "", in: outlineGroupData), - let branch = item.branch ?? sourceControlManager.currentBranch { - contextMenu(for: item, branch: branch) - } - } - ) - .sheet(isPresented: $showNewBranch) { - SourceControlNavigatorNewBranchView( - sourceControlManager: sourceControlManager, - fromBranch: fromBranch - ) - } - .sheet(isPresented: $showRenameBranch) { - SourceControlNavigatorRenameBranchView( - sourceControlManager: sourceControlManager, - fromBranch: fromBranch - ) - } - .sheet(isPresented: $addRemoteIsPresented) { - SourceControlAddRemoteView() - } - .alert( - sourceControlManager.changedFiles.isEmpty - ? "Do you want to apply stashed changes?" - : "The local repository has uncommitted changes.", - isPresented: $applyStashedChangesIsPresented - ) { - if sourceControlManager.changedFiles.isEmpty { - Button("Apply") { - if let stashEntry = stashEntryToApply { - Task { - try await sourceControlManager.applyStashEntry(stashEntry: stashEntryToApply) - applyStashedChangesIsPresented = false - stashEntryToApply = nil - } - } - } - Button("Apply and Delete") { - if let stashEntry = stashEntryToApply { - Task { - try await sourceControlManager.applyStashEntry(stashEntry: stashEntry) - try await sourceControlManager.deleteStashEntry(stashEntry: stashEntry) - applyStashedChangesIsPresented = false - stashEntryToApply = nil - } - } - } - Button("Cancel", role: .cancel) {} - } else { - Button("Okay", role: .cancel) {} - } - } message: { - sourceControlManager.changedFiles.isEmpty - ? Text("Applying the stashed changes will restore modifications to files in your local repository.") - : Text("Try committing or discarding the changes.") - } - .confirmationDialog( - "Do you want to delete the branch “\(branchToDelete?.name ?? "")”?", - isPresented: $isPresentingConfirmDeleteBranch - ) { - Button("Delete") { - if let branch = branchToDelete { - Task { - do { - try await sourceControlManager.deleteBranch(branch: branch) - } catch { - await sourceControlManager.showAlertForError( - title: "Failed to delete", - error: error - ) - } - branchToDelete = nil - } - } - } - } message: { - Text("The branch will be removed from the repository. You can’t undo this action.") - } - .confirmationDialog( - "Do you want to delete the stash “\(stashEntryToDelete?.message ?? "")”?", - isPresented: $isPresentingConfirmDeleteStashEntry - ) { - Button("Delete") { - if let stashEntry = stashEntryToDelete { - Task { - do { - try await sourceControlManager.deleteStashEntry(stashEntry: stashEntry) - } catch { - await sourceControlManager.showAlertForError( - title: "Failed to delete", - error: error - ) - } - stashEntryToDelete = nil - } - } - } - } message: { - Text("The stash will be removed from the repository. You can’t undo this action.") - } - .confirmationDialog( - "Do you want to delete the remote “\(remoteToDelete?.name ?? "")”?", - isPresented: $isPresentingConfirmDeleteRemote - ) { - Button("Delete") { - if let remote = remoteToDelete { - Task { - do { - try await sourceControlManager.deleteRemote(remote: remote) - } catch { - await sourceControlManager.showAlertForError( - title: "Failed to delete", - error: error - ) - } - remoteToDelete = nil - } - } - } - } message: { - Text("The remote will be removed from the repository. You can’t undo this action.") - } - .task { - await sourceControlManager.refreshBranches() - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlAddRemoteView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlAddRemoteView.swift deleted file mode 100644 index 5a186b4d00..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlAddRemoteView.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// SourceControlAddRemoteView.swift -// CodeEdit -// -// Created by Austin Condiff on 11/17/23. -// - -import SwiftUI - -struct SourceControlAddRemoteView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - @Environment(\.dismiss) - private var dismiss - - @State private var name: String = "" - @State private var location: String = "" - - enum FocusedField { - case name, location - } - - @FocusState private var focusedField: FocusedField? - - func submit() { - Task { - do { - try await sourceControlManager.addRemote(name: name, location: location) - name = "" - location = "" - dismiss() - } catch { - await sourceControlManager.showAlertForError(title: "Failed to add remote", error: error) - } - } - } - - var body: some View { - VStack(spacing: 0) { - Form { - Section("Add Remote") { - TextField("Remote Name", text: $name) - .focused($focusedField, equals: .name) - TextField("Location", text: $location) - .focused($focusedField, equals: .location) - } - } - .formStyle(.grouped) - .scrollDisabled(true) - .scrollContentBackground(.hidden) - .onSubmit(submit) - HStack { - Spacer() - Button("Cancel") { - dismiss() - name = "" - location = "" - } - Button("Add", action: submit) - .buttonStyle(.borderedProminent) - } - .padding(.horizontal, 20) - .padding(.bottom, 20) - } - .frame(minWidth: 480) - .onAppear { - let originExists = sourceControlManager.remotes.contains { $0.name == "origin" } - - if !originExists { - name = "origin" - focusedField = .location - } - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorNewBranchView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorNewBranchView.swift deleted file mode 100644 index cff40057a0..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorNewBranchView.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// SourceControlNavigatorNewBranchView.swift -// CodeEdit -// -// Created by Albert Vinizhanau on 10/21/23. -// - -import SwiftUI - -struct SourceControlNavigatorNewBranchView: View { - @Environment(\.dismiss) - var dismiss - - @State var name: String = "" - let sourceControlManager: SourceControlManager - let fromBranch: GitBranch? - - func submit(_ branch: GitBranch) { - Task { - do { - try await sourceControlManager.newBranch(name: name, from: branch) - await MainActor.run { - dismiss() - } - } catch { - await sourceControlManager.showAlertForError( - title: "Failed to create branch", - error: error - ) - } - } - } - - var body: some View { - if let branch = fromBranch ?? sourceControlManager.currentBranch { - VStack(spacing: 0) { - Form { - Section { - LabeledContent("From", value: branch.name) - TextField("To", text: $name) - } header: { - Text("Create a new branch") - Text( - "Create a branch from the current branch and switch to it. " + - "All uncommited changes will be preserved on the new branch. " - ) - } - } - .formStyle(.grouped) - .scrollDisabled(true) - .scrollContentBackground(.hidden) - .onSubmit { submit(branch) } - HStack { - Spacer() - Button("Cancel") { - dismiss() - } - Button("Create") { - submit(branch) - } - .buttonStyle(.borderedProminent) - .disabled(name.isEmpty) - } - .padding(.horizontal, 20) - .padding(.top, 12) - .padding(.bottom, 20) - } - .frame(maxWidth: 480) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorRenameBranchView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorRenameBranchView.swift deleted file mode 100644 index 1d093d3971..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorRenameBranchView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// SourceControlNavigatorRenameBranchView.swift -// CodeEdit -// -// Created by Austin Condiff on 11/28/23. -// - -import SwiftUI - -struct SourceControlNavigatorRenameBranchView: View { - @Environment(\.dismiss) - var dismiss - - @State var name: String = "" - let sourceControlManager: SourceControlManager - let fromBranch: GitBranch? - - func submit(_ branch: GitBranch) { - Task { - do { - try await sourceControlManager.renameBranch(oldName: branch.name, newName: name) - await MainActor.run { - dismiss() - } - } catch { - await sourceControlManager.showAlertForError( - title: "Failed to create branch", - error: error - ) - } - } - } - - var body: some View { - if let branch = fromBranch ?? sourceControlManager.currentBranch { - VStack(spacing: 0) { - Form { - Section { - LabeledContent("From", value: branch.name) - TextField("To", text: $name) - } header: { - Text("Rename branch") - Text("All uncommited changes will be preserved on the renamed branch.") - } - } - .formStyle(.grouped) - .scrollDisabled(true) - .scrollContentBackground(.hidden) - .onSubmit { submit(branch) } - HStack { - Spacer() - Button("Cancel") { - dismiss() - } - Button("Rename") { - submit(branch) - } - .buttonStyle(.borderedProminent) - .disabled(name.isEmpty) - } - .padding(.horizontal, 20) - .padding(.top, 12) - .padding(.bottom, 20) - } - .frame(maxWidth: 480) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift deleted file mode 100644 index 0135cd2385..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// SourceControlNavigatorToolbarBottom.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI - -struct SourceControlNavigatorToolbarBottom: View { - @EnvironmentObject private var workspace: WorkspaceDocument - @EnvironmentObject var sourceControlManager: SourceControlManager - - @State private var text = "" - @State private var stashChangesIsPresented = false - @State private var noChangesToStashIsPresented = false - @State private var noDiscardChangesIsPresented = false - - var body: some View { - HStack(spacing: 5) { - sourceControlMenu - PaneTextField( - "Filter", - text: $text, - leadingAccessories: { - Image( - systemName: text.isEmpty - ? "line.3.horizontal.decrease.circle" - : "line.3.horizontal.decrease.circle.fill" - ) - .foregroundStyle( - text.isEmpty - ? Color(nsColor: .secondaryLabelColor) - : Color(nsColor: .controlAccentColor) - ) - .padding(.leading, 4) - }, - clearable: true - ) - } - .frame(height: 28, alignment: .center) - .frame(maxWidth: .infinity) - .padding(.horizontal, 5) - .overlay(alignment: .top) { - Divider() - .opacity(0) - } - } - - private var sourceControlMenu: some View { - Menu { - Button("Discard All Changes...") { - guard let sourceControlManager = workspace.sourceControlManager else { return } - if sourceControlManager.changedFiles.isEmpty { - noDiscardChangesIsPresented = true - return - } - if discardChangesDialog() { - workspace.sourceControlManager?.discardAllChanges() - } - } - Button("Stash Changes...") { - if sourceControlManager.changedFiles.isEmpty { - noChangesToStashIsPresented = true - } else { - stashChangesIsPresented = true - } - } - } label: {} - .background { - Image(systemName: "ellipsis.circle") - } - .menuStyle(.borderlessButton) - .menuIndicator(.hidden) - .frame(maxWidth: 18, alignment: .center) - .sheet(isPresented: $stashChangesIsPresented) { - SourceControlStashChangesView() - } - .alert("Cannot Stash Changes", isPresented: $noChangesToStashIsPresented) { - Button("OK", role: .cancel) {} - } message: { - Text("There are no uncommitted changes in the local repository for this project.") - } - .alert("Cannot Discard Changes", isPresented: $noDiscardChangesIsPresented) { - Button("OK", role: .cancel) {} - } message: { - Text("There are no uncommitted changes in the local repository for this project.") - } - } - - /// Renders a Discard Changes Dialog - func discardChangesDialog() -> Bool { - let alert = NSAlert() - - alert.messageText = "Do you want to discard all uncommitted, local changes?" - alert.informativeText = "This operation cannot be undone." - alert.alertStyle = .warning - alert.addButton(withTitle: "Discard") - alert.addButton(withTitle: "Cancel") - - return alert.runModal() == .alertFirstButtonReturn - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift deleted file mode 100644 index c3f534d9be..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// SourceControlNavigatorView.swift -// CodeEdit -// -// Created by Nanashi Li on 2022/05/20. -// - -import SwiftUI - -struct SourceControlNavigatorView: View { - @EnvironmentObject private var workspace: WorkspaceDocument - - var body: some View { - if let sourceControlManager = workspace.workspaceFileManager?.sourceControlManager { - VStack(spacing: 0) { - SourceControlNavigatorTabs() - .environmentObject(sourceControlManager) - .task { - do { - while true { - try await sourceControlManager.fetch() - try await Task.sleep(for: .seconds(10)) - } - } catch { - // TODO: if source fetching fails, display message - } - } - } - .safeAreaInset(edge: .bottom, spacing: 0) { - SourceControlNavigatorToolbarBottom() - .environmentObject(sourceControlManager) - } - } - } -} - -struct SourceControlNavigatorTabs: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - @State private var selectedSection: Int = 0 - - var body: some View { - if sourceControlManager.isGitRepository { - SegmentedControl( - $selectedSection, - options: ["Changes", "History", "Repository"], - prominent: true - ) - .frame(maxWidth: .infinity) - .frame(height: 27) - .padding(.horizontal, 8) - .task { - do { - try await sourceControlManager.refreshRemotes() - try await sourceControlManager.refreshStashEntries() - } catch { - await sourceControlManager.showAlertForError(title: "Error refreshing Git data", error: error) - } - } - Divider() - if selectedSection == 0 { - SourceControlNavigatorChangesView() - } - if selectedSection == 1 { - SourceControlNavigatorHistoryView() - } - if selectedSection == 2 { - SourceControlNavigatorRepositoryView() - } - } else { - CEContentUnavailableView( - "No Repository", - description: "This project is not a git repository.", - systemImage: "externaldrive.fill", - actions: { - Button("Initialize") { - Task { - try await sourceControlManager.initiate() - } - } - } - ) - } - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlStashChangesView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlStashChangesView.swift deleted file mode 100644 index 63ff546ac7..0000000000 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlStashChangesView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// SourceControlAddRemoteView.swift -// CodeEdit -// -// Created by Austin Condiff on 11/17/23. -// - -import SwiftUI - -struct SourceControlStashChangesView: View { - @EnvironmentObject var sourceControlManager: SourceControlManager - @Environment(\.dismiss) - private var dismiss - - @State private var message: String = "" - - func submit() { - Task { - do { - try await sourceControlManager.stashChanges(message: message) - message = "" - dismiss() - } catch { - await sourceControlManager.showAlertForError(title: "Failed to stash changes", error: error) - } - } - } - - var body: some View { - VStack(spacing: 0) { - Form { - Section { - TextField("", text: $message, prompt: Text("Message (optional)"), axis: .vertical) - .labelsHidden() - .lineLimit(3...3) - .contentShape(Rectangle()) - .frame(height: 48) - } header: { - Text("Stash Changes") - Text("Enter a description for your stashed changes so you can reference them later. " + - "Stashes will appear in the Source Control navigator for your repository.") - .multilineTextAlignment(.leading) - .lineLimit(nil) - } - } - .formStyle(.grouped) - .scrollDisabled(true) - .scrollContentBackground(.hidden) - .onSubmit(submit) - HStack { - Spacer() - Button("Cancel") { - message = "" - dismiss() - } - Button("Stash", action: submit) - .buttonStyle(.borderedProminent) - } - .padding(.horizontal, 20) - .padding(.bottom, 20) - } - .frame(width: 480) - } -} diff --git a/CodeEdit/Features/NavigatorArea/ViewModels/NavigatorSidebarViewModel.swift b/CodeEdit/Features/NavigatorArea/ViewModels/NavigatorSidebarViewModel.swift deleted file mode 100644 index 82199b107d..0000000000 --- a/CodeEdit/Features/NavigatorArea/ViewModels/NavigatorSidebarViewModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// NavigatorSidebarViewModel.swift -// CodeEdit -// -// Created by Abe Malla on 7/23/23. -// - -import Foundation - -class NavigatorSidebarViewModel: ObservableObject { - @Published var selectedTab: NavigatorTab? = .project - /// The tab bar items in the Navigator - @Published var tabItems: [NavigatorTab] = [] - - func setNavigatorTab(tab newTab: NavigatorTab) { - selectedTab = newTab - } -} diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift deleted file mode 100644 index 074985d386..0000000000 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// NavigatorAreaView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 17.03.22. -// - -import SwiftUI - -struct NavigatorAreaView: View { - @ObservedObject private var workspace: WorkspaceDocument - @ObservedObject private var extensionManager = ExtensionManager.shared - @ObservedObject public var viewModel: NavigatorSidebarViewModel - - @AppSettings(\.general.navigatorTabBarPosition) - var sidebarPosition: SettingsData.SidebarTabBarPosition - - init(workspace: WorkspaceDocument, viewModel: NavigatorSidebarViewModel) { - self.workspace = workspace - self.viewModel = viewModel - - viewModel.tabItems = [.project, .sourceControl, .search] + - extensionManager - .extensions - .map { ext in - ext.availableFeatures.compactMap { - if case .sidebarItem(let data) = $0, data.kind == .navigator { - return NavigatorTab.uiExtension(endpoint: ext.endpoint, data: data) - } - return nil - } - } - .joined() - } - - var body: some View { - VStack { - if let selection = viewModel.selectedTab { - selection - } else { - NoSelectionInspectorView() - } - } - .safeAreaInset(edge: .leading, spacing: 0) { - if sidebarPosition == .side { - HStack(spacing: 0) { - AreaTabBar(items: $viewModel.tabItems, selection: $viewModel.selectedTab, position: sidebarPosition) - Divider() - } - } - } - .safeAreaInset(edge: .top, spacing: 0) { - if sidebarPosition == .top { - VStack(spacing: 0) { - Divider() - AreaTabBar(items: $viewModel.tabItems, selection: $viewModel.selectedTab, position: sidebarPosition) - Divider() - } - } else { - Divider() - } - } - .environmentObject(workspace) - } -} diff --git a/CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift b/CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift deleted file mode 100644 index 2c2e121608..0000000000 --- a/CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// QuickOpenState.swift -// CodeEditModules/QuickOpen -// -// Created by Marco Carnevali on 05/04/22. -// - -import Combine -import Foundation -import CollectionConcurrencyKit - -final class QuickOpenViewModel: ObservableObject { - @Published var openQuicklyQuery: String = "" - @Published var openQuicklyFiles: [URL] = [] - @Published var isShowingOpenQuicklyFiles: Bool = false - - let fileURL: URL - var runningTask: Task? - - init(fileURL: URL) { - self.fileURL = fileURL - } - - func fetchOpenQuickly() { - let startTime = Date() - guard openQuicklyQuery != "" else { - openQuicklyFiles = [] - self.isShowingOpenQuicklyFiles = !openQuicklyFiles.isEmpty - return - } - - runningTask?.cancel() - runningTask = Task.detached(priority: .userInitiated) { - let enumerator = FileManager.default.enumerator( - at: self.fileURL, - includingPropertiesForKeys: [ - .isRegularFileKey - ], - options: [ - .skipsPackageDescendants - ] - ) - if let filePaths = enumerator?.allObjects as? [URL] { - guard !Task.isCancelled else { return } - /// removes all filePaths which aren't regular files - let filteredFiles = filePaths.filter { url in - do { - let values = try url.resourceValues(forKeys: [.isRegularFileKey]) - return (values.isRegularFile ?? false) - } catch { - return false - } - } - - let files = await filteredFiles.fuzzySearch( - query: self.openQuicklyQuery.trimmingCharacters(in: .whitespaces) - ).concurrentMap { $0.item } - - guard !Task.isCancelled else { return } - await MainActor.run { - self.openQuicklyFiles = files - self.isShowingOpenQuicklyFiles = !self.openQuicklyFiles.isEmpty - print("Duration: \(Date().timeIntervalSince(startTime))") - } - } - } - } -} diff --git a/CodeEdit/Features/QuickOpen/ViewModels/URL+FuzzySearchable.swift b/CodeEdit/Features/QuickOpen/ViewModels/URL+FuzzySearchable.swift deleted file mode 100644 index ef6c363565..0000000000 --- a/CodeEdit/Features/QuickOpen/ViewModels/URL+FuzzySearchable.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// URL+FuzzySearchable.swift -// CodeEdit -// -// Created by Tommy Ludwig on 03.02.24. -// - -import Foundation - -extension URL: FuzzySearchable { - var searchableString: String { - return self.lastPathComponent - } -} diff --git a/CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift b/CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift deleted file mode 100644 index c538f8e0fd..0000000000 --- a/CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// NotList.swift -// CodeEdit -// -// Created by Wouter Hennen on 18/03/2023. -// - -import SwiftUI -import AppKit - -struct NSTableViewWrapper: NSViewRepresentable { - - var data: [Item] - var rowHeight: CGFloat = 50 - - @Binding var selection: Item? - - var itemView: (Item) -> Content - - class NonRespondingScrollView: NSScrollView { - override var acceptsFirstResponder: Bool { false } - } - - class NonRespondingTableView: NSTableView { - override var acceptsFirstResponder: Bool { false } - } - - func makeNSView(context: Context) -> NSScrollView { - let scrollView = NonRespondingScrollView() - scrollView.hasVerticalScroller = true - scrollView.verticalScroller?.controlSize = .mini - - let tableView = NonRespondingTableView() - tableView.headerView = nil - - let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("column")) - column.width = tableView.frame.width - - tableView.addTableColumn(column) - tableView.delegate = context.coordinator - tableView.dataSource = context.coordinator - - scrollView.documentView = tableView - - return scrollView - } - - func updateNSView(_ nsView: NSScrollView, context: Context) { - context.coordinator.parent = self - - if let view = nsView.documentView as? NSTableView { - view.reloadData() - if let selection, let item = data.firstIndex(of: selection) { - view.selectRowIndexes([item], byExtendingSelection: false) - view.scrollRowToVisible(item) - } else { - view.selectRowIndexes([], byExtendingSelection: false) - } - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(parent: self) - } - - class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource { - - var parent: NSTableViewWrapper - - init(parent: NSTableViewWrapper) { - self.parent = parent - } - - func numberOfRows(in tableView: NSTableView) -> Int { - return parent.data.count - } - - class AlwaysActiveTableRowView: NSTableRowView { - override var isEmphasized: Bool { - get { true } - set { } - } - } - - func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { - AlwaysActiveTableRowView() - } - - func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - parent.rowHeight - } - - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - let view = NSHostingView(rootView: parent.itemView(parent.data[row])) - view.translatesAutoresizingMaskIntoConstraints = false - - let cell = NSTableCellView() - cell.addSubview(view) - - NSLayoutConstraint.activate([ - .init( - item: view, - attribute: .centerY, - relatedBy: .equal, - toItem: cell, - attribute: .centerY, - multiplier: 1, - constant: 0 - ), - .init( - item: view, - attribute: .left, - relatedBy: .equal, - toItem: cell, - attribute: .left, - multiplier: 1, - constant: 0 - ), - .init( - item: view, - attribute: .right, - relatedBy: .equal, - toItem: cell, - attribute: .right, - multiplier: 1, - constant: 0 - ) - ]) - - return cell - } - - func tableViewSelectionDidChange(_ notification: Notification) { - if let view = notification.object as? NSTableView { - let newSelection = parent.data[view.selectedRow] - if newSelection != parent.selection { - parent.selection = newSelection - } - } - } - } -} diff --git a/CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift b/CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift deleted file mode 100644 index ad3aa8a12d..0000000000 --- a/CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// QuickOpenItem.swift -// CodeEditModules/QuickOpen -// -// Created by Pavel Kasila on 20.03.22. -// - -import SwiftUI - -struct QuickOpenItem: View { - private let baseDirectory: URL - private let fileURL: URL - - init( - baseDirectory: URL, - fileURL: URL - ) { - self.baseDirectory = baseDirectory - self.fileURL = fileURL - } - - var relativePathComponents: ArraySlice { - return fileURL.pathComponents.dropFirst(baseDirectory.pathComponents.count).dropLast() - } - - var body: some View { - HStack(spacing: 8) { - Image(nsImage: NSWorkspace.shared.icon(forFile: fileURL.path)) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 24, height: 24) - VStack(alignment: .leading, spacing: 0) { - Text(fileURL.lastPathComponent).font(.system(size: 13)) - .lineLimit(1) - Text(relativePathComponents.joined(separator: " ▸ ")) - .font(.system(size: 10.5)) - .foregroundColor(.secondary) - .lineLimit(1) - .truncationMode(.middle) - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .frame(maxWidth: .infinity) - } -} diff --git a/CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift b/CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift deleted file mode 100644 index 38ba4953d9..0000000000 --- a/CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// QuickOpenPreviewView.swift -// CodeEditModules/QuickOpen -// -// Created by Pavel Kasila on 20.03.22. -// - -import SwiftUI - -struct QuickOpenPreviewView: View { - - private let queue = DispatchQueue(label: "app.codeedit.CodeEdit.quickOpen.preview") - private let item: CEWorkspaceFile - - @ObservedObject var document: CodeFileDocument - - init( - item: CEWorkspaceFile - ) { - self.item = item - let doc = try? CodeFileDocument( - for: item.url, - withContentsOf: item.url, - ofType: "public.source-code" - ) - self._document = .init(wrappedValue: doc ?? .init()) - } - - var body: some View { - if let url = document.fileURL { - if url.isImage() { - if let image = NSImage(contentsOf: url) { - GeometryReader { proxy in - if image.size.width > proxy.size.width || image.size.height > proxy.size.height { - OtherFileView(document) - } else { - // FIXME: The following code causes a bug where the image size doesn't change when zooming. - // The proper version found in WorkspaceCodeFile.swift line 59 to 60. - // Cannot use that code as the image obscures the open quickly overlay. - // There might be a solution for this in QuickOpenView.swift or OverlayView.swift - - OtherFileView(document) - .frame( - width: image.size.width, - height: image.size.height - ) - .position(x: proxy.frame(in: .local).midX, y: proxy.frame(in: .local).midY) - } - } - } else { - OtherFileView(document) - } - } else { - CodeFileView(codeFile: document, isEditable: false) - } - } - } -} diff --git a/CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift b/CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift deleted file mode 100644 index 0983ee2d7b..0000000000 --- a/CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// QuickOpenView.swift -// CodeEditModules/QuickOpen -// -// Created by Pavel Kasila on 20.03.22. -// - -import SwiftUI - -extension URL: Identifiable { - public var id: String { - absoluteString - } -} - -struct QuickOpenView: View { - @EnvironmentObject private var workspace: WorkspaceDocument - - private let onClose: () -> Void - private let openFile: (CEWorkspaceFile) -> Void - - @ObservedObject private var state: QuickOpenViewModel - - @State private var selectedItem: CEWorkspaceFile? - - init( - state: QuickOpenViewModel, - onClose: @escaping () -> Void, - openFile: @escaping (CEWorkspaceFile) -> Void - ) { - self.state = state - self.onClose = onClose - self.openFile = openFile - } - - var body: some View { - OverlayView( - title: "Open Quickly", - image: Image(systemName: "magnifyingglass"), - options: $state.openQuicklyFiles, - text: $state.openQuicklyQuery, - optionRowHeight: 40 - ) { file in - QuickOpenItem(baseDirectory: state.fileURL, fileURL: file) - } preview: { fileURL in - QuickOpenPreviewView(item: CEWorkspaceFile(url: fileURL)) - } onRowClick: { fileURL in - guard let file = workspace.workspaceFileManager?.getFile( - fileURL.relativePath, - createIfNotFound: true - ) else { - return - } - openFile(file) - state.openQuicklyQuery = "" - onClose() - } onClose: { - onClose() - } - .onReceive(state.$openQuicklyQuery.debounce(for: 0.2, scheduler: DispatchQueue.main)) { _ in - state.fetchOpenQuickly() - } - } -} diff --git a/CodeEdit/Features/Search/Extensions/String+SafeOffset.swift b/CodeEdit/Features/Search/Extensions/String+SafeOffset.swift deleted file mode 100644 index a1c3ffadae..0000000000 --- a/CodeEdit/Features/Search/Extensions/String+SafeOffset.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// String+SafeOffset.swift -// CodeEdit -// -// Created by Khan Winter on 7/15/22. -// - -import Foundation - -/// Some safer alternative methods to ``String. -extension String { - /// Safely returns an offset index in a string. - /// Use ``safeOffset(_:offsetBy:)`` to default to limiting to the start or end indexes. - /// - Parameters: - /// - idx: The index to start at. - /// - offsetBy: The number (of characters) to offset from the first index. - /// - limitedBy: An index to limit the offset by. - /// - Returns: A `String.Index` - func safeOffset(_ idx: String.Index, offsetBy offset: Int, limitedBy: String.Index) -> String.Index { - // This is the odd case this method solves. Swift's - // ``String.index(_:offsetBy:limitedBy:)`` - // will crash if the given index is equal to the offset, and - // we try to go outside of the string's limits anyways. - if idx == limitedBy { - return limitedBy - } else if offset < 0 { - // If the offset is going backwards, but the limit index - // is ahead in the string we return the original index. - if limitedBy > idx { - return idx - } - - // Return the index offset by the given offset. - // If this index is nil we return the limit index. - return index(idx, offsetBy: offset, limitedBy: limitedBy) ?? limitedBy - } else if offset > 0 { - // If the offset is going forwards, but the limit index - // is behind in the string we return the original index. - if limitedBy < idx { - return idx - } - - // Return the index offset by the given offset. - // If this index is nil we return the limit index. - return index(idx, offsetBy: offset, limitedBy: limitedBy) ?? limitedBy - } else { - // The offset is 0, so we return the limit index. - return limitedBy - } - } - - /// Safely returns an offset index in a string. - /// This method will default to limiting to the start or end of the string. - /// See ``safeOffset(_:offsetBy:limitedBy:)`` for custom limit indexes. - /// - Parameters: - /// - idx: The index to start at. - /// - offsetBy: The number (of characters) to offset from the first index. - /// - Returns: A `String.Index` - func safeOffset(_ idx: String.Index, offsetBy offset: Int) -> String.Index { - if offset < 0 { - return safeOffset(idx, offsetBy: offset, limitedBy: self.startIndex) - } else if offset > 0 { - return safeOffset(idx, offsetBy: offset, limitedBy: self.endIndex) - } else { - // If the offset is 0 we return the original index. - return idx - } - } -} diff --git a/CodeEdit/Features/Search/FuzzySearch/Collection+FuzzySearch.swift b/CodeEdit/Features/Search/FuzzySearch/Collection+FuzzySearch.swift deleted file mode 100644 index 828866e391..0000000000 --- a/CodeEdit/Features/Search/FuzzySearch/Collection+FuzzySearch.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Collection+FuzzySearch.swift -// CodeEdit -// -// Created by Tommy Ludwig on 03.02.24. -// - -import Foundation -import CollectionConcurrencyKit - -extension Collection where Iterator.Element: FuzzySearchable { - /// Asynchronously performs a fuzzy search on a collection of elements conforming to FuzzySearchable. - /// - /// - Parameter query: The query string to match against the elements. - /// - /// - Returns: An array of tuples containing FuzzySearchMatchResult and the corresponding element. - /// - /// - Note: Because this is an extension on Collection and not only array, - /// you can also use this on sets. - func fuzzySearch(query: String) async -> [(result: FuzzySearchMatchResult, item: Iterator.Element)] { - return await concurrentMap { - (result: $0.fuzzyMatch(query: query), item: $0) - }.filter { - $0.result.weight > 0 - }.sorted { - $0.result.weight > $1.result.weight - } - } -} diff --git a/CodeEdit/Features/Search/FuzzySearch/FuzzySearchModels.swift b/CodeEdit/Features/Search/FuzzySearch/FuzzySearchModels.swift deleted file mode 100644 index 9f5511a701..0000000000 --- a/CodeEdit/Features/Search/FuzzySearch/FuzzySearchModels.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// FuzzySearchModels.swift -// CodeEdit -// -// Created by Tommy Ludwig on 03.02.24. -// - -import Foundation - -/// FuzzySearchCharacters is used to normalise strings -struct FuzzySearchCharacter { - let content: String - // normalised content is referring to a string that is case- and accent-insensitive - let normalisedContent: String -} - -/// FuzzySearchString is just made up by multiple characters, similar to a string, but also with normalised characters -struct FuzzySearchString { - var characters: [FuzzySearchCharacter] -} - -/// FuzzySearchMatchResult represents an object that has undergone a fuzzy search using the fuzzyMatch function. -struct FuzzySearchMatchResult { - let weight: Int - let matchedParts: [NSRange] -} diff --git a/CodeEdit/Features/Search/FuzzySearch/FuzzySearchable.swift b/CodeEdit/Features/Search/FuzzySearch/FuzzySearchable.swift deleted file mode 100644 index a961c2ad88..0000000000 --- a/CodeEdit/Features/Search/FuzzySearch/FuzzySearchable.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// FuzzySearchable.swift -// CodeEdit -// -// Created by Tommy Ludwig on 03.02.24. -// - -import Foundation - -/// A protocol defining the requirements for an object that can be searched using fuzzy matching. -protocol FuzzySearchable { - var searchableString: String { get } - - /// Performs a fuzzy search on the conforming object's searchable string. - /// - /// - Parameters: - /// - query: The query string to match against the searchable content. - /// - characters: The set of characters used for fuzzy matching. - /// - /// - Returns: A FuzzySearchMatchResult indicating the result of the fuzzy search. - func fuzzyMatch(query: String, characters: FuzzySearchString) -> FuzzySearchMatchResult -} - -extension FuzzySearchable { - func fuzzyMatch(query: String, characters: FuzzySearchString) -> FuzzySearchMatchResult { - let compareString = characters.characters - - let searchString = query.lowercased() - - var totalScore = 0 - var matchedParts = [NSRange]() - - var patternIndex = 0 - var currentScore = 0 - var currentMatchedPart = NSRange(location: 0, length: 0) - - for (index, character) in compareString.enumerated() { - if let prefixLength = searchString.lengthOfMatchingPrefix(prefix: character, startingAt: patternIndex) { - patternIndex += prefixLength - currentScore += 1 - currentMatchedPart.length += 1 - } else { - currentScore = 0 - if currentMatchedPart.length != 0 { - matchedParts.append(currentMatchedPart) - } - currentMatchedPart = NSRange(location: index + 1, length: 0) - } - - totalScore += currentScore - } - - if currentMatchedPart.length != 0 { - matchedParts.append(currentMatchedPart) - } - - if searchString.count == matchedParts.reduce(0, { partialResult, range in - range.length + partialResult - }) { - return FuzzySearchMatchResult(weight: totalScore, matchedParts: matchedParts) - } else { - return FuzzySearchMatchResult(weight: 0, matchedParts: []) - } - } - - /// Normalises the searchable string of the conforming object by converting its characters to ASCII representation. - /// The resulting FuzzySearchString contains both the original and normalised content of each character. - /// - /// - Returns: A FuzzySearchString - func normaliseString() -> FuzzySearchString { - return FuzzySearchString(characters: searchableString.normalise()) - } - - /// Performs a fuzzy search on the normalised content of the conforming object's searchable string. - /// - /// - Parameter query: The query string to match against the normalised searchable content. - /// - /// - Returns: A FuzzySearchMatchResult indicating the result of the fuzzy search. - func fuzzyMatch(query: String) -> FuzzySearchMatchResult { - let characters = normaliseString() - - return fuzzyMatch(query: query, characters: characters) - } -} diff --git a/CodeEdit/Features/Search/FuzzySearch/String+LengthOfMatchingPrefix.swift b/CodeEdit/Features/Search/FuzzySearch/String+LengthOfMatchingPrefix.swift deleted file mode 100644 index 4533ca8415..0000000000 --- a/CodeEdit/Features/Search/FuzzySearch/String+LengthOfMatchingPrefix.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// String+LengthOfMatchingPrefix.swift -// CodeEdit -// -// Created by Tommy Ludwig on 03.02.24. -// - -import Foundation - -extension String { - /// Returns the length of the matching prefix content or normalised content at the specified index. - /// - /// - Parameters: - /// - prefix: The FuzzySearchCharacter whose content or normalised content to check for a prefix match. - /// - index: The index from which to start searching for the prefix. - /// - /// - Returns: The length of the matching prefix, or nil if no match is found. - func lengthOfMatchingPrefix(prefix: FuzzySearchCharacter, startingAt index: Int) -> Int? { - guard let stringIndex = self.index(self.startIndex, offsetBy: index, limitedBy: self.endIndex) else { - return nil - } - - let searchString = self.suffix(from: stringIndex) - - for prefix in [prefix.content, prefix.normalisedContent] where searchString.hasPrefix(prefix) { - return prefix.count - } - - return nil - } -} diff --git a/CodeEdit/Features/Search/FuzzySearch/String+Normalise.swift b/CodeEdit/Features/Search/FuzzySearch/String+Normalise.swift deleted file mode 100644 index 40e9ee13ae..0000000000 --- a/CodeEdit/Features/Search/FuzzySearch/String+Normalise.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// String+Normalise.swift -// CodeEdit -// -// Created by Tommy Ludwig on 03.02.24. -// - -import Foundation - -extension String { - /// Normalises the characters of the string by converting them to ASCII representation. - /// Each character is transformed into its ASCII equivalent, and the resulting array - /// of FuzzySearchCharacter objects contains both the original and normalised content. - /// - /// - Returns: An array of FuzzySearchCharacter objects representing the original and - /// normalised content of each character in the string. - func normalise() -> [FuzzySearchCharacter] { - return self.lowercased().map { char in - guard let data = String(char).data(using: .ascii, allowLossyConversion: true), - let normalisedCharacter = String(data: data, encoding: .ascii) else { - return FuzzySearchCharacter(content: String(char), normalisedContent: String(char)) - } - - return FuzzySearchCharacter(content: String(char), normalisedContent: normalisedCharacter) - } - } -} diff --git a/CodeEdit/Features/Search/Model/SearchModeModel.swift b/CodeEdit/Features/Search/Model/SearchModeModel.swift deleted file mode 100644 index 007651727c..0000000000 --- a/CodeEdit/Features/Search/Model/SearchModeModel.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// SearchModeModel.swift -// CodeEditModules/Search -// -// Created by Ziyuan Zhao on 2022/3/22. -// - -import Foundation - -// TODO: DOCS (Ziyuan Zhao) -struct SearchModeModel: Hashable { - let title: String - let children: [SearchModeModel] - let needSelectionHighlight: Bool - - static let Containing = SearchModeModel(title: "Containing", children: [], needSelectionHighlight: false) - static let MatchingWord = SearchModeModel( - title: "Matching Word", - children: [], - needSelectionHighlight: true - ) - static let StartingWith = SearchModeModel( - title: "Starting With", - children: [], - needSelectionHighlight: true - ) - static let EndingWith = SearchModeModel(title: "Ending With", children: [], needSelectionHighlight: true) - - static let Text = SearchModeModel( - title: "Text", - children: [.Containing, .MatchingWord, .StartingWith, .EndingWith], - needSelectionHighlight: false - ) - static let References = SearchModeModel( - title: "References", - children: [.Containing, .MatchingWord, .StartingWith, .EndingWith], - needSelectionHighlight: true - ) - static let Definitions = SearchModeModel( - title: "Definitions", - children: [.Containing, .MatchingWord, .StartingWith, .EndingWith], - needSelectionHighlight: true - ) - static let RegularExpression = SearchModeModel( - title: "Regular Expression", - children: [], - needSelectionHighlight: true - ) - static let CallHierarchy = SearchModeModel( - title: "Call Hierarchy", - children: [], - needSelectionHighlight: true - ) - - static let Find = SearchModeModel( - title: "Find", - children: [.Text, .References, .Definitions, .RegularExpression, .CallHierarchy], - needSelectionHighlight: false - ) - static let Replace = SearchModeModel( - title: "Replace", - children: [.Text, .RegularExpression], - needSelectionHighlight: true - ) - - static let TextMatchingModes: [SearchModeModel] = [.Containing, .MatchingWord, .StartingWith, .EndingWith] - static let FindModes: [SearchModeModel] = [ - .Text, - .References, - .Definitions, - .RegularExpression, - .CallHierarchy - ] - static let ReplaceModes: [SearchModeModel] = [.Text, .RegularExpression] - static let SearchModes: [SearchModeModel] = [.Find, .Replace] -} - -extension SearchModeModel: Equatable { - static func == (lhs: SearchModeModel, rhs: SearchModeModel) -> Bool { - lhs.title == rhs.title - && lhs.children == rhs.children - && lhs.needSelectionHighlight == rhs.needSelectionHighlight - } -} diff --git a/CodeEdit/Features/Search/Model/SearchResultMatchModel.swift b/CodeEdit/Features/Search/Model/SearchResultMatchModel.swift deleted file mode 100644 index 1ad0b68102..0000000000 --- a/CodeEdit/Features/Search/Model/SearchResultMatchModel.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// SearchResultLineMatchModel.swift -// CodeEditModules/Search -// -// Created by Khan Winter on 7/6/22. -// - -import Foundation -import Cocoa - -/// A struct for holding information about a search match. -class SearchResultMatchModel: Hashable, Identifiable { - init( - rangeWithinFile: Range, - file: CEWorkspaceFile, - lineContent: String, - keywordRange: Range - ) { - self.id = UUID() - self.file = file - self.rangeWithinFile = rangeWithinFile - self.lineContent = lineContent - self.keywordRange = keywordRange - } - - var id: UUID - var file: CEWorkspaceFile - var rangeWithinFile: Range - var lineContent: String - var keywordRange: Range - - static func == (lhs: SearchResultMatchModel, rhs: SearchResultMatchModel) -> Bool { - return lhs.id == rhs.id - && lhs.file == rhs.file - && lhs.rangeWithinFile == rhs.rangeWithinFile - && lhs.lineContent == rhs.lineContent - && lhs.keywordRange == rhs.keywordRange - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - hasher.combine(file) - hasher.combine(rangeWithinFile) - hasher.combine(lineContent) - hasher.combine(keywordRange) - } - - /// Returns a formatted `NSAttributedString` with the search result bolded. - /// Will only return 60 characters before and after the matched result. - /// - Returns: The formatted `NSAttributedString` - func attributedLabel() -> NSAttributedString { - // By default `NSTextView` will ignore any paragraph wrapping set to the label when it's - // using an `NSAttributedString` so we need to set the wrap mode here. - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.lineBreakMode = .byCharWrapping - - let normalAttributes: [NSAttributedString.Key: Any] = [ - .font: NSFont.systemFont( - ofSize: 13, - weight: .regular - ), - .foregroundColor: NSColor.secondaryLabelColor, - .paragraphStyle: paragraphStyle - ] - let boldAttributes: [NSAttributedString.Key: Any] = [ - .font: NSFont.systemFont( - ofSize: 13, - weight: .bold - ), - .foregroundColor: NSColor.labelColor, - .paragraphStyle: paragraphStyle - ] - - // Set up the search result string with the matched search in bold. - let prefix = String(lineContent[.. Bool { - return lhs.file == rhs.file - && lhs.lineMatches == rhs.lineMatches - } - - func hash(into hasher: inout Hasher) { - hasher.combine(file) - hasher.combine(lineMatches) - } - -} diff --git a/CodeEdit/Features/Settings/Models/AppSettings.swift b/CodeEdit/Features/Settings/Models/AppSettings.swift deleted file mode 100644 index d7a115df37..0000000000 --- a/CodeEdit/Features/Settings/Models/AppSettings.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// AppSettings.swift -// CodeEdit -// -// Created by Wouter Hennen on 12/04/2023. -// - -import Foundation -import SwiftUI - -@propertyWrapper -struct AppSettings: DynamicProperty where T: Equatable { - - var settings: Environment - - let keyPath: WritableKeyPath - - init(_ keyPath: WritableKeyPath) { - self.keyPath = keyPath - let settingsKeyPath = (\EnvironmentValues.settings).appending(path: keyPath) - self.settings = Environment(settingsKeyPath) - } - - var wrappedValue: T { - get { - Settings.shared.preferences[keyPath: keyPath] - } - nonmutating set { - Settings.shared.preferences[keyPath: keyPath] = newValue - } - } - - var projectedValue: Binding { - Binding { - Settings.shared.preferences[keyPath: keyPath] - } set: { - Settings.shared.preferences[keyPath: keyPath] = $0 - } - } -} - -struct SettingsDataEnvironmentKey: EnvironmentKey { - static var defaultValue: SettingsData = .init() -} - -extension EnvironmentValues { - var settings: SettingsDataEnvironmentKey.Value { - get { self[SettingsDataEnvironmentKey.self] } - set { self[SettingsDataEnvironmentKey.self] = newValue } - } -} diff --git a/CodeEdit/Features/Settings/Models/PageAndSettings.swift b/CodeEdit/Features/Settings/Models/PageAndSettings.swift deleted file mode 100644 index 3297fcb06d..0000000000 --- a/CodeEdit/Features/Settings/Models/PageAndSettings.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// PageAndSettings.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 10/07/23. -// - -import Foundation - -struct PageAndSettings: Identifiable, Equatable { - let id: UUID = UUID() - let page: SettingsPage - let settings: [SettingsPage] - - init(_ page: SettingsPage) { - self.page = page - self.settings = SettingsData().propertiesOf(page.name) - } -} diff --git a/CodeEdit/Features/Settings/Models/Settings.swift b/CodeEdit/Features/Settings/Models/Settings.swift deleted file mode 100644 index deee382ebe..0000000000 --- a/CodeEdit/Features/Settings/Models/Settings.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// SettingsModel.swift -// CodeEditModules/Settings -// -// Created by Lukas Pistrol on 01.04.22. -// - -import Foundation -import SwiftUI -import Combine - -/// The Preferences View Model. Accessible via the singleton "``SettingsModel/shared``". -/// -/// **Usage:** -/// ```swift -/// @StateObject -/// private var prefs: SettingsModel = .shared -/// ``` -final class Settings: ObservableObject { - - /// The publicly available singleton instance of ``SettingsModel`` - static let shared: Settings = .init() - - private var storeTask: AnyCancellable! - - private init() { - self.preferences = .init() - self.preferences = loadSettings() - - self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink { - try? self.savePreferences($0) - } - } - - static subscript(_ path: WritableKeyPath, suite: Settings = .shared) -> T { - get { - suite.preferences[keyPath: path] - } - set { - suite.preferences[keyPath: path] = newValue - } - } - - /// Published instance of the ``Settings`` model. - /// - /// Changes are saved automatically. - @Published var preferences: SettingsData - - /// Load and construct ``Settings`` model from - /// `~/Library/Application Support/CodeEdit/settings.json` - private func loadSettings() -> SettingsData { - if !filemanager.fileExists(atPath: settingsURL.path) { - try? filemanager.createDirectory(at: baseURL, withIntermediateDirectories: false) - return .init() - } - - guard let json = try? Data(contentsOf: settingsURL), - let prefs = try? JSONDecoder().decode(SettingsData.self, from: json) - else { - return .init() - } - return prefs - } - - /// Save``Settings`` model to - /// `~/Library/Application Support/CodeEdit/settings.json` - private func savePreferences(_ data: SettingsData) throws { - print("Saving...") - let data = try JSONEncoder().encode(data) - let json = try JSONSerialization.jsonObject(with: data) - let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) - try prettyJSON.write(to: settingsURL, options: .atomic) - } - - /// Default instance of the `FileManager` - private let filemanager = FileManager.default - - /// The base URL of settings. - /// - /// Points to `~/Library/Application Support/CodeEdit/` - internal var baseURL: URL { - filemanager - .homeDirectoryForCurrentUser - .appendingPathComponent("Library/Application Support/CodeEdit", isDirectory: true) - } - - /// The URL of the `settings.json` settings file. - /// - /// Points to `~/Library/Application Support/CodeEdit/settings.json` - private var settingsURL: URL { - baseURL - .appendingPathComponent("settings") - .appendingPathExtension("json") - } -} diff --git a/CodeEdit/Features/Settings/Models/SettingsData.swift b/CodeEdit/Features/Settings/Models/SettingsData.swift deleted file mode 100644 index 1e04636737..0000000000 --- a/CodeEdit/Features/Settings/Models/SettingsData.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// Settings.swift -// CodeEditModules/Settings -// -// Created by Lukas Pistrol on 01.04.22. -// - -import SwiftUI -import Foundation - -/// # Settings -/// -/// The model structure of settings for `CodeEdit` -/// -/// A `JSON` representation is persisted in `~/Library/Application Support/CodeEdit/preference.json`. -/// - Attention: Don't use `UserDefaults` for persisting user accessible settings. -/// If a further setting is needed, extend the struct like ``GeneralSettings``, -/// ``ThemeSettings``, or ``TerminalSettings`` does. -/// -/// - Note: Also make sure to implement the ``init(from:)`` initializer, decoding -/// all properties with -/// [`decodeIfPresent`](https://developer.apple.com/documentation/swift/keyeddecodingcontainer/2921389-decodeifpresent) -/// and providing a default value. Otherwise all settings get overridden. -struct SettingsData: Codable, Hashable { - - /// The general global settings - var general: GeneralSettings = .init() - - /// The global settings for accounts - var accounts: AccountsSettings = .init() - - /// The global settings for themes - var navigation: NavigationSettings = .init() - - /// The global settings for themes - var theme: ThemeSettings = .init() - - /// The global settings for text editing - var textEditing: TextEditingSettings = .init() - - /// The global settings for the terminal emulator - var terminal: TerminalSettings = .init() - - /// The global settings for source control - var sourceControl: SourceControlSettings = .init() - - /// The global settings for keybindings - var keybindings: KeybindingsSettings = .init() - - /// Searh Settings - var search: SearchSettings = .init() - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.general = try container.decodeIfPresent(GeneralSettings.self, forKey: .general) ?? .init() - self.accounts = try container.decodeIfPresent(AccountsSettings.self, forKey: .accounts) ?? .init() - self.navigation = try container.decodeIfPresent(NavigationSettings.self, forKey: .navigation) ?? .init() - self.theme = try container.decodeIfPresent(ThemeSettings.self, forKey: .theme) ?? .init() - self.terminal = try container.decodeIfPresent(TerminalSettings.self, forKey: .terminal) ?? .init() - self.textEditing = try container.decodeIfPresent(TextEditingSettings.self, forKey: .textEditing) ?? .init() - self.search = try container.decodeIfPresent(SearchSettings.self, forKey: .search) ?? .init() - self.sourceControl = try container.decodeIfPresent( - SourceControlSettings.self, - forKey: .sourceControl - ) ?? .init() - self.keybindings = try container.decodeIfPresent( - KeybindingsSettings.self, - forKey: .keybindings - ) ?? .init() - } - - // swiftlint:disable cyclomatic_complexity - func propertiesOf(_ name: SettingsPage.Name) -> [SettingsPage] { - var settings: [SettingsPage] = [] - - switch name { - case .general: - general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .accounts: - accounts.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .navigation: - navigation.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .theme: - theme.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .textEditing: - textEditing.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .terminal: - terminal.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .search: - search.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .sourceControl: - sourceControl.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .location: - LocationsSettings().searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } - case .behavior: return [.init(name, settingName: "Error")] - case .components: return [.init(name, settingName: "Error")] - case .keybindings: return [.init(name, settingName: "Error")] - case .advanced: return [.init(name, settingName: "Error")] - } - - return settings - } - // swiftlint:enable cyclomatic_complexity -} diff --git a/CodeEdit/Features/Settings/Models/SettingsInjector.swift b/CodeEdit/Features/Settings/Models/SettingsInjector.swift deleted file mode 100644 index 301991273d..0000000000 --- a/CodeEdit/Features/Settings/Models/SettingsInjector.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// SettingsInjector.swift -// CodeEdit -// -// Created by Wouter Hennen on 28/04/2023. -// - -import SwiftUI - -struct SettingsInjector: View { - - @ObservedObject var settings = Settings.shared - - @ViewBuilder var content: Content - - var body: some View { - content - .environment(\.settings, settings.preferences) - } -} diff --git a/CodeEdit/Features/Settings/Models/SettingsPage.swift b/CodeEdit/Features/Settings/Models/SettingsPage.swift deleted file mode 100644 index d8caed9933..0000000000 --- a/CodeEdit/Features/Settings/Models/SettingsPage.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// SettingsPage.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 30/03/23. -// - -import Foundation -import SwiftUI - -/// A struct for a settings page -struct SettingsPage: Hashable, Equatable, Identifiable { - /// A struct for a sidebar icon, with a base color and SF Symbol - enum IconResource: Equatable, Hashable { - case system(_ name: String) - case symbol(_ name: String) - case asset(_ name: String) - } - - /// An enum of all the settings pages - enum Name: String { - case general = "General" - case accounts = "Accounts" - case behavior = "Behaviors" - case navigation = "Navigation" - case theme = "Themes" - case textEditing = "Text Editing" - case terminal = "Terminal" - case search = "Search" - case keybindings = "Key Bindings" - case sourceControl = "Source Control" - case components = "Components" - case location = "Locations" - case advanced = "Advanced" - } - - let id: UUID = UUID() - - let name: Name - let baseColor: Color? - let isSetting: Bool - let settingName: String - var nameString: LocalizedStringKey { - LocalizedStringKey(name.rawValue) - } - let icon: IconResource? - - /// Default initializer - init( - _ name: Name, - baseColor: Color? = nil, - icon: IconResource? = nil, - isSetting: Bool = false, - settingName: String = "" - ) { - self.name = name - self.baseColor = baseColor - self.icon = icon - self.isSetting = isSetting - self.settingName = settingName - } -} diff --git a/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift b/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift deleted file mode 100644 index 22f2ab2564..0000000000 --- a/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SettingsSearchResult.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 17/06/23. -// - -import Foundation -import SwiftUI - -// TODO: Extend this struct further to support setting "flashing" -class SettingsSearchResult: Identifiable { - init( - pageFound: Bool, - pages: [SettingsPage] - ) { - self.pageFound = pageFound - self.pages = pages - } - - let id: UUID = UUID() - - let pageFound: Bool - let pages: [SettingsPage] -} diff --git a/CodeEdit/Features/Settings/Models/SettingsSidebarFix.swift b/CodeEdit/Features/Settings/Models/SettingsSidebarFix.swift deleted file mode 100644 index d3076240ec..0000000000 --- a/CodeEdit/Features/Settings/Models/SettingsSidebarFix.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SettingsSidebarFix.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/04/2023. -// - -import Foundation -import AppKit - -extension NSSplitViewItem { - @objc fileprivate var canCollapseSwizzled: Bool { - if let check = self.viewController.view.window?.isSettingsWindow, check { - return false - } - return self.canCollapseSwizzled - } - - static func swizzle() { - let origSelector = #selector(getter: NSSplitViewItem.canCollapse) - let swizzledSelector = #selector(getter: canCollapseSwizzled) - let originalMethodSet = class_getInstanceMethod(self as AnyClass, origSelector) - let swizzledMethodSet = class_getInstanceMethod(self as AnyClass, swizzledSelector) - - method_exchangeImplementations(originalMethodSet!, swizzledMethodSet!) - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountSelectionView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountSelectionView.swift deleted file mode 100644 index 5d0db5d5ba..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountSelectionView.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// AccoundSelectionView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/5/23. -// - -import SwiftUI - -struct AccountSelectionView: View { - @Environment(\.dismiss) - var dismiss - - @Binding var selectedProvider: SourceControlAccount.Provider? - - var gitProviders = SourceControlAccount.Provider.allCases - - var body: some View { - VStack(spacing: 0) { - Form { - Section { - VStack(alignment: .leading, spacing: 0) { - ForEach(gitProviders, id: \.self) { provider in - AccountsSettingsProviderRow( - name: provider.name, - iconName: provider.iconName, - action: { - selectedProvider = provider - dismiss() - } - ) - Divider() - } - } - .padding(-10) - } footer: { - HStack { - Spacer() - Button { - dismiss() - } label: { - Text("Cancel") - .padding(.horizontal) - } - .buttonStyle(.borderedProminent) - .controlSize(.large) - } - .padding(.top, 10) - } - } - .formStyle(.grouped) - .scrollDisabled(true) - } - .frame(width: 300) - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift deleted file mode 100644 index ed3621cd2a..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// AccountsSettingsAccountLink.swift -// CodeEdit -// -// Created by Austin Condiff on 4/30/23. -// - -import SwiftUI - -struct AccountsSettingsAccountLink: View { - @Binding var account: SourceControlAccount - - init(_ account: Binding) { - _account = account - } - - var body: some View { - NavigationLink(destination: AccountsSettingsDetailsView($account)) { - Label { - Text(account.description) - Text(account.name) - .font(.footnote) - .foregroundColor(.secondary) - } icon: { - Image(account.provider.iconName) - .resizable() - .aspectRatio(contentMode: .fill) - .cornerRadius(6) - .frame(width: 26, height: 26) - .padding(.top, 2) - .padding(.bottom, 2) - .padding(.leading, 2) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift deleted file mode 100644 index 0f681eb850..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// AccountsSettingsDetailView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/6/23. -// - -import SwiftUI - -struct AccountsSettingsDetailsView: View { - @Environment(\.dismiss) - private var dismiss - @AppSettings(\.accounts.sourceControlAccounts.sshKey) - var sshKey - @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) - var gitAccounts - @Binding var account: SourceControlAccount - - @State var currentAccount: SourceControlAccount - @State var deleteConfirmationIsPresented: Bool = false - @State var prevSshKey: String - @State var createSshKeyIsPresented: Bool = false - - init(_ account: Binding) { - _account = account - _currentAccount = State(initialValue: account.wrappedValue) - _prevSshKey = State(initialValue: account.sshKey.wrappedValue) - } - - /// Default instance of the `FileManager` - private let filemanager = FileManager.default - - /// The URL of the users ssh folder. Points to `~/.ssh` - internal var sshURL: URL { - filemanager - .homeDirectoryForCurrentUser - .appendingPathComponent(".ssh", isDirectory: true) - } - - func isPrivateSSHKey(_ contents: String) -> Bool { - if contents.starts(with: "-----BEGIN OPENSSH PRIVATE KEY-----\n") && - contents.hasSuffix("\n-----END OPENSSH PRIVATE KEY-----\n") { - return true - } else { - return false - } - } - - func isPublicSSHKey(_ contents: String) -> Bool { - let sshKeyPattern = "^ssh-(rsa|dss|ed25519)\\s+[A-Za-z0-9+/]+[=]{0,2}(\\s+.+)?$" - do { - let regex = try NSRegularExpression(pattern: sshKeyPattern) - let range = NSRange(location: 0, length: contents.utf16.count) - return regex.firstMatch(in: contents, options: [], range: range) != nil - } catch { - print("Error creating regular expression: \(error.localizedDescription)") - return false - } - } - - var body: some View { - SettingsForm { - Section { - LabeledContent("Account") { - Text(currentAccount.name) - } - TextField("Description", text: $currentAccount.description) - if currentAccount.provider.baseURL == nil { - TextField("Server", text: $currentAccount.serverURL) - } - } - - Section { - Picker(selection: $currentAccount.urlProtocol) { - Text("HTTPS") - .tag(SourceControlAccount.URLProtocol.https) - Text("SSH") - .tag(SourceControlAccount.URLProtocol.ssh) - } label: { - Text("Clone Using") - Text("New repositories will be cloned from \(currentAccount.provider.name)" - + " using \(currentAccount.urlProtocol.rawValue).") - } - .pickerStyle(.radioGroup) - if currentAccount.urlProtocol == .ssh { - Picker("SSH Key", selection: $currentAccount.sshKey) { - Text("None") - .tag("") - Divider() - if let sshPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent( - ".ssh", - isDirectory: true - ) as URL? { - if let files = try? FileManager.default.contentsOfDirectory( - atPath: sshPath.path - ) { - ForEach(files, id: \.self) { filename in - let fileURL = sshPath.appendingPathComponent(filename) - if let contents = try? String(contentsOf: fileURL) { - if isPublicSSHKey(contents) { - Text(filename.replacingOccurrences(of: ".pub", with: "")) - .tag(fileURL.path) - } - } - } - Divider() - } - } - Text("Create New...") - .tag("CREATE_NEW") - Text("Choose...") - .tag("CHOOSE") - } - .onReceive([currentAccount.sshKey].publisher.first()) { value in - if value == "CREATE_NEW" { - print("Create a new ssh key...") - createSshKeyIsPresented = true - currentAccount.sshKey = prevSshKey - } else if value == "CHOOSE" { - print("Choose a ssh key...") - currentAccount.sshKey = prevSshKey - } else { - // TODO: Validate SSH key and check if it is uploaded to git provider. - // If not provide button to do so - } - prevSshKey = currentAccount.sshKey - } - .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) - } - } footer: { - HStack { - Button("Delete Account...") { - deleteConfirmationIsPresented.toggle() - } - .alert( - Text("Are you sure you want to delete the account “\(account.description)”?"), - isPresented: $deleteConfirmationIsPresented - ) { - Button("OK") { - // Handle the account delete - handleAccountDelete() - dismiss() - } - Button("Cancel") { - // Handle the cancel, dismiss the alert - deleteConfirmationIsPresented.toggle() - } - } message: { - Text("Deleting this account will remove it from CodeEdit.") - } - - Spacer() - } - .padding(.top, 10) - } - } - .onChange(of: currentAccount) { newValue in - account = newValue - } - .navigationTitle(currentAccount.description) - .navigationBarBackButtonVisible() - } - - private func handleAccountDelete() { - // Delete account by finding the position of the account and remove by position - if let gitAccount = gitAccounts.firstIndex(of: account) { - gitAccounts.remove(at: gitAccount) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift deleted file mode 100644 index 154661d78d..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// AccoundsSettingsAccountRow.swift -// CodeEdit -// -// Created by Austin Condiff on 4/5/23. -// - -import SwiftUI - -struct AccountsSettingsProviderRow: View { - var name: String - var iconName: String - var action: () -> Void - - @State private var hovering = false - @State private var pressing = false - - var body: some View { - HStack { - Image(iconName) - .resizable() - .aspectRatio(contentMode: .fill) - .cornerRadius(6) - .frame(width: 28, height: 28) - Text(name) - Spacer() - if hovering { - Image(systemName: "plus") - .foregroundColor(Color(.tertiaryLabelColor)) - .padding(.horizontal, 5) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(10) - .background(pressing ? Color(nsColor: .quaternaryLabelColor) : Color(nsColor: .clear)) - .overlay(Color(.black).opacity(0.0001)) - .onHover { hover in - hovering = hover - } - .pressAction { - pressing = true - } onRelease: { - pressing = false - action() - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift deleted file mode 100644 index b2965e16a7..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift +++ /dev/null @@ -1,250 +0,0 @@ -// -// AccountsSettingsSigninView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/5/23. -// - -import SwiftUI - -struct AccountsSettingsSigninView: View { - @Environment(\.dismiss) - var dismiss - @Environment(\.openURL) - var createToken - - var provider: SourceControlAccount.Provider - @Binding var addAccountSheetPresented: Bool - - init(_ provider: SourceControlAccount.Provider, addAccountSheetPresented: Binding) { - self.provider = provider - self._addAccountSheetPresented = addAccountSheetPresented - } - - @State var server = "" - @State var username = "" - @State var personalAccessToken = "" - - @State var signinErrorAlertIsPresented: Bool = false - @State var signinErrorDetail: String = "" - - @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) - var gitAccounts - - private let keychain = CodeEditKeychain() - - var body: some View { - VStack(spacing: 0) { - Form { - Section( - content: { - if provider.baseURL == nil { - VStack(alignment: .leading, spacing: 5) { - Text("Server") - .font(.caption3) - .foregroundColor(.secondary) - TextField("", text: $server, prompt: Text("https://git.example.com")) - .labelsHidden() - } - } - VStack(alignment: .leading, spacing: 5) { - Text("Username") - .font(.caption3) - .foregroundColor(.secondary) - TextField("", text: $username) - .labelsHidden() - } - VStack(alignment: .leading, spacing: 5) { - Text("Personal Access Token") - .font(.caption3) - .foregroundColor(.secondary) - SecureField("", text: $personalAccessToken) - .labelsHidden() - } - }, - header: { - VStack(alignment: .center, spacing: 10) { - Image(provider.iconName) - .resizable() - .aspectRatio(contentMode: .fill) - .cornerRadius(12) - .frame(width: 52, height: 52) - .padding(.top, 5) - Text("Sign in to \(provider.name)") - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity) - }, - footer: { - VStack(alignment: .leading, spacing: 5) { - if provider == .github { - Text("\(provider.name) personal access tokens must have these scopes set:") - .font(.system(size: 10.5)) - .foregroundColor(.secondary) - HStack(alignment: .center) { - Spacer() - VStack(alignment: .leading) { - HStack(spacing: 2.5) { - Image(systemName: "checkmark") - .font(.system(size: 10.5, weight: .semibold)) - Text("admin:public _key") - .font(.system(size: 10.5)) - } - HStack(spacing: 2.5) { - Image(systemName: "checkmark") - .font(.system(size: 10.5, weight: .semibold)) - Text("write:discussion") - .font(.system(size: 10.5)) - } - HStack(spacing: 2.5) { - Image(systemName: "checkmark") - .font(.system(size: 10.5, weight: .semibold)) - Text("repo") - .font(.system(size: 10.5)) - } - HStack(spacing: 2.5) { - Image(systemName: "checkmark") - .font(.system(size: 10.5, weight: .semibold)) - Text("user") - .font(.system(size: 10.5)) - } - } - Spacer() - } - .foregroundColor(.secondary) - } - Button { - createToken(provider.authHelpURL) - } label: { - if provider.authType == .password { - Text("Create a Password on \(provider.name)") - .font(.system(size: 10.5)) - } else { - Text("Create a Token on \(provider.name)") - .font(.system(size: 10.5)) - } - } - .buttonStyle(.link) - .frame(maxWidth: .infinity, alignment: .leading) - } - .frame(maxWidth: .infinity) - } - ) - } - .formStyle(.grouped) - .scrollDisabled(true) - .onSubmit { - signin() - } - HStack { - Button { - addAccountSheetPresented.toggle() - dismiss() - } label: { - Text("Cancel") - .frame(maxWidth: .infinity) - } - .controlSize(.large) - .frame(maxWidth: .infinity) - - Button { - signin() - } label: { - Text("Sign In") - .frame(maxWidth: .infinity) - } - .disabled(username.isEmpty || personalAccessToken.isEmpty) - .buttonStyle(.borderedProminent) - .controlSize(.large) - .alert( - Text("Unable to add account “\(username)”"), - isPresented: $signinErrorAlertIsPresented - ) { - Button("OK") { - signinErrorAlertIsPresented.toggle() - } - } message: { - Text(signinErrorDetail) - } - } - .padding(.horizontal) - .padding(.bottom) - } - .frame(width: 300) - } - - private func signin() { - if gitAccounts.contains( - where: { - $0.serverURL == provider.baseURL?.absoluteString ?? server && - $0.name.lowercased() == username.lowercased() - } - ) { - // Show alert when adding a duplicated account - signinErrorDetail = "Account with the same username and provider already exists!" - signinErrorAlertIsPresented.toggle() - } else { - let configURL = provider.apiURL?.absoluteString ?? server - switch provider { - case .github, .githubEnterprise: - let config = GitHubTokenConfiguration(personalAccessToken, url: configURL) - GitHubAccount(config).me { response in - switch response { - case .success: - handleGitRequestSuccess() - case .failure(let error): - handleGitRequestFailed(error) - } - } - case .gitlab, .gitlabSelfHosted: - let config = GitLabTokenConfiguration(personalAccessToken, url: configURL) - GitLabAccount(config).me { response in - switch response { - case .success: - handleGitRequestSuccess() - case .failure(let error): - handleGitRequestFailed(error) - } - } - default: - print("do nothing") - } - } - } - - private func handleGitRequestSuccess() { - let providerLink = provider.baseURL?.absoluteString ?? server - - self.gitAccounts.append( - SourceControlAccount( - id: "\(providerLink)_\(username.lowercased())", - name: username, - description: provider.name, - provider: provider, - serverURL: providerLink, - urlProtocol: .https, - sshKey: "", - isTokenValid: true - ) - ) - - keychain.set(personalAccessToken, forKey: "github_\(username)_enterprise") - dismiss() - } - - private func handleGitRequestFailed(_ error: Error) { - print("git auth failure: \(error)") - // Show alert if error encountered while requesting signin - switch error._code { - case -1009: - signinErrorDetail = error.localizedDescription - case 401: - signinErrorDetail = "Authentication Failed" - case 403: - signinErrorDetail = "API Access Forbidden" - default: - signinErrorDetail = "Unknown Error" - } - signinErrorAlertIsPresented.toggle() - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift deleted file mode 100644 index 92926889c6..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// AccountSettingsView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/4/23. -// - -import SwiftUI - -struct AccountsSettingsView: View { - @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) - var gitAccounts - - @State private var addAccountSheetPresented: Bool = false - @State private var selectedProvider: SourceControlAccount.Provider? - - var body: some View { - SettingsForm { - Section { - if $gitAccounts.isEmpty { - Text("No accounts") - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, alignment: .center) - } else { - ForEach($gitAccounts, id: \.self) { $account in - AccountsSettingsAccountLink($account) - } - } - } footer: { - HStack { - Spacer() - Button("Add Account...") { addAccountSheetPresented.toggle() } - .sheet(isPresented: $addAccountSheetPresented, content: { - AccountSelectionView(selectedProvider: $selectedProvider) - }) - .sheet(item: $selectedProvider, content: { provider in - switch provider { - case .github, .githubEnterprise, .gitlab, .gitlabSelfHosted: - AccountsSettingsSigninView(provider, addAccountSheetPresented: $addAccountSheetPresented) - default: - implementationNeeded - } - }) - } - .padding(.top, 10) - } - } - } - - private var implementationNeeded: some View { - VStack(spacing: 20) { - Text("This git client is currently not supported.") - HStack { - Button("Close") { - addAccountSheetPresented.toggle() - selectedProvider = nil - } - .buttonStyle(.borderedProminent) - } - .frame(maxWidth: .infinity, alignment: .trailing) - } - .padding(20) - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift deleted file mode 100644 index d32d50ea41..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// CreateSSHKeyView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/28/23. -// - -import SwiftUI - -struct CreateSSHKeyView: View { - @Environment(\.dismiss) - private var dismiss - - enum KeyType: String, CaseIterable { - case ed25519 = "ED25519" - case ecdsa = "ECDSA" - case rsa = "RSA" - case dsa = "DSA" - } - - @State var selectedKeyType: KeyType = .ed25519 - @State var passphrase: String = "" - @State var confirmPassphrase: String = "" - - var body: some View { - VStack { - Form { - Section("Create SSH key") { - Picker("Key Type", selection: $selectedKeyType) { - Text(KeyType.ed25519.rawValue) - .tag(KeyType.ed25519) - Text(KeyType.ecdsa.rawValue) - .tag(KeyType.ecdsa) - Divider() - Group { - Text(KeyType.rsa.rawValue) + Text(" (less secure)").foregroundColor(.secondary) - } - .tag(KeyType.rsa) - Group { - Text(KeyType.dsa.rawValue) + Text(" (less secure)").foregroundColor(.secondary) - } - .tag(KeyType.dsa) - } - SecureField("Passphrase", text: $passphrase) - if !passphrase.isEmpty { - SecureField("Confirm Passphrase", text: $confirmPassphrase) - } - } - } - .formStyle(.grouped) - .fixedSize() - .scrollDisabled(true) - HStack { - Spacer() - Button("Cancel") { - dismiss() - } - Button("Create") { - // create the ssh key - dismiss() - } - .buttonStyle(.borderedProminent) - } - .padding(.horizontal, 20) - .padding(.bottom, 20) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift deleted file mode 100644 index b0220a2fc9..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// AccountsPreferences.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import Foundation - -extension SettingsData { - - /// The global settings for text editing - struct AccountsSettings: Codable, Hashable, SearchableSettingsPage { - /// An integer indicating how many spaces a `tab` will generate - var sourceControlAccounts: GitAccounts = .init() - - /// The search keys - var searchKeys: [String] { - [ - "Accounts", - "Delete Account...", - "Add Account..." - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.sourceControlAccounts = try container.decodeIfPresent( - GitAccounts.self, - forKey: .sourceControlAccounts - ) ?? .init() - } - } - - struct GitAccounts: Codable, Hashable { - /// This id will store the account name as the identifiable - var gitAccounts: [SourceControlAccount] = [] - - var sshKey: String = "" - /// Default initializer - init() {} - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.gitAccounts = try container.decodeIfPresent([SourceControlAccount].self, forKey: .gitAccounts) ?? [] - self.sshKey = try container.decodeIfPresent(String.self, forKey: .sshKey) ?? "" - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift deleted file mode 100644 index fdc2b4fd6b..0000000000 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// SourceControlAccount.swift -// CodeEdit -// -// Created by Austin Condiff on 4/6/23. -// - -import SwiftUI - -struct SourceControlAccount: Codable, Identifiable, Hashable { - - var id: String - var name: String - var description: String - var provider: Provider - var serverURL: String - // TODO: Should we use an enum instead of a boolean here: - // If true we use the HTTP protocol else if false we use SSH - var urlProtocol: URLProtocol - var sshKey: String - var isTokenValid: Bool - - enum URLProtocol: String, Codable, CaseIterable { - case https = "HTTPS" - case ssh = "SSH" - } - - enum Provider: Codable, CaseIterable, Identifiable { - case bitbucketCloud - case bitbucketServer - case github - case githubEnterprise - case gitlab - case gitlabSelfHosted - - var id: String { - switch self { - case .bitbucketCloud: - return "bitbucketCloud" - case .bitbucketServer: - return "bitbucketServer" - case .github: - return "github" - case .githubEnterprise: - return "githubEnterprise" - case .gitlab: - return "gitlab" - case .gitlabSelfHosted: - return "gitlabSelfHosted" - } - } - - var name: String { - switch self { - case .bitbucketCloud: - return "BitBucket Cloud" - case .bitbucketServer: - return "BitBucket Server" - case .github: - return "GitHub" - case .githubEnterprise: - return "GitHub Enterprise" - case .gitlab: - return "GitLab" - case .gitlabSelfHosted: - return "GitLab Self-hosted" - } - } - - var baseURL: URL? { - switch self { - case .bitbucketCloud: - return URL(string: "https://www.bitbucket.com/")! - case .bitbucketServer: - return nil - case .github: - return URL(string: "https://www.github.com/")! - case .githubEnterprise: - return nil - case .gitlab: - return URL(string: "https://www.gitlab.com/")! - case .gitlabSelfHosted: - return nil - } - } - - var apiURL: URL? { - switch self { - case .bitbucketCloud: - return URL(string: "https://api.bitbucket.org/2.0/")! - case .bitbucketServer: - return nil - case .github: - return URL(string: "https://api.github.com/")! - case .githubEnterprise: - return nil - case .gitlab: - return URL(string: "https://gitlab.com/api/v4/")! - case .gitlabSelfHosted: - return nil - } - } - - var iconName: String { - switch self { - case .bitbucketCloud: - return "BitBucketIcon" - case .bitbucketServer: - return "BitBucketIcon" - case .github: - return "GitHubIcon" - case .githubEnterprise: - return "GitHubIcon" - case .gitlab: - return "GitLabIcon" - case .gitlabSelfHosted: - return "GitLabIcon" - } - } - - var authHelpURL: URL { - switch self { - case .bitbucketCloud: - return URL(string: "https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/")! - case .bitbucketServer: - return URL(string: - "https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html")! - case .github: - return URL(string: "https://github.com/settings/tokens/new")! - case .githubEnterprise: - return URL(string: "https://github.com/settings/tokens/new")! - case .gitlab: - return URL(string: "https://gitlab.com/-/profile/personal_access_tokens")! - case .gitlabSelfHosted: - return URL(string: "https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html")! - } - } - - var authType: AuthType { - switch self { - case .bitbucketCloud: - return .password - case .bitbucketServer: - return .token - case .github: - return .token - case .githubEnterprise: - return .token - case .gitlab: - return .token - case .gitlabSelfHosted: - return .token - } - } - } - - enum AuthType { - case token - case password - } -} diff --git a/CodeEdit/Features/Settings/Pages/GeneralSettings/GeneralSettingsView.swift b/CodeEdit/Features/Settings/Pages/GeneralSettings/GeneralSettingsView.swift deleted file mode 100644 index c7fefb5db5..0000000000 --- a/CodeEdit/Features/Settings/Pages/GeneralSettings/GeneralSettingsView.swift +++ /dev/null @@ -1,384 +0,0 @@ -// -// GeneralSettingsView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/1/23. -// - -import SwiftUI - -/// A view that implements the `General` settings page -struct GeneralSettingsView: View { - private let inputWidth: Double = 160 - private let textEditorWidth: Double = 220 - private let textEditorHeight: Double = 30 - - @EnvironmentObject var updater: SoftwareUpdater - @FocusState private var focusedField: UUID? - - @AppSettings(\.general) - var settings - - @State private var openInCodeEdit: Bool = true - - init() { - guard let defaults = UserDefaults.init( - suiteName: "app.codeedit.CodeEdit.shared" - ) else { - print("Failed to get/init shared defaults") - return - } - - self.openInCodeEdit = defaults.bool(forKey: "enableOpenInCE") - } - - var body: some View { - SettingsForm { - Section { - appearance - fileIconStyle - tabBarStyle - showEditorPathBar - dimEditorsWithoutFocus - navigatorTabBarPosition - inspectorTabBarPosition - } - Section { - showIssues - showLiveIssues - } - Section { - autoSave - revealFileOnFocusChangeToggle - reopenBehavior - afterWindowsCloseBehaviour - fileExtensions - } - Section { - projectNavigatorSize - findNavigatorDetail - issueNavigatorDetail - } - Section { - openInCodeEditToggle - shellCommand - dialogWarnings - - } - Section { - updateChecker - autoUpdateToggle - // TODO: Uncomment when production build is released. - // prereleaseToggle - } - } - } -} - -/// The extension of the view with all the preferences -private extension GeneralSettingsView { - var appearance: some View { - Picker("Appearance", selection: $settings.appAppearance) { - Text("System") - .tag(SettingsData.Appearances.system) - Divider() - Text("Light") - .tag(SettingsData.Appearances.light) - Text("Dark") - .tag(SettingsData.Appearances.dark) - } - .onChange(of: settings.appAppearance) { tag in - tag.applyAppearance() - } - } - - // TODO: Implement reflecting Show Issues preference and remove disabled modifier - var showIssues: some View { - Picker("Show Issues", selection: $settings.showIssues) { - Text("Show Inline") - .tag(SettingsData.Issues.inline) - Text("Show Minimized") - .tag(SettingsData.Issues.minimized) - } - } - - var showLiveIssues: some View { - Toggle("Show Live Issues", isOn: $settings.showLiveIssues) - } - - var showEditorPathBar: some View { - Toggle("Show Path Bar", isOn: $settings.showEditorPathBar) - } - - var dimEditorsWithoutFocus: some View { - Toggle("Dim editors without focus", isOn: $settings.dimEditorsWithoutFocus) - } - - var fileExtensions: some View { - Group { - Picker("File Extensions", selection: $settings.fileExtensionsVisibility) { - Text("Hide all") - .tag(SettingsData.FileExtensionsVisibility.hideAll) - Text("Show all") - .tag(SettingsData.FileExtensionsVisibility.showAll) - Divider() - Text("Show only") - .tag(SettingsData.FileExtensionsVisibility.showOnly) - Text("Hide only") - .tag(SettingsData.FileExtensionsVisibility.hideOnly) - } - if case .showOnly = settings.fileExtensionsVisibility { - TextField("", text: $settings.shownFileExtensions.string, axis: .vertical) - .labelsHidden() - .lineLimit(1...3) - } - if case .hideOnly = settings.fileExtensionsVisibility { - TextField("", text: $settings.hiddenFileExtensions.string, axis: .vertical) - .labelsHidden() - .lineLimit(1...3) - } - } - } - - var fileIconStyle: some View { - Picker("File Icon Style", selection: $settings.fileIconStyle) { - Text("Color") - .tag(SettingsData.FileIconStyle.color) - Text("Monochrome") - .tag(SettingsData.FileIconStyle.monochrome) - } - .pickerStyle(.radioGroup) - } - - var tabBarStyle: some View { - Picker("Tab Bar Style", selection: $settings.tabBarStyle) { - Text("Xcode") - .tag(SettingsData.TabBarStyle.xcode) - Text("Native") - .tag(SettingsData.TabBarStyle.native) - } - .pickerStyle(.radioGroup) - } - - var navigatorTabBarPosition: some View { - Picker("Navigator Tab Bar Position", selection: $settings.navigatorTabBarPosition) { - Text("Top") - .tag(SettingsData.SidebarTabBarPosition.top) - Text("Side") - .tag(SettingsData.SidebarTabBarPosition.side) - } - .pickerStyle(.radioGroup) - } - - var inspectorTabBarPosition: some View { - Picker("Inspector Tab Bar Position", selection: $settings.inspectorTabBarPosition) { - Text("Top") - .tag(SettingsData.SidebarTabBarPosition.top) - Text("Side") - .tag(SettingsData.SidebarTabBarPosition.side) - } - .pickerStyle(.radioGroup) - } - - var reopenBehavior: some View { - Picker("Reopen Behavior", selection: $settings.reopenBehavior) { - Text("Welcome Screen") - .tag(SettingsData.ReopenBehavior.welcome) - Divider() - Text("Open Panel") - .tag(SettingsData.ReopenBehavior.openPanel) - Text("New Document") - .tag(SettingsData.ReopenBehavior.newDocument) - } - } - - var afterWindowsCloseBehaviour: some View { - Picker( - "After the last window is closed", - selection: $settings.reopenWindowAfterClose - ) { - Text("Do nothing") - .tag(SettingsData.ReopenWindowBehavior.doNothing) - Divider() - Text("Show Welcome Window") - .tag(SettingsData.ReopenWindowBehavior.showWelcomeWindow) - Text("Quit") - .tag(SettingsData.ReopenWindowBehavior.quit) - } - } - - var projectNavigatorSize: some View { - Picker("Project Navigator Size", selection: $settings.projectNavigatorSize) { - Text("Small") - .tag(SettingsData.ProjectNavigatorSize.small) - Text("Medium") - .tag(SettingsData.ProjectNavigatorSize.medium) - Text("Large") - .tag(SettingsData.ProjectNavigatorSize.large) - } - } - - var findNavigatorDetail: some View { - Picker("Find Navigator Detail", selection: $settings.findNavigatorDetail) { - ForEach(SettingsData.NavigatorDetail.allCases, id: \.self) { tag in - Text(tag.label).tag(tag) - } - } - } - - // TODO: Implement reflecting Issue Navigator Detail preference and remove disabled modifier - var issueNavigatorDetail: some View { - Picker("Issue Navigator Detail", selection: $settings.issueNavigatorDetail) { - ForEach(SettingsData.NavigatorDetail.allCases, id: \.self) { tag in - Text(tag.label).tag(tag) - } - } - .disabled(true) - } - - // TODO: Implement reset for Don't Ask Me warnings Button and remove disabled modifier - var dialogWarnings: some View { - LabeledContent("Dialog Warnings") { - Button(action: { - }, label: { - Text("Reset \"Don't Ask Me\" Warnings") - }) - .buttonStyle(.bordered) - } - .disabled(true) - } - - var shellCommand: some View { - LabeledContent("'codeedit' Shell Command") { - Button(action: installShellCommand, label: { - Text("Install") - }) - .disabled(true) - .buttonStyle(.bordered) - } - } - - func installShellCommand() { - do { - let url = Bundle.main.url(forResource: "codeedit", withExtension: nil, subdirectory: "Resources") - let destination = "/usr/local/bin/codeedit" - - if FileManager.default.fileExists(atPath: destination) { - try FileManager.default.removeItem(atPath: destination) - } - - guard let shellUrl = url?.path else { - print("Failed to get URL to shell command") - return - } - - NSWorkspace.shared.requestAuthorization(to: .createSymbolicLink) { auth, error in - guard let auth, error == nil else { - fallbackShellInstallation(commandPath: shellUrl, destinationPath: destination) - return - } - - do { - try FileManager(authorization: auth).createSymbolicLink( - atPath: destination, withDestinationPath: shellUrl - ) - } catch { - fallbackShellInstallation(commandPath: shellUrl, destinationPath: destination) - } - } - } catch { - print(error) - } - } - - var updateChecker: some View { - Section { - LabeledContent { - Button("Check Now") { - updater.checkForUpdates() - } - } label: { - Text("Check for updates") - Text("Last checked: \(lastUpdatedString)") - - } - } - } - - var autoUpdateToggle: some View { - Toggle("Automatically check for app updates", isOn: $updater.automaticallyChecksForUpdates) - } - - var prereleaseToggle: some View { - Toggle("Include pre-release versions", isOn: $updater.includePrereleaseVersions) - } - - var autoSave: some View { - Toggle("Automatically save changes to disk", isOn: $settings.isAutoSaveOn) - } - - // MARK: - Preference Views - - private var lastUpdatedString: String { - if let lastUpdatedDate = updater.lastUpdateCheckDate { - return Self.formatter.string(from: lastUpdatedDate) - } else { - return "Never" - } - } - - private static func configure(_ subject: Subject, configuration: (inout Subject) -> Void) -> Subject { - var copy = subject - configuration(©) - return copy - } - - func fallbackShellInstallation(commandPath: String, destinationPath: String) { - let cmd = [ - "osascript", - "-e", - "\"do shell script \\\"mkdir -p /usr/local/bin && ln -sf \'\(commandPath)\' \'\(destinationPath)\'\\\"\"", - "with administrator privileges" - ] - - let cmdStr = cmd.joined(separator: " ") - - let task = Process() - let pipe = Pipe() - - task.standardOutput = pipe - task.standardError = pipe - task.arguments = ["-c", cmdStr] - task.executableURL = URL(fileURLWithPath: "/bin/zsh") - task.standardInput = nil - - do { - try task.run() - } catch { - print(error) - } - } - - var openInCodeEditToggle: some View { - Toggle("Show “Open With CodeEdit” option in Finder", isOn: $openInCodeEdit) - .onChange(of: openInCodeEdit) { newValue in - guard let defaults = UserDefaults.init( - suiteName: "app.codeedit.CodeEdit.shared" - ) else { - print("Failed to get/init shared defaults") - return - } - - defaults.set(newValue, forKey: "enableOpenInCE") - } - } - - var revealFileOnFocusChangeToggle: some View { - Toggle("Automatically reveal in project navigator", isOn: $settings.revealFileOnFocusChange) - } - - private static let formatter = configure(DateFormatter()) { - $0.dateStyle = .medium - $0.timeStyle = .medium - } -} diff --git a/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift b/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift deleted file mode 100644 index 9da6e504df..0000000000 --- a/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift +++ /dev/null @@ -1,333 +0,0 @@ -// -// GeneralSettings.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import SwiftUI - -extension SettingsData { - - /// The general global setting - struct GeneralSettings: Codable, Hashable, SearchableSettingsPage { - - /// The appearance of the app - var appAppearance: Appearances = .system - - /// The show issues behavior of the app - var showIssues: Issues = .inline - - /// The show live issues behavior of the app - var showLiveIssues: Bool = true - - /// The search keys - var searchKeys: [String] { - [ - "Appearance", - "File Icon Style", - "Tab Bar Style", - "Show Path Bar", - "Dim editors without focus", - "Navigator Tab Bar Position", - "Inspector Tab Bar Position", - "Show Issues", - "Show Live Issues", - "Automatically save change to disk", - "Automatically reveal in project navigator", - "Reopen Behavior", - "After the last window is closed", - "File Extensions", - "Project Navigator Size", - "Find Navigator Detail", - "Issue Navigator Detail", - "Show “Open With CodeEdit“ option in Finder", - "'codeedit' Shell command", - "Dialog Warnings", - "Check for updates", - "Automatically check for app updates", - "Include pre-release versions" - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// Show editor path bar - var showEditorPathBar: Bool = true - - /// Dims editors without focus - var dimEditorsWithoutFocus: Bool = false - - /// The show file extensions behavior of the app - var fileExtensionsVisibility: FileExtensionsVisibility = .showAll - - /// The file extensions collection to display - var shownFileExtensions: FileExtensions = .default - - /// The file extensions collection to hide - var hiddenFileExtensions: FileExtensions = .default - - /// The style for file icons - var fileIconStyle: FileIconStyle = .color - - /// Choose between native-styled tab bar and Xcode-liked tab bar. - var tabBarStyle: TabBarStyle = .xcode - - /// The position for the navigator sidebar tab bar - var navigatorTabBarPosition: SidebarTabBarPosition = .top - - /// The position for the inspector sidebar tab bar - var inspectorTabBarPosition: SidebarTabBarPosition = .top - - /// The reopen behavior of the app - var reopenBehavior: ReopenBehavior = .welcome - - /// Decides what the app does after a workspace is closed - var reopenWindowAfterClose: ReopenWindowBehavior = .doNothing - - /// The size of the project navigator - var projectNavigatorSize: ProjectNavigatorSize = .medium - - /// The Find Navigator Detail line limit - var findNavigatorDetail: NavigatorDetail = .upTo3 - - /// The Issue Navigator Detail line limit - var issueNavigatorDetail: NavigatorDetail = .upTo3 - - /// The reveal file in navigator when focus changes behavior of the app. - var revealFileOnFocusChange: Bool = false - - /// Auto save behavior toggle - var isAutoSaveOn: Bool = true - - /// Default initializer - init() {} - - // swiftlint:disable function_body_length - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.appAppearance = try container.decodeIfPresent( - Appearances.self, - forKey: .appAppearance - ) ?? .system - self.showIssues = try container.decodeIfPresent( - Issues.self, - forKey: .showIssues - ) ?? .inline - self.showLiveIssues = try container.decodeIfPresent( - Bool.self, - forKey: .showLiveIssues - ) ?? true - self.showEditorPathBar = try container.decodeIfPresent( - Bool.self, - forKey: .showEditorPathBar - ) ?? true - self.dimEditorsWithoutFocus = try container.decodeIfPresent( - Bool.self, - forKey: .dimEditorsWithoutFocus - ) ?? false - self.fileExtensionsVisibility = try container.decodeIfPresent( - FileExtensionsVisibility.self, - forKey: .fileExtensionsVisibility - ) ?? .showAll - self.shownFileExtensions = try container.decodeIfPresent( - FileExtensions.self, - forKey: .shownFileExtensions - ) ?? .default - self.hiddenFileExtensions = try container.decodeIfPresent( - FileExtensions.self, - forKey: .hiddenFileExtensions - ) ?? .default - self.fileIconStyle = try container.decodeIfPresent( - FileIconStyle.self, - forKey: .fileIconStyle - ) ?? .color - self.tabBarStyle = try container.decodeIfPresent( - TabBarStyle.self, - forKey: .tabBarStyle - ) ?? .xcode - self.navigatorTabBarPosition = try container.decodeIfPresent( - SidebarTabBarPosition.self, - forKey: .navigatorTabBarPosition - ) ?? .top - self.inspectorTabBarPosition = try container.decodeIfPresent( - SidebarTabBarPosition.self, - forKey: .inspectorTabBarPosition - ) ?? .top - self.reopenBehavior = try container.decodeIfPresent( - ReopenBehavior.self, - forKey: .reopenBehavior - ) ?? .welcome - self.reopenWindowAfterClose = try container.decodeIfPresent( - ReopenWindowBehavior.self, - forKey: .reopenWindowAfterClose - ) ?? .doNothing - self.projectNavigatorSize = try container.decodeIfPresent( - ProjectNavigatorSize.self, - forKey: .projectNavigatorSize - ) ?? .medium - self.findNavigatorDetail = try container.decodeIfPresent( - NavigatorDetail.self, - forKey: .findNavigatorDetail - ) ?? .upTo3 - self.issueNavigatorDetail = try container.decodeIfPresent( - NavigatorDetail.self, - forKey: .issueNavigatorDetail - ) ?? .upTo3 - self.revealFileOnFocusChange = try container.decodeIfPresent( - Bool.self, - forKey: .revealFileOnFocusChange - ) ?? false - self.isAutoSaveOn = try container.decodeIfPresent( - Bool.self, - forKey: .isAutoSaveOn - ) ?? true - } - // swiftlint:enable function_body_length - } - - /// The appearance of the app - /// - **system**: uses the system appearance - /// - **dark**: always uses dark appearance - /// - **light**: always uses light appearance - enum Appearances: String, Codable { - case system - case light - case dark - - /// Applies the selected appearance - func applyAppearance() { - switch self { - case .system: - NSApp.appearance = nil - - case .dark: - NSApp.appearance = .init(named: .darkAqua) - - case .light: - NSApp.appearance = .init(named: .aqua) - } - } - } - - /// The style for issues display - /// - **inline**: Issues show inline - /// - **minimized** Issues show minimized - enum Issues: String, Codable { - case inline - case minimized - } - - /// The style for file extensions visibility - /// - **hideAll**: File extensions are hidden - /// - **showAll** File extensions are visible - /// - **showOnly** Specific file extensions are visible - /// - **hideOnly** Specific file extensions are hidden - enum FileExtensionsVisibility: Codable, Hashable { - case hideAll - case showAll - case showOnly - case hideOnly - } - - /// The collection of file extensions used by - /// ``FileExtensionsVisibility/showOnly`` or ``FileExtensionsVisibility/hideOnly`` preference - struct FileExtensions: Codable, Hashable { - var extensions: [String] - - var string: String { - get { - extensions.joined(separator: ", ") - } - set { - extensions = newValue - .components(separatedBy: ",") - .map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) }) - .filter({ !$0.isEmpty || string.count < newValue.count }) - } - } - - static var `default` = FileExtensions(extensions: [ - "c", "cc", "cpp", "h", "hpp", "m", "mm", "gif", - "icns", "jpeg", "jpg", "png", "tiff", "swift" - ]) - } - /// The style for file icons - /// - **color**: File icons appear in their default colors - /// - **monochrome**: File icons appear monochromatic - enum FileIconStyle: String, Codable { - case color - case monochrome - } - - /// The style for tab bar - /// - **native**: Native-styled tab bar (like Finder) - /// - **xcode**: Xcode-liked tab bar - enum TabBarStyle: String, Codable { - case native - case xcode - } - - /// The position for a sidebar tab bar - /// - **top**: Tab bar is positioned at the top of the sidebar - /// - **side**: Tab bar is positioned to the side of the sidebar - enum SidebarTabBarPosition: String, Codable { - case top, side - } - - /// The reopen behavior of the app - /// - **welcome**: On restart the app will show the welcome screen - /// - **openPanel**: On restart the app will show an open panel - /// - **newDocument**: On restart a new empty document will be created - enum ReopenBehavior: String, Codable { - case welcome - case openPanel - case newDocument - } - - enum ReopenWindowBehavior: String, Codable { - case showWelcomeWindow - case doNothing - case quit - } - - enum ProjectNavigatorSize: String, Codable { - case small - case medium - case large - - /// Returns the row height depending on the `projectNavigatorSize` in `Settings`. - /// - /// * `small`: 20 - /// * `medium`: 22 - /// * `large`: 24 - var rowHeight: Double { - switch self { - case .small: return 20 - case .medium: return 22 - case .large: return 24 - } - } - } - - /// The Navigation Detail behavior of the app - /// - Use **rawValue** to set lineLimit - enum NavigatorDetail: Int, Codable, CaseIterable { - case upTo1 = 1 - case upTo2 = 2 - case upTo3 = 3 - case upTo4 = 4 - case upTo5 = 5 - case upTo10 = 10 - case upTo30 = 30 - - var label: String { - switch self { - case .upTo1: - return "One Line" - default: - return "Up to \(self.rawValue) lines" - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/GeneralSettings/View+actionBar.swift b/CodeEdit/Features/Settings/Pages/GeneralSettings/View+actionBar.swift deleted file mode 100644 index 2da0f779af..0000000000 --- a/CodeEdit/Features/Settings/Pages/GeneralSettings/View+actionBar.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// View+actionBar.swift -// CodeEdit -// -// Created by Austin Condiff on 9/18/23. -// - -import SwiftUI - -extension View { - func actionBar(@ViewBuilder content: () -> Content) -> some View { - self - .padding(.bottom, 24) - .overlay(alignment: .bottom) { - VStack(spacing: -1) { - Divider() - HStack(spacing: 0) { - content() - .buttonStyle(.icon(font: Font.system(size: 11, weight: .medium), size: 24)) - } - .frame(height: 16) - .padding(.vertical, 4) - .frame(maxWidth: .infinity, alignment: .leading) - } - .frame(height: 24) - .background(.separator) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift b/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift deleted file mode 100644 index 429d455528..0000000000 --- a/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// KeybindingsPreferences.swift -// CodeEditModules/Settings -// -// Created by Alex on 18.05.2022. -// - -import Foundation - -extension SettingsData { - - /// The global settings for text editing - struct KeybindingsSettings: Codable, Hashable { - - /// An integer indicating how many spaces a `tab` will generate - var keybindings: [String: KeyboardShortcutWrapper] = .init() - - /// Default initializer - init() { - self.keybindings = KeybindingManager.shared.keyboardShortcuts - } - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.keybindings = try container.decodeIfPresent( - [String: KeyboardShortcutWrapper].self, - forKey: .keybindings - ) ?? .init() - appendNew() - - let mgr = CommandManager.shared - let wrap = CommandClosureWrapper.init(closure: { - print("testing closure") - }) - mgr.addCommand( - name: "Send test to console", - title: "Send test to console", id: "codeedit.test", command: wrap - ) - mgr.executeCommand("test") - } - - /// Adds new keybindings if they were added to default_keybindings.json. - /// To ensure users will get new keybindings with new app version releases - private mutating func appendNew() { - let newKeybindings = KeybindingManager.shared - .keyboardShortcuts.filter { !keybindings.keys.contains($0.key) } - for keybinding in newKeybindings { - self.keybindings[keybinding.key] = KeybindingManager.shared.named(with: keybinding.key) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift b/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift deleted file mode 100644 index d797cc1f6b..0000000000 --- a/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// LocationSettingsView.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 02/04/23. -// - -import SwiftUI - -/// A view that implements the `Locations` settings section -struct LocationsSettingsView: View { - var body: some View { - SettingsForm { - Section { - applicationSupportLocation - settingsLocation - themesLocation - extensionsLocation - } - } - } -} - -private extension LocationsSettingsView { - @ViewBuilder private var applicationSupportLocation: some View { - ExternalLink(destination: Settings.shared.baseURL) { - Text("Application Support") - Text(Settings.shared.baseURL.path) - .font(.footnote) - .foregroundColor(.secondary) - } - } - - private var settingsLocation: some View { - ExternalLink(destination: ThemeModel.shared.settingsURL) { - Text("Settings") - Text(ThemeModel.shared.settingsURL.path) - .font(.footnote) - .foregroundColor(.secondary) - } - } - - private var themesLocation: some View { - ExternalLink(destination: ThemeModel.shared.themesURL) { - Text("Themes") - Text(ThemeModel.shared.themesURL.path) - .font(.footnote) - .foregroundColor(.secondary) - } - } - - private var extensionsLocation: some View { - ExternalLink(destination: ThemeModel.shared.extensionsURL) { - Text("Extensions") - Text(ThemeModel.shared.extensionsURL.path()) - .font(.footnote) - .foregroundColor(.secondary) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift b/CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift deleted file mode 100644 index 9481b1f019..0000000000 --- a/CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// LocationsSettings.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 24/06/23. -// - -import Foundation - -extension SettingsData { - - struct LocationsSettings: SearchableSettingsPage { - - /// The search keys - var searchKeys: [String] { - [ - "Settings Location", - "Themes Location", - "Extensions Location" - ] - .map { NSLocalizedString($0, comment: "") } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift b/CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift deleted file mode 100644 index fa95f98d37..0000000000 --- a/CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// NavigationSettings.swift -// CodeEdit -// -// Created by Austin Condiff on 3/4/24. -// - -import Foundation - -extension SettingsData { - - /// The global settings for the terminal emulator - struct NavigationSettings: Codable, Hashable, SearchableSettingsPage { - - /// The search keys - var searchKeys: [String] { - [ - "Navigation Style", - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// Navigation style used - var navigationStyle: NavigationStyle = .openInTabs - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.navigationStyle = try container.decodeIfPresent( - NavigationStyle.self, forKey: .navigationStyle - ) ?? .openInTabs - } - } - - enum NavigationStyle: String, Codable, Hashable { - case openInTabs - case openInPlace - } -} diff --git a/CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift b/CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift deleted file mode 100644 index 552eb4a075..0000000000 --- a/CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// NavigationSettingsView.swift -// CodeEdit -// -// Created by Austin Condiff on 3/4/24. -// - -import SwiftUI - -struct NavigationSettingsView: View { - @AppSettings(\.navigation) - var settings - - var body: some View { - SettingsForm { - Section { - navigationStyle - } - } - } -} - -private extension NavigationSettingsView { - private var navigationStyle: some View { - Picker("Navigation Style", selection: $settings.navigationStyle) { - Text("Open in Tabs") - .tag(SettingsData.NavigationStyle.openInTabs) - Text("Open in Place") - .tag(SettingsData.NavigationStyle.openInPlace) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SearchSettings/IgnorePatternListItemView.swift b/CodeEdit/Features/Settings/Pages/SearchSettings/IgnorePatternListItemView.swift deleted file mode 100644 index 0ba3f4ffdc..0000000000 --- a/CodeEdit/Features/Settings/Pages/SearchSettings/IgnorePatternListItemView.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// IgnorePatternListItemView.swift -// CodeEdit -// -// Created by Esteban on 2/2/24. -// - -import SwiftUI - -struct IgnorePatternListItem: View { - @Binding var pattern: GlobPattern - @Binding var selectedPattern: GlobPattern? - var addPattern: () -> Void - var removePattern: (GlobPattern) -> Void - var focusedField: FocusState.Binding - var isLast: Bool - - @State var value: String - - @FocusState private var isFocused: Bool - - init( - pattern: Binding, - selectedPattern: Binding, - addPattern: @escaping () -> Void, - removePattern: @escaping (GlobPattern) -> Void, - focusedField: FocusState.Binding, - isLast: Bool - ) { - self._pattern = pattern - self._selectedPattern = selectedPattern - self.addPattern = addPattern - self.removePattern = removePattern - self.focusedField = focusedField - self.isLast = isLast - self._value = State(initialValue: pattern.wrappedValue.value) - } - - var body: some View { - TextField("", text: $value) - .focused(focusedField, equals: pattern.id.uuidString) - .focused($isFocused) - .disableAutocorrection(true) - .autocorrectionDisabled() - .labelsHidden() - .onSubmit { - if !value.isEmpty && isLast { - addPattern() - } - } - .onChange(of: isFocused) { newIsFocused in - if newIsFocused { - if selectedPattern != pattern { - selectedPattern = pattern - } - } else { - if value.isEmpty { - removePattern(pattern) - } else { - pattern.value = value - } - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettings.swift b/CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettings.swift deleted file mode 100644 index 6510d418c0..0000000000 --- a/CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettings.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SearchSettings.swift -// CodeEdit -// -// Created by Esteban on 12/10/23. -// - -import Foundation - -extension SettingsData { - struct SearchSettings: Codable, Hashable, SearchableSettingsPage { - - /// The search keys - var searchKeys: [String] { - [ - "Ignore Glob Patterns", - "Ignore Patterns" - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// List of Glob Patterns that determine which files or directories to ignore - var ignoreGlobPatterns: [GlobPattern] = .init() - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.ignoreGlobPatterns = try container.decodeIfPresent( - [GlobPattern].self, - forKey: .ignoreGlobPatterns - ) ?? [] - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettingsModel.swift b/CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettingsModel.swift deleted file mode 100644 index 9bd6cd8311..0000000000 --- a/CodeEdit/Features/Settings/Pages/SearchSettings/Models/SearchSettingsModel.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// SearchSettingsModel.swift -// CodeEdit -// -// Created by Esteban on 12/10/23. -// - -import SwiftUI - -struct GlobPattern: Identifiable, Hashable, Decodable, Encodable { - /// Ephimeral UUID used to track its representation in the UI - var id = UUID() - - /// The Glob Pattern to render - var value: String -} - -/// The Search Settings View Model. Accessible via the singleton "``SearchSettings/shared``". -/// -/// **Usage:** -/// ```swift -/// @StateObject -/// private var searchSettigs: SearchSettingsModel = .shared -/// ``` -final class SearchSettingsModel: ObservableObject { - /// Reads settings file for Search Settings and updates the values in this model - /// correspondingly - private init() { - let value = Settings[\.search].ignoreGlobPatterns - self.ignoreGlobPatterns = value - } - - static let shared: SearchSettingsModel = .init() - - /// Default instance of the `FileManager` - private let filemanager = FileManager.default - - /// The base folder url `~/Library/Application Support/CodeEdit/` - private var baseURL: URL { - filemanager.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support/CodeEdit") - } - - /// The URL of the `search` folder - internal var searchURL: URL { - baseURL.appendingPathComponent("search", isDirectory: true) - } - - /// The URL of the `Extensions` folder - internal var extensionsURL: URL { - baseURL.appendingPathComponent("Extensions", isDirectory: true) - } - - /// The URL of the `settings.json` file - internal var settingsURL: URL { - baseURL.appendingPathComponent("settings.json", isDirectory: true) - } - - /// Stores the new values from the Search Settings Model into the settings.json whenever - /// `ignoreGlobPatterns` is updated - @Published var ignoreGlobPatterns: [GlobPattern] { - didSet { - DispatchQueue.main.async { - Settings[\.search].ignoreGlobPatterns = self.ignoreGlobPatterns - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsIgnoreGlobPatternItemView.swift b/CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsIgnoreGlobPatternItemView.swift deleted file mode 100644 index b29c33c361..0000000000 --- a/CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsIgnoreGlobPatternItemView.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// SearchSettingsIgnoreGlobPatternItemView.swift -// CodeEdit -// -// Created by Esteban on 12/10/23. -// - -import SwiftUI - -struct SearchSettingsIgnoreGlobPatternItemView: View { - @Binding var globPattern: String - - var body: some View { - Text(globPattern) - } -} diff --git a/CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsView.swift b/CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsView.swift deleted file mode 100644 index cebc9f65c2..0000000000 --- a/CodeEdit/Features/Settings/Pages/SearchSettings/SearchSettingsView.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// SearchSettingsView.swift -// CodeEdit -// -// Created by Esteban on 12/10/23. -// - -import SwiftUI - -struct SearchSettingsView: View { - var body: some View { - SettingsForm { - Section { - ExcludedGlobPatternList() - } header: { - Text("Exclude") - Text( - "Add glob patterns to exclude matching files and folders from searches and open quickly. " + - "This will inherit glob patterns from the Exclude from Project setting." - ) - } - } - } -} - -struct ExcludedGlobPatternList: View { - @ObservedObject private var searchSettingsModel: SearchSettingsModel = .shared - - @FocusState private var focusedField: String? - - @State private var selection: GlobPattern? - - var body: some View { - List(selection: $selection) { - ForEach( - Array(searchSettingsModel.ignoreGlobPatterns.enumerated()), - id: \.element - ) { index, ignorePattern in - IgnorePatternListItem( - pattern: $searchSettingsModel.ignoreGlobPatterns[index], - selectedPattern: $selection, - addPattern: addPattern, - removePattern: removePattern, - focusedField: $focusedField, - isLast: searchSettingsModel.ignoreGlobPatterns.count == index+1 - ) - .onAppear { - if ignorePattern.value.isEmpty { - focusedField = ignorePattern.id.uuidString - } - } - } - .onMove { fromOffsets, toOffset in - searchSettingsModel.ignoreGlobPatterns.move(fromOffsets: fromOffsets, toOffset: toOffset) - } - } - .frame(minHeight: 96) - .contextMenu( - forSelectionType: GlobPattern.self, - menu: { selection in - if let pattern = selection.first { - Button("Edit") { - focusedField = pattern.id.uuidString - } - Button("Add") { - addPattern() - } - Divider() - Button("Remove") { - if !searchSettingsModel.ignoreGlobPatterns.isEmpty { - removePattern(pattern) - } - } - } - }, - primaryAction: { selection in - if let pattern = selection.first { - focusedField = pattern.id.uuidString - } - } - ) - .overlay { - if searchSettingsModel.ignoreGlobPatterns.isEmpty { - Text("No excluded glob patterns") - .foregroundStyle(Color(.secondaryLabelColor)) - } - } - .actionBar { - Button { - addPattern() - } label: { - Image(systemName: "plus") - } - Divider() - Button { - if let pattern = selection { - removePattern(pattern) - } - } label: { - Image(systemName: "minus") - } - .disabled(selection == nil) - } - .onDeleteCommand { - removePattern(selection) - } - } - - func addPattern() { - searchSettingsModel.ignoreGlobPatterns.append(GlobPattern(value: "")) - } - - func removePattern(_ pattern: GlobPattern?) { - let selectedIndex = searchSettingsModel.ignoreGlobPatterns.firstIndex { - $0 == selection - } - - let removeIndex = searchSettingsModel.ignoreGlobPatterns.firstIndex { - $0 == selection - } - - searchSettingsModel.ignoreGlobPatterns.removeAll { - pattern == $0 - } - - if selectedIndex == removeIndex && !searchSettingsModel.ignoreGlobPatterns.isEmpty && selectedIndex != nil { - selection = searchSettingsModel.ignoreGlobPatterns[ - selectedIndex == 0 ? 0 : (selectedIndex ?? 1) - 1 - ] - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/IgnoredFiles.swift b/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/IgnoredFiles.swift deleted file mode 100644 index 1895b91d15..0000000000 --- a/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/IgnoredFiles.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// IgnoredFiles.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import Foundation - -struct IgnoredFiles: Codable, Identifiable, Hashable { - var id: String - var name: String -} diff --git a/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift b/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift deleted file mode 100644 index 097825782a..0000000000 --- a/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// SourceControlPreferences.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import Foundation - -extension SettingsData { - /// The global settings for source control - struct SourceControlSettings: Codable, Hashable, SearchableSettingsPage { - - var searchKeys: [String] { - [ - "General", - "Enable source control", - "Refresh local status automatically", - "Fetch and refresh server status automatically", - "Add and remove files automatically", - "Select files to commit automatically", - "Show source control changes", - "Include upstream changes", - "Comparison view", - "Source control navigator", - "Default branch name", - "Git", - "Author Name", - "Author Email", - "Prefer to rebase when pulling", - "Show merge commits in per-file log" - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// The general source control settings - var general: SourceControlGeneral = .init() - - /// The source control git settings - var git: SourceControlGit = .init() - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.general = try container.decodeIfPresent(SourceControlGeneral.self, forKey: .general) ?? .init() - self.git = try container.decodeIfPresent(SourceControlGit.self, forKey: .git) ?? .init() - } - } - - struct SourceControlGeneral: Codable, Hashable { - /// Indicates whether or not the source control is active - var enableSourceControl: Bool = true - /// Indicates whether or not we should include the upstream changes - var refreshStatusLocally: Bool = false - /// Indicates whether or not we should include the upstream changes - var fetchRefreshServerStatus: Bool = false - /// Indicates whether or not we should include the upstream changes - var addRemoveAutomatically: Bool = false - /// Indicates whether or not we should include the upstream changes - var selectFilesToCommit: Bool = false - /// Indicates whether or not to show the source control changes - var showSourceControlChanges: Bool = true - /// Indicates whether or not we should include the upstream - var includeUpstreamChanges: Bool = false - /// Indicates whether or not we should open the reported feedback in the browser - var openFeedbackInBrowser: Bool = true - /// The selected value of the comparison view - var revisionComparisonLayout: RevisionComparisonLayout = .localLeft - /// The selected value of the control navigator - var controlNavigatorOrder: ControlNavigatorOrder = .sortByName - /// The name of the default branch - var defaultBranchName: String = "main" - /// Default initializer - init() {} - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.enableSourceControl = try container.decodeIfPresent(Bool.self, forKey: .enableSourceControl) ?? true - self.refreshStatusLocally = try container.decodeIfPresent(Bool.self, forKey: .refreshStatusLocally) ?? true - self.fetchRefreshServerStatus = try container.decodeIfPresent( - Bool.self, - forKey: .fetchRefreshServerStatus - ) ?? true - self.addRemoveAutomatically = try container.decodeIfPresent( - Bool.self, - forKey: .addRemoveAutomatically - ) ?? true - self.selectFilesToCommit = try container.decodeIfPresent(Bool.self, forKey: .selectFilesToCommit) ?? true - self.showSourceControlChanges = try container.decodeIfPresent( - Bool.self, - forKey: .showSourceControlChanges - ) ?? true - self.includeUpstreamChanges = try container.decodeIfPresent( - Bool.self, - forKey: .includeUpstreamChanges - ) ?? true - self.openFeedbackInBrowser = try container.decodeIfPresent( - Bool.self, - forKey: .openFeedbackInBrowser - ) ?? true - self.revisionComparisonLayout = try container.decodeIfPresent( - RevisionComparisonLayout.self, - forKey: .revisionComparisonLayout - ) ?? .localLeft - self.controlNavigatorOrder = try container.decodeIfPresent( - ControlNavigatorOrder.self, - forKey: .controlNavigatorOrder - ) ?? .sortByName - self.defaultBranchName = try container.decodeIfPresent(String.self, forKey: .defaultBranchName) ?? "main" - } - } - - /// The style for comparison View - /// - **localLeft**: Local Revision on Left Side - /// - **localRight**: Local Revision on Right Side - enum RevisionComparisonLayout: String, Codable { - case localLeft - case localRight - } - - /// The style for control Navigator - /// - **sortName**: They are sorted by Name - /// - **sortDate**: They are sorted by Date - enum ControlNavigatorOrder: String, Codable { - case sortByName - case sortByDate - } - - struct SourceControlGit: Codable, Hashable { - /// The author name - var authorName: String = "" - /// The author email - var authorEmail: String = "" - /// Indicates what files should be ignored when committing - var ignoredFiles: [IgnoredFiles] = [] - /// Indicates whether we should rebase when pulling commits - var preferRebaseWhenPulling: Bool = false - /// Indicates whether we should show commits per file log - var showMergeCommitsPerFileLog: Bool = false - /// Default initializer - init() {} - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.authorName = try container.decodeIfPresent(String.self, forKey: .authorName) ?? "" - self.authorEmail = try container.decodeIfPresent(String.self, forKey: .authorEmail) ?? "" - self.ignoredFiles = try container.decodeIfPresent( - [IgnoredFiles].self, - forKey: .ignoredFiles - ) ?? [] - self.preferRebaseWhenPulling = try container.decodeIfPresent( - Bool.self, - forKey: .preferRebaseWhenPulling - ) ?? false - self.showMergeCommitsPerFileLog = try container.decodeIfPresent( - Bool.self, - forKey: .showMergeCommitsPerFileLog - ) ?? false - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGeneralView.swift b/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGeneralView.swift deleted file mode 100644 index 2ef8c3b947..0000000000 --- a/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGeneralView.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// SourceControlGeneralView.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 02/04/23. -// - -import SwiftUI - -struct SourceControlGeneralView: View { - @AppSettings(\.sourceControl.general) - var settings - - @State private var text: String = "main" - - var body: some View { - SettingsForm { - Section { - enableSourceControl - refreshLocalStatusAuto - fetchRefreshStatusAuto - addRemoveFilesAuto - selectFilesToCommitAuto - } - Section { - showSourceControlChanges - includeUpstreamChanges - } - Section { - comparisonView - sourceControlNavigator - defaultBranchName - } - } - } -} - -private extension SourceControlGeneralView { - private var enableSourceControl: some View { - Toggle( - "Enable source control", - isOn: $settings.enableSourceControl - ) - } - - private var refreshLocalStatusAuto: some View { - Toggle( - "Refresh local status automatically", - isOn: $settings.refreshStatusLocally - ) - } - - private var fetchRefreshStatusAuto: some View { - Toggle( - "Fetch and refresh server status automatically", - isOn: $settings.fetchRefreshServerStatus - ) - } - - private var addRemoveFilesAuto: some View { - Toggle( - "Add and remove files automatically", - isOn: $settings.addRemoveAutomatically - ) - } - - private var selectFilesToCommitAuto: some View { - Toggle( - "Select files to commit automatically", - isOn: $settings.selectFilesToCommit - ) - } - - private var showSourceControlChanges: some View { - Toggle( - "Show source control changes", - isOn: $settings.showSourceControlChanges - ) - } - - private var includeUpstreamChanges: some View { - Toggle( - "Include upstream changes", - isOn: $settings.includeUpstreamChanges - ) - } - - private var comparisonView: some View { - Picker( - "Comparison view", - selection: $settings.revisionComparisonLayout - ) { - Text("Local Revision on Left Side") - .tag(SettingsData.RevisionComparisonLayout.localLeft) - Text("Local Revision on Right Side") - .tag(SettingsData.RevisionComparisonLayout.localRight) - } - } - - private var sourceControlNavigator: some View { - Picker( - "Source control navigator", - selection: $settings.controlNavigatorOrder - ) { - Text("Sort by Name") - .tag(SettingsData.ControlNavigatorOrder.sortByName) - Text("Sort by Date") - .tag(SettingsData.ControlNavigatorOrder.sortByDate) - } - } - - private var defaultBranchName: some View { - TextField(text: $text) { - Text("Default branch name") - Text("Cannot contain spaces, backslashes, or other symbols") - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGitView.swift b/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGitView.swift deleted file mode 100644 index 504da57663..0000000000 --- a/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGitView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// SourceControlGitView.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 02/04/23. -// - -import SwiftUI - -struct SourceControlGitView: View { - @AppSettings(\.sourceControl.git) - var git - - @State var ignoredFileSelection: IgnoredFiles.ID? - - var body: some View { - SettingsForm { - Section { - gitAuthorName - gitEmail - } - Section { - preferToRebaseWhenPulling - showMergeCommitsInPerFileLog - } - } - } -} - -private extension SourceControlGitView { - private var gitAuthorName: some View { - TextField("Author Name", text: $git.authorName) - } - - private var gitEmail: some View { - TextField("Author Email", text: $git.authorEmail) - } - - private var preferToRebaseWhenPulling: some View { - Toggle( - "Prefer to rebase when pulling", - isOn: $git.preferRebaseWhenPulling - ) - } - - private var showMergeCommitsInPerFileLog: some View { - Toggle( - "Show merge commits in per-file log", - isOn: $git.showMergeCommitsPerFileLog - ) - } - - private var bottomToolbar: some View { - HStack(spacing: 12) { - Button {} label: { - Image(systemName: "plus") - .foregroundColor(Color.secondary) - } - .buttonStyle(.plain) - Button {} label: { - Image(systemName: "minus") - } - .disabled(true) - .buttonStyle(.plain) - Spacer() - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlSettingsView.swift b/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlSettingsView.swift deleted file mode 100644 index 120ead4a19..0000000000 --- a/CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlSettingsView.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// SourceControlSettingsView.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 02/04/23. -// - -import SwiftUI - -struct SourceControlSettingsView: View { - @State var selectedTab: String = "general" - - var body: some View { - Group { - switch selectedTab { - case "general": - SourceControlGeneralView() - case "git": - SourceControlGitView() - default: - SourceControlGeneralView() - } - } - .safeAreaInset(edge: .top, spacing: 0) { - Picker("", selection: $selectedTab) { - Text("General").tag("general") - Text("Git").tag("git") - } - .pickerStyle(.segmented) - .labelsHidden() - .padding(.horizontal, 20) - .padding(.bottom, 20) - - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift b/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift deleted file mode 100644 index b3201179aa..0000000000 --- a/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// TerminalPreferences.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import AppKit -import Foundation - -extension SettingsData { - - /// The global settings for the terminal emulator - struct TerminalSettings: Codable, Hashable, SearchableSettingsPage { - - /// The search keys - var searchKeys: [String] { - [ - "Shell", - "Use \"Option\" key as \"Meta\"", - "Use text editor font", - "Font", - "Font Size", - "Terminal Cursor Style", - "Blink Cursor" - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// If true terminal will use editor theme. - var useEditorTheme: Bool = true - - /// If true terminal appearance will always be `dark`. Otherwise it adapts to the system setting. - var darkAppearance: Bool = false - - /// If true, the terminal uses the background color of the theme, otherwise it is clear - var useThemeBackground: Bool = true - - /// If true, the terminal treats the `Option` key as the `Meta` key - var optionAsMeta: Bool = false - - /// The selected shell to use. - var shell: TerminalShell = .system - - /// The font to use in terminal. - var font: TerminalFont = .init() - - // The cursor style to use in terminal - var cursorStyle: TerminalCursorStyle = .block - - // Toggle for blinking cursor or not - var cursorBlink: Bool = false - - // Use font settings from Text Editing - var useTextEditorFont: Bool = true - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.darkAppearance = try container.decodeIfPresent(Bool.self, forKey: .darkAppearance) ?? false - self.optionAsMeta = try container.decodeIfPresent(Bool.self, forKey: .optionAsMeta) ?? false - self.shell = try container.decodeIfPresent(TerminalShell.self, forKey: .shell) ?? .system - self.font = try container.decodeIfPresent(TerminalFont.self, forKey: .font) ?? .init() - self.cursorStyle = try container.decodeIfPresent( - TerminalCursorStyle.self, - forKey: .cursorStyle - ) ?? .block - self.cursorBlink = try container.decodeIfPresent(Bool.self, forKey: .cursorBlink) ?? false - self.useTextEditorFont = try container.decodeIfPresent(Bool.self, forKey: .useTextEditorFont) ?? true - } - } - - /// The shell options. - /// - **bash**: uses the default bash shell - /// - **zsh**: uses the ZSH shell - /// - **system**: uses the system default shell (most likely ZSH) - enum TerminalShell: String, Codable, Hashable { - case bash - case zsh - case system - } - - enum TerminalCursorStyle: String, Codable, Hashable { - case block - case underline - case bar - } - - struct TerminalFont: Codable, Hashable { - /// The font size for the custom font - var size: Double = 12 - - /// The name of the custom font - var name: String = "SF Mono" - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? size - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? name - } - - /// Returns an NSFont representation of the current configuration. - /// - /// Returns the custom font, if enabled and able to be instantiated. - /// Otherwise returns a default system font monospaced. - var current: NSFont { - return NSFont(name: name, size: size) ?? NSFont.monospacedSystemFont(ofSize: size, weight: .medium) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/TerminalSettings/TerminalSettingsView.swift b/CodeEdit/Features/Settings/Pages/TerminalSettings/TerminalSettingsView.swift deleted file mode 100644 index ad0e5087a7..0000000000 --- a/CodeEdit/Features/Settings/Pages/TerminalSettings/TerminalSettingsView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// TerminalSettingsView.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 02/04/23. -// - -import SwiftUI - -struct TerminalSettingsView: View { - @AppSettings(\.terminal) - var settings - - var body: some View { - SettingsForm { - Section { - shellSelector - optionAsMetaToggle - } - Section { - useTextEditorFontToggle - if !settings.useTextEditorFont { - fontSelector - fontSizeSelector - } - } - Section { - cursorStyle - cursorBlink - } - } - } -} - -private extension TerminalSettingsView { - @ViewBuilder private var shellSelector: some View { - Picker("Shell", selection: $settings.shell) { - Text("System Default") - .tag(SettingsData.TerminalShell.system) - Divider() - Text("ZSH") - .tag(SettingsData.TerminalShell.zsh) - Text("Bash") - .tag(SettingsData.TerminalShell.bash) - } - } - - private var cursorStyle: some View { - Picker("Terminal Cursor Style", selection: $settings.cursorStyle) { - Text("Block") - .tag(SettingsData.TerminalCursorStyle.block) - Text("Underline") - .tag(SettingsData.TerminalCursorStyle.underline) - Text("Bar") - .tag(SettingsData.TerminalCursorStyle.bar) - } - } - - private var cursorBlink: some View { - Toggle("Blink Cursor", isOn: $settings.cursorBlink) - } - - private var optionAsMetaToggle: some View { - Toggle("Use \"Option\" key as \"Meta\"", isOn: $settings.optionAsMeta) - } - - private var useTextEditorFontToggle: some View { - Toggle("Use text editor font", isOn: $settings.useTextEditorFont) - } - - @ViewBuilder private var fontSelector: some View { - MonospacedFontPicker(title: "Font", selectedFontName: $settings.font.name) - } - - private var fontSizeSelector: some View { - Stepper( - "Font Size", - value: $settings.font.size, - in: 1...288, - step: 1, - format: .number - ) - } -} diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift deleted file mode 100644 index ab1112423e..0000000000 --- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// TextEditingPreferences.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import AppKit -import Foundation - -extension SettingsData { - - /// The global settings for text editing - struct TextEditingSettings: Codable, Hashable, SearchableSettingsPage { - - var searchKeys: [String] { - [ - "Prefer Indent Using", - "Tab Width", - "Wrap lines to editor width", - "Font", - "Font Size", - "Line Height", - "Letter Spacing", - "Autocomplete braces", - "Enable type-over completion", - "Bracket Pair Highlight" - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// An integer indicating how many spaces a `tab` will appear as visually. - var defaultTabWidth: Int = 4 - - /// The behavior of a `tab` keypress. If `.tab`, will insert a tab character. If `.spaces` will insert - /// `.spaceCount` spaces instead. - var indentOption: IndentOption = IndentOption(indentType: .spaces, spaceCount: 4) - - /// The font to use in editor. - var font: EditorFont = .init() - - /// A flag indicating whether type-over completion is enabled - var enableTypeOverCompletion: Bool = true - - /// A flag indicating whether braces are automatically completed - var autocompleteBraces: Bool = true - - /// A flag indicating whether to wrap lines to editor width - var wrapLinesToEditorWidth: Bool = true - - /// A multiplier for setting the line height. Defaults to `1.45` - var lineHeightMultiple: Double = 1.45 - - /// A multiplier for setting the letter spacing, `1` being no spacing and - /// `2` is one character of spacing between letters, defaults to `1`. - var letterSpacing: Double = 1.0 - - /// The behavior of bracket pair highlights. - var bracketHighlight: BracketPairHighlight = BracketPairHighlight() - - /// Default initializer - init() { - self.populateCommands() - } - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.defaultTabWidth = try container.decodeIfPresent(Int.self, forKey: .defaultTabWidth) ?? 4 - self.indentOption = try container.decodeIfPresent( - IndentOption.self, - forKey: .indentOption - ) ?? IndentOption(indentType: .spaces, spaceCount: 4) - self.font = try container.decodeIfPresent(EditorFont.self, forKey: .font) ?? .init() - self.enableTypeOverCompletion = try container.decodeIfPresent( - Bool.self, - forKey: .enableTypeOverCompletion - ) ?? true - self.autocompleteBraces = try container.decodeIfPresent( - Bool.self, - forKey: .autocompleteBraces - ) ?? true - self.wrapLinesToEditorWidth = try container.decodeIfPresent( - Bool.self, - forKey: .wrapLinesToEditorWidth - ) ?? true - self.lineHeightMultiple = try container.decodeIfPresent( - Double.self, - forKey: .lineHeightMultiple - ) ?? 1.45 - self.letterSpacing = try container.decodeIfPresent( - Double.self, - forKey: .letterSpacing - ) ?? 1 - self.bracketHighlight = try container.decodeIfPresent( - BracketPairHighlight.self, - forKey: .bracketHighlight - ) ?? BracketPairHighlight() - - self.populateCommands() - } - - /// Adds toggle-able preferences to the command palette via shared `CommandManager` - private func populateCommands() { - let mgr = CommandManager.shared - - mgr.addCommand( - name: "Toggle Type-Over Completion", - title: "Toggle Type-Over Completion", - id: "prefs.text_editing.type_over_completion", - command: CommandClosureWrapper { - Settings.shared.preferences.textEditing.enableTypeOverCompletion.toggle() - } - ) - - mgr.addCommand( - name: "Toggle Autocomplete Braces", - title: "Toggle Autocomplete Braces", - id: "prefs.text_editing.autocomplete_braces", - command: CommandClosureWrapper { - Settings.shared.preferences.textEditing.autocompleteBraces.toggle() - } - ) - - mgr.addCommand( - name: "Toggle Word Wrap", - title: "Toggle Word Wrap", - id: "prefs.text_editing.wrap_lines_to_editor_width", - command: CommandClosureWrapper { - Settings[\.textEditing].wrapLinesToEditorWidth.toggle() - } - ) - } - - struct IndentOption: Codable, Hashable { - var indentType: IndentType - // Kept even when `indentType` is `.tab` to retain the user's - // settings when changing `indentType`. - var spaceCount: Int = 4 - - enum IndentType: String, Codable { - case tab - case spaces - } - } - - struct BracketPairHighlight: Codable, Hashable { - /// The type of highlight to use - var highlightType: HighlightType = .flash - var useCustomColor: Bool = false - /// The color to use for the highlight. - var color: Theme.Attributes = Theme.Attributes(color: "FFFFFF") - - enum HighlightType: String, Codable { - case disabled - case bordered - case flash - case underline - } - } - } - - struct EditorFont: Codable, Hashable { - /// The font size for the font - var size: Double = 12 - - /// The name of the custom font - var name: String = "SF Mono" - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? size - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? name - } - - /// Returns an NSFont representation of the current configuration. - /// - /// Returns the custom font, if enabled and able to be instantiated. - /// Otherwise returns a default system font monospaced. - var current: NSFont { - return NSFont(name: name, size: size) ?? NSFont.monospacedSystemFont(ofSize: size, weight: .medium) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift deleted file mode 100644 index fc9b377fa4..0000000000 --- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift +++ /dev/null @@ -1,162 +0,0 @@ -// -// TextEditingSettingsView.swift -// CodeEdit -// -// Created by Austin Condiff on 4/2/23. -// - -import SwiftUI - -/// A view that implements the `Text Editing` settings page -struct TextEditingSettingsView: View { - @AppSettings(\.textEditing) - var textEditing - - var body: some View { - SettingsForm { - Section { - indentOption - defaultTabWidth - wrapLinesToEditorWidth - } - Section { - fontSelector - fontSizeSelector - lineHeight - letterSpacing - } - Section { - autocompleteBraces - enableTypeOverCompletion - } - Section { - bracketPairHighlight - } - } - } -} - -private extension TextEditingSettingsView { - @ViewBuilder private var fontSelector: some View { - MonospacedFontPicker(title: "Font", selectedFontName: $textEditing.font.name) - } - - @ViewBuilder private var fontSizeSelector: some View { - Stepper( - "Font Size", - value: $textEditing.font.size, - in: 1...288, - step: 1, - format: .number - ) - } - - @ViewBuilder private var autocompleteBraces: some View { - Toggle(isOn: $textEditing.autocompleteBraces) { - Text("Autocomplete braces") - Text("Automatically insert closing braces (\"}\")") - } - } - - @ViewBuilder private var enableTypeOverCompletion: some View { - Toggle("Enable type-over completion", isOn: $textEditing.enableTypeOverCompletion) - } - - @ViewBuilder private var wrapLinesToEditorWidth: some View { - Toggle("Wrap lines to editor width", isOn: $textEditing.wrapLinesToEditorWidth) - } - - @ViewBuilder private var lineHeight: some View { - Stepper( - "Line Height", - value: $textEditing.lineHeightMultiple, - in: 0.75...2.0, - step: 0.05, - format: .number - ) - } - - @ViewBuilder private var indentOption: some View { - Group { - Picker("Prefer Indent Using", selection: $textEditing.indentOption.indentType) { - Text("Tabs") - .tag(SettingsData.TextEditingSettings.IndentOption.IndentType.tab) - Text("Spaces") - .tag(SettingsData.TextEditingSettings.IndentOption.IndentType.spaces) - } - if textEditing.indentOption.indentType == .spaces { - HStack { - Stepper( - "Indent Width", - value: Binding( - get: { Double(textEditing.indentOption.spaceCount) }, - set: { textEditing.indentOption.spaceCount = Int($0) } - ), - in: 0...10, - step: 1, - format: .number - ) - Text("spaces") - .foregroundColor(.secondary) - } - .help("The number of spaces to insert when the tab key is pressed.") - } - } - } - - @ViewBuilder private var defaultTabWidth: some View { - HStack(alignment: .top) { - Stepper( - "Tab Width", - value: Binding( - get: { Double(textEditing.defaultTabWidth) }, - set: { textEditing.defaultTabWidth = Int($0) } - ), - in: 1...16, - step: 1, - format: .number - ) - Text("spaces") - .foregroundColor(.secondary) - } - .help("The visual width of tabs.") - } - - @ViewBuilder private var letterSpacing: some View { - Stepper( - "Letter Spacing", - value: $textEditing.letterSpacing, - in: 0.5...2.0, - step: 0.05, - format: .number - ) - } - - @ViewBuilder private var bracketPairHighlight: some View { - Group { - Picker( - "Bracket Pair Highlight", - selection: $textEditing.bracketHighlight.highlightType - ) { - Text("Disabled").tag(SettingsData.TextEditingSettings.BracketPairHighlight.HighlightType.disabled) - Divider() - Text("Bordered").tag(SettingsData.TextEditingSettings.BracketPairHighlight.HighlightType.bordered) - Text("Flash").tag(SettingsData.TextEditingSettings.BracketPairHighlight.HighlightType.flash) - Text("Underline").tag(SettingsData.TextEditingSettings.BracketPairHighlight.HighlightType.underline) - } - if [.bordered, .underline].contains(textEditing.bracketHighlight.highlightType) { - Toggle("Use Custom Color", isOn: $textEditing.bracketHighlight.useCustomColor) - SettingsColorPicker( - "Bracket Pair Highlight Color", - color: $textEditing.bracketHighlight.color.swiftColor - ) - .foregroundColor( - textEditing.bracketHighlight.useCustomColor - ? Color(NSColor.labelColor) - : Color(NSColor.secondaryLabelColor) - ) - .disabled(!textEditing.bracketHighlight.useCustomColor) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/Theme.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/Theme.swift deleted file mode 100644 index cbb1319991..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/Theme.swift +++ /dev/null @@ -1,442 +0,0 @@ -// -// Theme.swift -// CodeEditModules/Settings -// -// Created by Lukas Pistrol on 31.03.22. -// - -import SwiftUI -import CodeEditSourceEditor - -// swiftlint:disable file_length - -/// # Theme -/// -/// The model structure of themes for the editor & terminal emulator -struct Theme: Identifiable, Codable, Equatable, Hashable, Loopable { - enum CodingKeys: String, CodingKey { - case author, license, distributionURL, name, displayName, editor, terminal, version - case appearance = "type" - case metadataDescription = "description" - } - - static func == (lhs: Theme, rhs: Theme) -> Bool { - lhs.id == rhs.id - } - - /// The `id` of the theme - var id: String { self.name } - - /// The `author` of the theme - var author: String - - /// The `license` of the theme - var license: String - - /// A short `description` of the theme - var metadataDescription: String - - /// An URL for reference - var distributionURL: String - - /// The `unique name` of the theme - var name: String - - /// The `display name` of the theme - var displayName: String - - /// The `version` of the theme - var version: String - - /// The ``ThemeType`` of the theme - /// - /// Appears as `"type"` in the `settings.json` - var appearance: ThemeType - - /// Editor colors of the theme - var editor: EditorColors - - /// Terminal colors of the theme - var terminal: TerminalColors - - init( - editor: EditorColors, - terminal: TerminalColors, - author: String, - license: String, - metadataDescription: String, - distributionURL: String, - name: String, - displayName: String, - appearance: ThemeType, - version: String - ) { - self.author = author - self.license = license - self.metadataDescription = metadataDescription - self.distributionURL = distributionURL - self.name = name - self.displayName = displayName - self.appearance = appearance - self.version = version - self.editor = editor - self.terminal = terminal - } -} - -extension Theme { - /// The type of the theme - /// - **dark**: this is a theme for dark system appearance - /// - **light**: this is a theme for light system appearance - enum ThemeType: String, Codable, Hashable { - case dark - case light - } -} - -// MARK: - Attributes -extension Theme { - /// Attributes of a certain field - /// - /// As of now it only includes the colors `hex` string and - /// an accessor for a `SwiftUI` `Color`. - struct Attributes: Codable, Equatable, Hashable, Loopable { - - /// The 24-bit hex string of the color (e.g. #123456) - var color: String - - init(color: String) { - self.color = color - } - - /// The `SwiftUI` of ``color`` - var swiftColor: Color { - get { - Color(hex: color) - } - set { - self.color = newValue.hexString - } - } - - /// The `NSColor` of ``color`` - var nsColor: NSColor { - get { - NSColor(hex: color) - } - set { - self.color = newValue.hexString - } - } - } -} - -extension Theme { - /// The editor colors of the theme - struct EditorColors: Codable, Hashable, Loopable { - - var editorTheme: EditorTheme { - get { - .init( - text: text.nsColor, - insertionPoint: insertionPoint.nsColor, - invisibles: invisibles.nsColor, - background: background.nsColor, - lineHighlight: lineHighlight.nsColor, - selection: selection.nsColor, - keywords: keywords.nsColor, - commands: commands.nsColor, - types: types.nsColor, - attributes: attributes.nsColor, - variables: variables.nsColor, - values: values.nsColor, - numbers: numbers.nsColor, - strings: strings.nsColor, - characters: characters.nsColor, - comments: comments.nsColor - ) - } - set { - self.text.nsColor = newValue.text - self.insertionPoint.nsColor = newValue.insertionPoint - self.invisibles.nsColor = newValue.invisibles - self.background.nsColor = newValue.background - self.lineHighlight.nsColor = newValue.lineHighlight - self.selection.nsColor = newValue.selection - self.keywords.nsColor = newValue.keywords - self.commands.nsColor = newValue.commands - self.types.nsColor = newValue.types - self.attributes.nsColor = newValue.attributes - self.variables.nsColor = newValue.variables - self.values.nsColor = newValue.values - self.numbers.nsColor = newValue.numbers - self.strings.nsColor = newValue.strings - self.characters.nsColor = newValue.characters - self.comments.nsColor = newValue.comments - } - } - - var text: Attributes - var insertionPoint: Attributes - var invisibles: Attributes - var background: Attributes - var lineHighlight: Attributes - var selection: Attributes - var keywords: Attributes - var commands: Attributes - var types: Attributes - var attributes: Attributes - var variables: Attributes - var values: Attributes - var numbers: Attributes - var strings: Attributes - var characters: Attributes - var comments: Attributes - - /// Allows to look up properties by their name - /// - /// **Example:** - /// ```swift - /// editor["text"] - /// // equal to calling - /// editor.text - /// ``` - subscript(key: String) -> Attributes { - get { - switch key { - case "text": return self.text - case "insertionPoint": return self.insertionPoint - case "invisibles": return self.invisibles - case "background": return self.background - case "lineHighlight": return self.lineHighlight - case "selection": return self.selection - case "keywords": return self.keywords - case "commands": return self.commands - case "types": return self.types - case "attributes": return self.attributes - case "variables": return self.variables - case "values": return self.values - case "numbers": return self.numbers - case "strings": return self.strings - case "characters": return self.characters - case "comments": return self.comments - default: fatalError("Invalid key") - } - } - set { - switch key { - case "text": self.text = newValue - case "insertionPoint": self.insertionPoint = newValue - case "invisibles": self.invisibles = newValue - case "background": self.background = newValue - case "lineHighlight": self.lineHighlight = newValue - case "selection": self.selection = newValue - case "keywords": self.keywords = newValue - case "commands": self.commands = newValue - case "types": self.types = newValue - case "attributes": self.attributes = newValue - case "variables": self.variables = newValue - case "values": self.values = newValue - case "numbers": self.numbers = newValue - case "strings": self.strings = newValue - case "characters": self.characters = newValue - case "comments": self.comments = newValue - default: fatalError("Invalid key") - } - } - } - - init( - text: Attributes, - insertionPoint: Attributes, - invisibles: Attributes, - background: Attributes, - lineHighlight: Attributes, - selection: Attributes, - keywords: Attributes, - commands: Attributes, - types: Attributes, - attributes: Attributes, - variables: Attributes, - values: Attributes, - numbers: Attributes, - strings: Attributes, - characters: Attributes, - comments: Attributes - ) { - self.text = text - self.insertionPoint = insertionPoint - self.invisibles = invisibles - self.background = background - self.lineHighlight = lineHighlight - self.selection = selection - self.keywords = keywords - self.commands = commands - self.types = types - self.attributes = attributes - self.variables = variables - self.values = values - self.numbers = numbers - self.strings = strings - self.characters = characters - self.comments = comments - } - } -} - -extension Theme { - /// The terminal emulator colors of the theme - struct TerminalColors: Codable, Hashable, Loopable { - var text: Attributes - var boldText: Attributes - var cursor: Attributes - var background: Attributes - var selection: Attributes - var black: Attributes - var red: Attributes - var green: Attributes - var yellow: Attributes - var blue: Attributes - var magenta: Attributes - var cyan: Attributes - var white: Attributes - var brightBlack: Attributes - var brightRed: Attributes - var brightGreen: Attributes - var brightYellow: Attributes - var brightBlue: Attributes - var brightMagenta: Attributes - var brightCyan: Attributes - var brightWhite: Attributes - - var ansiColors: [String] { - [ - black.color, - red.color, - green.color, - yellow.color, - blue.color, - magenta.color, - cyan.color, - white.color, - brightBlack.color, - brightRed.color, - brightGreen.color, - brightYellow.color, - brightBlue.color, - brightMagenta.color, - brightCyan.color, - brightWhite.color, - ] - } - - /// Allows to look up properties by their name - /// - /// **Example:** - /// ```swift - /// terminal["text"] - /// // equal to calling - /// terminal.text - /// ``` - subscript(key: String) -> Attributes { - get { - switch key { - case "text": return self.text - case "boldText": return self.boldText - case "cursor": return self.cursor - case "background": return self.background - case "selection": return self.selection - case "black": return self.black - case "red": return self.red - case "green": return self.green - case "yellow": return self.yellow - case "blue": return self.blue - case "magenta": return self.magenta - case "cyan": return self.cyan - case "white": return self.white - case "brightBlack": return self.brightBlack - case "brightRed": return self.brightRed - case "brightGreen": return self.brightGreen - case "brightYellow": return self.brightYellow - case "brightBlue": return self.brightBlue - case "brightMagenta": return self.brightMagenta - case "brightCyan": return self.brightCyan - case "brightWhite": return self.brightWhite - default: fatalError("Invalid key") - } - } - set { - switch key { - case "text": self.text = newValue - case "boldText": self.boldText = newValue - case "cursor": self.cursor = newValue - case "background": self.background = newValue - case "selection": self.selection = newValue - case "black": self.black = newValue - case "red": self.red = newValue - case "green": self.green = newValue - case "yellow": self.yellow = newValue - case "blue": self.blue = newValue - case "magenta": self.magenta = newValue - case "cyan": self.cyan = newValue - case "white": self.white = newValue - case "brightBlack": self.brightBlack = newValue - case "brightRed": self.brightRed = newValue - case "brightGreen": self.brightGreen = newValue - case "brightYellow": self.brightYellow = newValue - case "brightBlue": self.brightBlue = newValue - case "brightMagenta": self.brightMagenta = newValue - case "brightCyan": self.brightCyan = newValue - case "brightWhite": self.brightWhite = newValue - default: fatalError("Invalid key") - } - } - } - - init( - text: Attributes, - boldText: Attributes, - cursor: Attributes, - background: Attributes, - selection: Attributes, - black: Attributes, - red: Attributes, - green: Attributes, - yellow: Attributes, - blue: Attributes, - magenta: Attributes, - cyan: Attributes, - white: Attributes, - brightBlack: Attributes, - brightRed: Attributes, - brightGreen: Attributes, - brightYellow: Attributes, - brightBlue: Attributes, - brightMagenta: Attributes, - brightCyan: Attributes, - brightWhite: Attributes - ) { - self.text = text - self.boldText = boldText - self.cursor = cursor - self.background = background - self.selection = selection - self.black = black - self.red = red - self.green = green - self.yellow = yellow - self.blue = blue - self.magenta = magenta - self.cyan = cyan - self.white = white - self.brightBlack = brightBlack - self.brightRed = brightRed - self.brightGreen = brightGreen - self.brightYellow = brightYellow - self.brightBlue = brightBlue - self.brightMagenta = brightMagenta - self.brightCyan = brightCyan - self.brightWhite = brightWhite - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeModel.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeModel.swift deleted file mode 100644 index 2c0586a540..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeModel.swift +++ /dev/null @@ -1,328 +0,0 @@ -// -// ThemeModel.swift -// CodeEditModules/Settings -// -// Created by Lukas Pistrol on 31.03.22. -// - -import SwiftUI - -/// The Theme View Model. Accessible via the singleton "``ThemeModel/shared``". -/// -/// **Usage:** -/// ```swift -/// @StateObject -/// private var themeModel: ThemeModel = .shared -/// ``` -final class ThemeModel: ObservableObject { - static let shared: ThemeModel = .init() - - /// Default instance of the `FileManager` - private let filemanager = FileManager.default - - /// The base folder url `~/Library/Application Support/CodeEdit/` - private var baseURL: URL { - filemanager.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support/CodeEdit") - } - - var bundledThemesURL: URL? { - Bundle.main.resourceURL?.appendingPathComponent("DefaultThemes", isDirectory: true) ?? nil - } - - /// The URL of the `Themes` folder - internal var themesURL: URL { - baseURL.appendingPathComponent("Themes", isDirectory: true) - } - - /// The URL of the `Extensions` folder - internal var extensionsURL: URL { - baseURL.appendingPathComponent("Extensions", isDirectory: true) - } - - /// The URL of the `settings.json` file - internal var settingsURL: URL { - baseURL.appendingPathComponent("settings.json", isDirectory: true) - } - - /// Selected 'light' theme - /// Used for auto-switching theme to match macOS system appearance - @Published var selectedLightTheme: Theme? { - didSet { - DispatchQueue.main.async { - Settings.shared - .preferences.theme.selectedLightTheme = self.selectedLightTheme?.name ?? "Broken" - } - } - } - - /// Selected 'dark' theme - /// Used for auto-switching theme to match macOS system appearance - @Published var selectedDarkTheme: Theme? { - didSet { - DispatchQueue.main.async { - Settings.shared - .preferences.theme.selectedDarkTheme = self.selectedDarkTheme?.name ?? "Broken" - } - } - } - - /// The selected appearance in the sidebar. - /// - **0**: dark mode themes - /// - **1**: light mode themes - @Published var selectedAppearance: Int = 0 - - /// The selected tab in the main section. - /// - **0**: Preview - /// - **1**: Editor - /// - **2**: Terminal - @Published var selectedTab: Int = 1 - - /// An array of loaded ``Theme``. - @Published var themes: [Theme] = [] { - didSet { - saveThemes() - } - } - - /// The currently selected ``Theme``. - @Published var selectedTheme: Theme? { - didSet { - DispatchQueue.main.async { - Settings[\.theme].selectedTheme = self.selectedTheme?.name - } - updateAppearanceTheme() - } - } - - /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/dark`` - var darkThemes: [Theme] { - themes.filter { $0.appearance == .dark } - } - - /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/light`` - var lightThemes: [Theme] { - themes.filter { $0.appearance == .light } - } - - private init() { - do { - try loadThemes() - } catch { - print(error) - } - } - - /// Loads a theme from a given url and appends it to ``themes``. - /// - Parameter url: The URL of the theme - /// - Returns: A ``Theme`` - private func load(from url: URL) throws -> Theme? { - do { - // get the data from the provided file - let json = try Data(contentsOf: url) - // decode the json into ``Theme`` - let theme = try JSONDecoder().decode(Theme.self, from: json) - return theme - } catch { - print(error) - return nil - } - } - - /// Loads all available themes from `~/Library/Application Support/CodeEdit/Themes/` - /// - /// If no themes are available, it will create a default theme and save - /// it to the location mentioned above. - /// - /// When overrides are found in `~/Library/Application Support/CodeEdit/settings.json` - /// they are applied to the loaded themes without altering the original - /// the files in `~/Library/Application Support/CodeEdit/Themes/`. - func loadThemes() throws { // swiftlint:disable:this function_body_length - if let bundledThemesURL = bundledThemesURL { - // remove all themes from memory - themes.removeAll() - - var isDir: ObjCBool = false - - // check if a themes directory exists, otherwise create one - if !filemanager.fileExists(atPath: themesURL.path, isDirectory: &isDir) { - try filemanager.createDirectory(at: themesURL, withIntermediateDirectories: true) - } - - // get all URLs in users themes folder that end with `.cetheme` - let userDefinedThemeFilenames = try filemanager.contentsOfDirectory(atPath: themesURL.path).filter { - $0.contains(".cetheme") - } - let userDefinedThemeURLs = userDefinedThemeFilenames.map { - themesURL.appendingPathComponent($0) - } - - // get all bundled theme URLs - let bundledThemeFilenames = try filemanager.contentsOfDirectory(atPath: bundledThemesURL.path).filter { - $0.contains(".cetheme") - } - let bundledThemeURLs = bundledThemeFilenames.map { - bundledThemesURL.appendingPathComponent($0) - } - - // combine user theme URLs with bundled theme URLs - let themeURLs = userDefinedThemeURLs + bundledThemeURLs - - let prefs = Settings.shared.preferences - - // load each theme from disk and store in memory - try themeURLs.forEach { fileURL in - if var theme = try load(from: fileURL) { - - // get all properties of terminal and editor colors - guard let terminalColors = try theme.terminal.allProperties() as? [String: Theme.Attributes], - let editorColors = try theme.editor.allProperties() as? [String: Theme.Attributes] - else { - print("error") - // TODO: Throw a proper error - throw NSError() // swiftlint:disable:this discouraged_direct_init - } - - // check if there are any overrides in `settings.json` - if let overrides = prefs.theme.overrides[theme.name]?["terminal"] { - terminalColors.forEach { (key, _) in - if let attributes = overrides[key] { - theme.terminal[key] = attributes - } - } - } - - if let overrides = prefs.theme.overrides[theme.name]?["editor"] { - editorColors.forEach { (key, _) in - if let attributes = overrides[key] { - theme.editor[key] = attributes - } - } - } - - // add the theme to themes array - self.themes.append(theme) - - // if there already is a selected theme in `settings.json` select this theme - // otherwise take the first in the list - self.selectedDarkTheme = self.darkThemes.first { - $0.name == prefs.theme.selectedDarkTheme - } ?? self.darkThemes.first - - self.selectedLightTheme = self.lightThemes.first { - $0.name == prefs.theme.selectedLightTheme - } ?? self.lightThemes.first - - // For selecting the default theme, doing it correctly on startup requires some more logic - let userSelectedTheme = self.themes.first { $0.name == prefs.theme.selectedTheme } - let systemAppearance = NSAppearance.currentDrawing().name - - if userSelectedTheme != nil { - self.selectedTheme = userSelectedTheme - } else { - if systemAppearance == .darkAqua { - self.selectedTheme = self.selectedDarkTheme - } else { - self.selectedTheme = self.selectedLightTheme - } - } - } - } - } - } - - /// This function stores 'dark' and 'light' themes into `ThemePreferences` if user happens to select a theme - private func updateAppearanceTheme() { - if self.selectedTheme?.appearance == .dark { - self.selectedDarkTheme = self.selectedTheme - } else if self.selectedTheme?.appearance == .light { - self.selectedLightTheme = self.selectedTheme - } - } - - /// Removes all overrides of the given theme in - /// `~/Library/Application Support/CodeEdit/settings.json` - /// - /// After removing overrides, themes are reloaded - /// from `~/Library/Application Support/CodeEdit/Themes`. See ``loadThemes()`` - /// for more information. - /// - /// - Parameter theme: The theme to reset - func reset(_ theme: Theme) { - Settings.shared.preferences.theme.overrides[theme.name] = [:] - do { - try self.loadThemes() - } catch { - print(error) - } - } - - /// Removes the given theme from `–/Library/Application Support/CodeEdit/themes` - /// - /// After removing the theme, themes are reloaded - /// from `~/Library/Application Support/CodeEdit/Themes`. See ``loadThemes()`` - /// for more information. - /// - /// - Parameter theme: The theme to delete - func delete(_ theme: Theme) { - let url = themesURL - .appendingPathComponent(theme.name) - .appendingPathExtension("cetheme") - do { - // remove the theme from the list - try filemanager.removeItem(at: url) - - // remove from overrides in `settings.json` - Settings.shared.preferences.theme.overrides.removeValue(forKey: theme.name) - - // reload themes - try self.loadThemes() - } catch { - print(error) - } - } - - /// Saves changes on theme properties to `overrides` - /// in `~/Library/Application Support/CodeEdit/settings.json`. - private func saveThemes() { - let url = themesURL - themes.forEach { theme in - do { - // load the original theme from `~/Library/Application Support/CodeEdit/Themes/` - let originalUrl = url.appendingPathComponent(theme.name).appendingPathExtension("cetheme") - let originalData = try Data(contentsOf: originalUrl) - let originalTheme = try JSONDecoder().decode(Theme.self, from: originalData) - - // get properties of the current theme as well as the original - guard let terminalColors = try theme.terminal.allProperties() as? [String: Theme.Attributes], - let editorColors = try theme.editor.allProperties() as? [String: Theme.Attributes], - let oTermColors = try originalTheme.terminal.allProperties() as? [String: Theme.Attributes], - let oEditColors = try originalTheme.editor.allProperties() as? [String: Theme.Attributes] - else { - // TODO: Throw a proper error - throw NSError() // swiftlint:disable:this discouraged_direct_init - } - - // compare the properties and if there are differences, save to overrides - // in `settings.json - var newAttr: [String: [String: Theme.Attributes]] = ["terminal": [:], "editor": [:]] - terminalColors.forEach { (key, value) in - if value != oTermColors[key] { - newAttr["terminal"]?[key] = value - } - } - - editorColors.forEach { (key, value) in - if value != oEditColors[key] { - newAttr["editor"]?[key] = value - } - } - DispatchQueue.main.async { - Settings.shared.preferences.theme.overrides[theme.name] = newAttr - } - - } catch { - print(error) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift deleted file mode 100644 index b37f986be3..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// ThemePreferences.swift -// CodeEditModules/Settings -// -// Created by Nanashi Li on 2022/04/08. -// - -import Foundation - -extension SettingsData { - - /// A dictionary containing the keys and associated ``Theme/Attributes`` of overridden properties - /// - /// ```json - /// { - /// "editor" : { - /// "background" : { - /// "color" : "#123456" - /// }, - /// ... - /// }, - /// "terminal" : { - /// "blue" : { - /// "color" : "#1100FF" - /// }, - /// ... - /// } - /// } - /// ``` - typealias ThemeOverrides = [String: [String: Theme.Attributes]] - - /// The global settings for themes - struct ThemeSettings: Codable, Hashable, SearchableSettingsPage { - - var searchKeys: [String] { - [ - "Automatically Change theme based on system appearance", - "Always use dark terminal appearance", - "Use theme background", - "Light Appearance", - "GitHub Light", - "Xcode Light", - "Solarized Light", - "Solarized Dark", - "Midnight", - "Xcode Dark", - "GitHub Dark" - ] - .map { NSLocalizedString($0, comment: "") } - } - - /// The name of the currently selected dark theme - var selectedDarkTheme: String = "Default (Dark)" - - /// The name of the currently selected light theme - var selectedLightTheme: String = "Default (Light)" - - /// The name of the currently selected theme - var selectedTheme: String? - - /// Use the system background that matches the appearance setting - var useThemeBackground: Bool = true - - /// Automatically change theme based on system appearance - var matchAppearance: Bool = true - - /// Dictionary of themes containing overrides - /// - /// ```json - /// { - /// "overrides" : { - /// "DefaultDark" : { - /// "editor" : { - /// "background" : { - /// "color" : "#123456" - /// }, - /// ... - /// }, - /// "terminal" : { - /// "blue" : { - /// "color" : "#1100FF" - /// }, - /// ... - /// } - /// ... - /// }, - /// ... - /// }, - /// ... - /// } - /// ``` - var overrides: [String: ThemeOverrides] = [:] - - /// Default initializer - init() {} - - /// Explicit decoder init for setting default values when key is not present in `JSON` - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.selectedDarkTheme = try container.decodeIfPresent( - String.self, forKey: .selectedDarkTheme - ) ?? selectedDarkTheme - self.selectedLightTheme = try container.decodeIfPresent( - String.self, forKey: .selectedLightTheme - ) ?? selectedLightTheme - self.selectedTheme = try container.decodeIfPresent(String.self, forKey: .selectedTheme) - self.useThemeBackground = try container.decodeIfPresent(Bool.self, forKey: .useThemeBackground) ?? true - self.matchAppearance = try container.decodeIfPresent( - Bool.self, forKey: .matchAppearance - ) ?? true - self.overrides = try container.decodeIfPresent([String: ThemeOverrides].self, forKey: .overrides) ?? [:] - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingThemeRow.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingThemeRow.swift deleted file mode 100644 index 2e1522f3a5..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingThemeRow.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// ThemeSettingThemeRow.swift -// CodeEdit -// -// Created by Austin Condiff on 4/3/23. -// - -import SwiftUI - -struct ThemeSettingsThemeRow: View { - @Binding var theme: Theme - var active: Bool - var action: (Theme) -> Void - - @State private var presentingDetails: Bool = false - - @State private var isHovering = false - - var body: some View { - HStack { - Image(systemName: "checkmark") - .opacity(active ? 1 : 0) - .font(.system(size: 10.5, weight: .bold)) - VStack(alignment: .leading) { - Text(theme.displayName) - Text(theme.author) - .foregroundColor(.secondary) - .font(.footnote) - } - .frame(maxWidth: .infinity, alignment: .leading) - Button { - presentingDetails = true - } label: { - Text("Details...") - } - .buttonStyle(.bordered) - .opacity(isHovering ? 1 : 0) - ThemeSettingsColorPreview(theme) - } - .padding(10) - .onHover { hovering in - isHovering = hovering - } - .onTapGesture { - action(theme) - } - .sheet(isPresented: $presentingDetails) { - ThemeSettingsThemeDetails($theme) - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift deleted file mode 100644 index 77f39f9f53..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// ThemeSettingsColorPreview.swift -// CodeEdit -// -// Created by Austin Condiff on 4/3/23. -// - -import SwiftUI - -struct ThemeSettingsColorPreview: View { - var theme: Theme - - @StateObject private var themeModel: ThemeModel = .shared - - @State private var displayName: String - - init(_ theme: Theme) { - self.theme = theme - self.displayName = theme.displayName - } - - var body: some View { - HStack(spacing: 5) { - ThemeSettingsColorPreviewColor( - theme.editor.keywords.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.commands.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.types.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.attributes.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.variables.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.values.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.numbers.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.strings.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.characters.swiftColor - ) - ThemeSettingsColorPreviewColor( - theme.editor.comments.swiftColor - ) - } - .padding(12) - .background(theme.editor.background.swiftColor) - .clipShape(Capsule()) - .overlay { - ZStack { - Capsule() - .stroke(Color(.black).opacity(0.2), lineWidth: 0.5) - .frame(maxWidth: .infinity, maxHeight: .infinity) - Capsule() - .strokeBorder(Color(.white).opacity(0.2), lineWidth: 0.5) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - } - } -} - -struct ThemeSettingsColorPreviewColor: View { - private var color: Color - - init(_ color: Color) { - self.color = color - } - - var body: some View { - color - .frame(width: 5, height: 5) - .cornerRadius(5) - .overlay { - ZStack { - Circle() - .stroke(Color(.black).opacity(0.2), lineWidth: 0.5) - .frame(width: 5, height: 5) - Circle() - .strokeBorder(Color(.white).opacity(0.2), lineWidth: 0.5) - .frame(width: 5, height: 5) - } - } - - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsThemeDetails.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsThemeDetails.swift deleted file mode 100644 index 77ebfa7777..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsThemeDetails.swift +++ /dev/null @@ -1,123 +0,0 @@ -// -// ThemeSettingsThemeDetails.swift -// CodeEdit -// -// Created by Austin Condiff on 4/3/23. -// - -import SwiftUI - -struct ThemeSettingsThemeDetails: View { - @Environment(\.dismiss) - var dismiss - - @Binding var theme: Theme - - @State private var initialTheme: Theme - - @StateObject private var themeModel: ThemeModel = .shared - - init(_ theme: Binding) { - _theme = theme - _initialTheme = State(initialValue: theme.wrappedValue) - } - - var body: some View { - VStack(spacing: 0) { - Form { - Section { - TextField("Name", text: $theme.displayName) - } - Section { - SettingsColorPicker( - "Text", - color: $theme.editor.text.swiftColor - ) - SettingsColorPicker( - "Cursor", - color: $theme.editor.insertionPoint.swiftColor - ) - SettingsColorPicker( - "Invisibles", - color: $theme.editor.invisibles.swiftColor - ) - } - Section { - SettingsColorPicker( - "Background", - color: $theme.editor.background.swiftColor - ) - SettingsColorPicker( - "Current Line", - color: $theme.editor.lineHighlight.swiftColor - ) - SettingsColorPicker( - "Selection", - color: $theme.editor.selection.swiftColor - ) - } - Section { - SettingsColorPicker( - "Keywords", - color: $theme.editor.keywords.swiftColor - ) - SettingsColorPicker( - "Commands", - color: $theme.editor.commands.swiftColor - ) - SettingsColorPicker( - "Types", - color: $theme.editor.types.swiftColor - ) - SettingsColorPicker( - "Attributes", - color: $theme.editor.attributes.swiftColor - ) - SettingsColorPicker( - "Variables", - color: $theme.editor.variables.swiftColor - ) - SettingsColorPicker( - "Values", - color: $theme.editor.values.swiftColor - ) - SettingsColorPicker( - "Numbers", - color: $theme.editor.numbers.swiftColor - ) - SettingsColorPicker( - "Strings", - color: $theme.editor.strings.swiftColor - ) - SettingsColorPicker( - "Characters", - color: $theme.editor.characters.swiftColor - ) - SettingsColorPicker( - "Comments", - color: $theme.editor.comments.swiftColor - ) - } - }.formStyle(.grouped) - Divider() - HStack { - Spacer() - Button { - theme = initialTheme - dismiss() - } label: { - Text("Cancel") - } - .buttonStyle(.bordered) - Button { - dismiss() - } label: { - Text("Done") - } - .buttonStyle(.borderedProminent) - } - .padding() - } - .constrainHeightToWindow() - } -} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsView.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsView.swift deleted file mode 100644 index b3e44793c4..0000000000 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsView.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// ThemePreferencesView.swift -// CodeEdit -// -// Created by Lukas Pistrol on 30.03.22. -// - -import SwiftUI - -/// A view that implements the `Theme` preference section -struct ThemeSettingsView: View { - @Environment(\.colorScheme) - var colorScheme - @ObservedObject private var themeModel: ThemeModel = .shared - @AppSettings(\.theme) - var settings - @AppSettings(\.terminal.darkAppearance) - var useDarkTerminalAppearance - - @State private var listView: Bool = false - @State private var selectedAppearance: ThemeSettingsAppearances = .dark - - enum ThemeSettingsAppearances: String, CaseIterable { - case light = "Light Appearance" - case dark = "Dark Appearance" - } - - func getThemeActive (_ theme: Theme) -> Bool { - if settings.matchAppearance { - return selectedAppearance == .dark - ? themeModel.selectedDarkTheme == theme - : selectedAppearance == .light - ? themeModel.selectedLightTheme == theme - : themeModel.selectedTheme == theme - } - return themeModel.selectedTheme == theme - } - - func activateTheme (_ theme: Theme) { - if settings.matchAppearance { - if selectedAppearance == .dark { - themeModel.selectedDarkTheme = theme - } else if selectedAppearance == .light { - themeModel.selectedLightTheme = theme - } - if (selectedAppearance == .dark && colorScheme == .dark) - || (selectedAppearance == .light && colorScheme == .light) { - themeModel.selectedTheme = theme - } - } else { - themeModel.selectedTheme = theme - if colorScheme == .light { - themeModel.selectedLightTheme = theme - } - if colorScheme == .dark { - themeModel.selectedDarkTheme = theme - } - } - } - - var body: some View { - SettingsForm { - Section { - changeThemeOnSystemAppearance - if settings.matchAppearance { - alwaysUseDarkTerminalAppearance - } - useThemeBackground - } - Section { - VStack(spacing: 0) { - if settings.matchAppearance { - Picker("", selection: $selectedAppearance) { - ForEach(ThemeSettingsAppearances.allCases, id: \.self) { tab in - Text(tab.rawValue) - .tag(tab) - } - } - .pickerStyle(.segmented) - .labelsHidden() - .padding(10) - } - VStack(spacing: 0) { - ForEach(selectedAppearance == .dark ? themeModel.darkThemes : themeModel.lightThemes) { theme in - Divider() - ThemeSettingsThemeRow( - theme: $themeModel.themes[themeModel.themes.firstIndex(of: theme)!], - active: getThemeActive(theme), - action: activateTheme - ).id(theme) - } - ForEach(selectedAppearance == .dark ? themeModel.lightThemes : themeModel.darkThemes) { theme in - Divider() - ThemeSettingsThemeRow( - theme: $themeModel.themes[themeModel.themes.firstIndex(of: theme)!], - active: getThemeActive(theme), - action: activateTheme - ).id(theme) - } - } - } - .padding(-10) - } - } - } -} - -private extension ThemeSettingsView { - private var useThemeBackground: some View { - Toggle("Use theme background ", isOn: $settings.useThemeBackground) - } - - private var alwaysUseDarkTerminalAppearance: some View { - Toggle("Always use dark terminal appearance", isOn: $useDarkTerminalAppearance) - } - - private var changeThemeOnSystemAppearance: some View { - Toggle( - "Automatically change theme based on system appearance", - isOn: $settings.matchAppearance - ) - .onChange(of: settings.matchAppearance) { value in - if value { - if colorScheme == .dark { - themeModel.selectedTheme = themeModel.selectedDarkTheme - } else { - themeModel.selectedTheme = themeModel.selectedLightTheme - } - } else { - themeModel.selectedTheme = themeModel.themes.first { - $0.name == settings.selectedTheme - } - } - } - } -} diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift deleted file mode 100644 index 8bc583efd6..0000000000 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// SettingsView.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 26/03/23. -// - -import SwiftUI -import CodeEditSymbols - -/// A struct for settings -struct SettingsView: View { - @StateObject var model = SettingsViewModel() - @Environment(\.colorScheme) - private var colorScheme - - /// Variables for the selected Page, the current search text and software updater - @State private var selectedPage: SettingsPage = Self.pages[0].page - @State private var searchText: String = "" - - @Environment(\.presentationMode) - var presentationMode - - static var pages: [PageAndSettings] = [ - .init( - SettingsPage( - .general, - baseColor: .gray, - icon: .system("gear") - ) - ), - .init( - SettingsPage( - .accounts, - baseColor: .blue, - icon: .system("at") - ) - ), - .init( - SettingsPage( - .navigation, - baseColor: .green, - icon: .system("arrow.triangle.turn.up.right.diamond.fill") - ) - ), - .init( - SettingsPage( - .theme, - baseColor: .pink, - icon: .system("paintbrush.fill") - ) - ), - .init( - SettingsPage( - .textEditing, - baseColor: .blue, - icon: .system("pencil.line") - ) - ), - .init( - SettingsPage( - .terminal, - baseColor: .blue, - icon: .system("terminal.fill") - ) - ), - .init( - SettingsPage( - .search, - baseColor: .blue, - icon: .system("magnifyingglass") - ) - ), - .init( - SettingsPage( - .sourceControl, - baseColor: .blue, - icon: .symbol("vault") - ) - ), - .init( - SettingsPage( - .location, - baseColor: .green, - icon: .system("externaldrive.fill") - ) - ) - ] - - @ObservedObject private var settings: Settings = .shared - - let updater: SoftwareUpdater - - /// Searches through an array of pages to check if a page name exists in the array - private func resultFound(_ page: SettingsPage, pages: [SettingsPage]) -> SettingsSearchResult { - let lowercasedSearchText = searchText.lowercased() - var returnedPages: [SettingsPage] = [] - var foundPage = false - - for item in pages where item.name == page.name { - if item.isSetting && item.settingName.lowercased().contains(lowercasedSearchText) { - returnedPages.append(item) - } else if item.name.rawValue.contains(lowercasedSearchText) && !item.isSetting { - foundPage = true - } - } - - return SettingsSearchResult(pageFound: foundPage, pages: returnedPages) - } - - /// Gets search results from a settings page and an array of settings - @ViewBuilder - private func results(_ page: SettingsPage, _ settings: [SettingsPage]) -> some View { - if !searchText.isEmpty { - let results: SettingsSearchResult = resultFound(page, pages: settings) - - if !results.pages.isEmpty && !page.isSetting { - SettingsPageView(page, searchText: searchText) - - ForEach(results.pages, id: \.settingName) { setting in - NavigationLink(value: setting) { - setting.settingName.highlightOccurrences(searchText) - .padding(.leading, 22) - } - } - } else if - page.name.rawValue.lowercased().contains(searchText.lowercased()) && - !page.isSetting - { - SettingsPageView(page, searchText: searchText) - } - } else if !page.isSetting { - SettingsPageView(page, searchText: searchText) - } - } - - var body: some View { - NavigationSplitView { - List(selection: $selectedPage) { - Section { - ForEach(Self.pages) { pageAndSettings in - results(pageAndSettings.page, pageAndSettings.settings) - } - } - } - .searchable(text: $searchText, placement: .sidebar, prompt: "Search") - .navigationSplitViewColumnWidth(215) - } detail: { - Group { - switch selectedPage.name { - case .general: - GeneralSettingsView().environmentObject(updater) - case .accounts: - AccountsSettingsView() - case .navigation: - NavigationSettingsView() - case .theme: - ThemeSettingsView() - case .textEditing: - TextEditingSettingsView() - case .terminal: - TerminalSettingsView() - case .search: - SearchSettingsView() - case .sourceControl: - SourceControlSettingsView() - case .location: - LocationsSettingsView() - default: - Text("Implementation Needed").frame(alignment: .center) - } - } - .navigationSplitViewColumnWidth(500) - .hideSidebarToggle() - .onAppear { - model.backButtonVisible = false - } - } - .navigationTitle(selectedPage.name.rawValue) - .toolbar { - ToolbarItem(placement: .navigation) { - if !model.backButtonVisible { - Rectangle() - .frame(width: 10) - .opacity(0) - } else { - EmptyView() - } - } - } - .environmentObject(model) - .onAppear { - selectedPage = Self.pages[0].page - } - } -} - -class SettingsViewModel: ObservableObject { - @Published var backButtonVisible: Bool = false - @Published var scrolledToTop: Bool = false -} diff --git a/CodeEdit/Features/Settings/SettingsWindow.swift b/CodeEdit/Features/Settings/SettingsWindow.swift deleted file mode 100644 index ab34f579bb..0000000000 --- a/CodeEdit/Features/Settings/SettingsWindow.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// SettingsWindow.swift -// CodeEdit -// -// Created by Austin Condiff on 3/31/23. -// - -import SwiftUI - -struct SettingsWindow: Scene { - private let updater = SoftwareUpdater() - - var body: some Scene { - Window("Settings", id: SceneID.settings.rawValue) { - SettingsView(updater: updater) - .frame(minWidth: 715, maxWidth: 715) - .task { - let window = NSApp.windows.first { $0.identifier?.rawValue == SceneID.settings.rawValue }! - window.titlebarAppearsTransparent = true - } - } - .windowStyle(.automatic) - .windowToolbarStyle(.unified) - .windowResizability(.contentSize) - } -} diff --git a/CodeEdit/Features/Settings/SoftwareUpdater.swift b/CodeEdit/Features/Settings/SoftwareUpdater.swift deleted file mode 100644 index cb27da810e..0000000000 --- a/CodeEdit/Features/Settings/SoftwareUpdater.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// SoftwareUpdater.swift -// CodeEdit -// -// Created by Austin Condiff on 9/19/22. -// - -import Foundation -import Sparkle - -class SoftwareUpdater: NSObject, ObservableObject, SPUUpdaterDelegate { - private var updater: SPUUpdater? - private var automaticallyChecksForUpdatesObservation: NSKeyValueObservation? - private var lastUpdateCheckDateObservation: NSKeyValueObservation? - private var appcastURL = URL( - string: "https://github.com/CodeEditApp/CodeEdit/releases/download/latest/appcast.xml" - )! - - @Published var automaticallyChecksForUpdates = false { - didSet { - updater?.automaticallyChecksForUpdates = automaticallyChecksForUpdates - } - } - - @Published var lastUpdateCheckDate: Date? - - @Published var includePrereleaseVersions = true { - didSet { - UserDefaults.standard.setValue(includePrereleaseVersions, forKey: "includePrereleaseVersions") - } - } - - private var feedURLTask: Task<(), Never>? - - private func setFeedURL() async { - let url = URL(string: "https://api.github.com/repos/CodeEditApp/CodeEdit/releases/latest")! - let request = URLRequest(url: url) - guard let data = try? await URLSession.shared.data(for: request), - let result = try? JSONDecoder().decode(GHAPIResult.self, from: data.0) else { - await MainActor.run { - self.updater?.setFeedURL(nil) - } - return - } - await MainActor.run { - appcastURL = URL( - string: "https://github.com/CodeEditApp/CodeEdit/releases/download/\(result.tagName)/appcast.xml" - )! - self.updater?.setFeedURL(appcastURL) - } - } - - override init() { - super.init() - updater = SPUStandardUpdaterController( - startingUpdater: true, - updaterDelegate: self, - userDriverDelegate: nil - ).updater - - feedURLTask = Task { - await setFeedURL() - } - - automaticallyChecksForUpdatesObservation = updater?.observe( - \.automaticallyChecksForUpdates, - options: [.initial, .new, .old], - changeHandler: { [unowned self] updater, change in - guard change.newValue != change.oldValue else { return } - self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates - } - ) - - lastUpdateCheckDateObservation = updater?.observe( - \.lastUpdateCheckDate, - options: [.initial, .new, .old], - changeHandler: { [unowned self] updater, _ in - self.lastUpdateCheckDate = updater.lastUpdateCheckDate - } - ) - - includePrereleaseVersions = UserDefaults.standard.bool(forKey: "includePrereleaseVersions") - } - - deinit { - feedURLTask?.cancel() - } - - func allowedChannels(for updater: SPUUpdater) -> Set { - // TODO: Uncomment when production build is released. - // if includePrereleaseVersions { - return ["dev"] - // } - // return [] - } - - func checkForUpdates() { - updater?.checkForUpdates() - } - - private struct GHAPIResult: Codable { - enum CodingKeys: String, CodingKey { - case tagName = "tag_name" - } - - var tagName: String - } -} diff --git a/CodeEdit/Features/Settings/Views/ExternalLink.swift b/CodeEdit/Features/Settings/Views/ExternalLink.swift deleted file mode 100644 index 9995df5537..0000000000 --- a/CodeEdit/Features/Settings/Views/ExternalLink.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// ExternalLink.swift -// CodeEdit -// -// Created by Austin Condiff on 4/20/23. -// - -import SwiftUI - -// Usage 1: ExternalLink with title -// ExternalLink("Title", destination: URL(string: "https://apple.com")!) -// -// Usage 2: ExternalLink with title and subtitle but no icon -// ExternalLink(destination: URL(string: "https://apple.com")!) { -// Text("Title") -// Text("Subtitle") -// } icon: { -// Image(systemName: "star") -// } -// -// Usage 3: ExternalLink with title, subtitle and icon -// ExternalLink(destination: URL(string: "https://apple.com")!) { -// Text("Title") -// Text("Subtitle") -// } icon: { -// Image(systemName: "star") -// } - -struct ExternalLink: View { - let title: String? - let subtitle: String? - let showInFinder: Bool - let destination: URL - let icon: (() -> Icon)? - let content: () -> Content - - init( - _ title: String, - showInFinder: Bool = false, - destination: URL, - subtitle: String? = nil, - icon: (() -> Icon)? = nil - ) where Content == EmptyView, Icon == EmptyView { - self.title = title - self.showInFinder = showInFinder - self.subtitle = subtitle - self.destination = destination - self.icon = icon - self.content = { EmptyView() } - } - - init( - showInFinder: Bool = false, - destination: URL, - @ViewBuilder content: @escaping () -> Content, - title: String? = nil, - subtitle: String? = nil, - @ViewBuilder icon: @escaping() -> Icon = { EmptyView() } - ) { - self.showInFinder = showInFinder - self.title = title - self.subtitle = subtitle - self.destination = destination - self.icon = icon - self.content = content - } - - var body: some View { - Button(action: { - if showInFinder { - NSWorkspace.shared.activateFileViewerSelecting([destination]) - } else { - NSWorkspace.shared.open(destination) - } - }, label: { - HStack(spacing: 8) { - icon?() - VStack(alignment: .leading, spacing: 2) { - if let title = title { - Text(title) - } - if let subtitle = subtitle { - Text(subtitle) - .font(.caption) - .foregroundColor(Color(.secondaryLabelColor)) - } - content() - } - Spacer() - Image(systemName: "arrow.up.right") - .font(.system(size: 12, weight: .semibold)) - .foregroundColor(Color(.tertiaryLabelColor)) - } - }) - .buttonStyle(ExternalLinkButtonStyle()) - } -} - -struct ExternalLinkButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(10) - .background(configuration.isPressed ? Color(.separatorColor) : Color(.clear)) - .contentShape(Rectangle()) - .padding(-10) - } -} diff --git a/CodeEdit/Features/Settings/Views/MonospacedFontPicker.swift b/CodeEdit/Features/Settings/Views/MonospacedFontPicker.swift deleted file mode 100644 index 3c294a09cb..0000000000 --- a/CodeEdit/Features/Settings/Views/MonospacedFontPicker.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// MonospacedFontPicker.swift -// CodeEdit -// -// Created by Austin Condiff on 4/22/23. -// - -import SwiftUI - -struct MonospacedFontPicker: View { - var title: String - @Binding var selectedFontName: String - @State private var recentFonts: [String] - @State private var monospacedFontFamilyNames: [String] = [] - @State private var otherFontFamilyNames: [String] = [] - - init(title: String, selectedFontName: Binding) { - self.title = title - self._selectedFontName = selectedFontName - self.recentFonts = UserDefaults.standard.stringArray(forKey: "recentFonts") ?? [] - } - - var body: some View { - Picker(selection: $selectedFontName, label: Text(title)) { - Text("System Font") - .font(Font(NSFont.monospacedSystemFont(ofSize: 13.5, weight: .medium))) - .tag("SF Mono") - - if !recentFonts.isEmpty { - Divider() - ForEach(recentFonts, id: \.self) { fontFamilyName in - Text(fontFamilyName) - .font(.custom(fontFamilyName, size: 13.5)) - .tag(fontFamilyName) // to prevent picker invalid and does not have an associated tag error. - } - } - - if !monospacedFontFamilyNames.isEmpty { - Divider() - ForEach(monospacedFontFamilyNames, id: \.self) { fontFamilyName in - Text(fontFamilyName) - .font(.custom(fontFamilyName, size: 13.5)) - .tag(fontFamilyName) - } - } - - if !otherFontFamilyNames.isEmpty { - Divider() - Menu { - ForEach(otherFontFamilyNames, id: \.self) { fontFamilyName in - Button { - pushIntoRecentFonts(fontFamilyName) - selectedFontName = fontFamilyName - } label: { - Text(fontFamilyName) - .font(.custom(fontFamilyName, size: 13.5)) - } - .tag(fontFamilyName) - } - } label: { - Text("Other fonts...") - } - } - } - .onChange(of: selectedFontName) { _ in - if selectedFontName != "SF Mono" { - pushIntoRecentFonts(selectedFontName) - - // remove the font to prevent ForEach conflict - monospacedFontFamilyNames.removeAll { $0 == selectedFontName } - otherFontFamilyNames.removeAll { $0 == selectedFontName } - } - } - .task { - await getFonts() - } - } -} - -extension MonospacedFontPicker { - private func pushIntoRecentFonts(_ newItem: String) { - recentFonts.removeAll(where: { $0 == newItem }) - recentFonts.insert(newItem, at: 0) - if recentFonts.count > 3 { - recentFonts.removeLast() - } - UserDefaults.standard.set(recentFonts, forKey: "recentFonts") - } - - private func getFonts() async { - await withTaskGroup(of: Void.self) { group in - group.addTask { - let monospacedFontFamilyNames = getMonospacedFamilyNames() - await MainActor.run { - self.monospacedFontFamilyNames = monospacedFontFamilyNames - } - } - - group.addTask { - let otherFontFamilyNames = getOtherFontFamilyNames() - await MainActor.run { - self.otherFontFamilyNames = otherFontFamilyNames - } - } - } - } - - private func getMonospacedFamilyNames() -> [String] { - let availableFontFamilies = NSFontManager.shared.availableFontFamilies - - return availableFontFamilies.filter { fontFamilyName in - // exclude the font if it is in recentFonts to prevent ForEach conflict - if recentFonts.contains(fontFamilyName) { - return false - } - - // exclude default font - if fontFamilyName == "SF Mono" { - return false - } - - // include the font which is fixedPitch - // include the font which numberOfGlyphs is greater than 26 - if let font = NSFont(name: fontFamilyName, size: 14) { - return font.isFixedPitch && font.numberOfGlyphs > 26 - } else { - return false - } - } - } - - private func getOtherFontFamilyNames() -> [String] { - let availableFontFamilies = NSFontManager.shared.availableFontFamilies - - return availableFontFamilies.filter { fontFamilyName in - // exclude the font if it is in recentFonts to prevent ForEach conflict - if recentFonts.contains(fontFamilyName) { - return false - } - - // include the font which is NOT fixedPitch - // include the font which numberOfGlyphs is greater than 26 - if let font = NSFont(name: fontFamilyName, size: 14) { - return !font.isFixedPitch && font.numberOfGlyphs > 26 - } else { - return false - } - } - } -} diff --git a/CodeEdit/Features/Settings/Views/SettingsColorPicker.swift b/CodeEdit/Features/Settings/Views/SettingsColorPicker.swift deleted file mode 100644 index 13b751f6a8..0000000000 --- a/CodeEdit/Features/Settings/Views/SettingsColorPicker.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// SettingsColorPicker.swift -// CodeEdit -// -// Created by Austin Condiff on 4/3/23. -// - -import SwiftUI - -struct SettingsColorPicker: View { - - /// Color modified elsewhere in user theme - @Binding var color: Color - - /// Component private color to display - /// UI changes - @State private var selectedColor: Color - - private let label: String - - init(_ label: String, color: Binding) { - self._color = color - self.label = label - self._selectedColor = State(initialValue: color.wrappedValue) - } - - var body: some View { - LabeledContent(label) { - ColorPicker(selection: $selectedColor, supportsOpacity: false) { } - .labelsHidden() - } - .onChange(of: selectedColor) { newValue in - color = newValue - } - } -} diff --git a/CodeEdit/Features/Settings/Views/SettingsForm.swift b/CodeEdit/Features/Settings/Views/SettingsForm.swift deleted file mode 100644 index 4701d1ac0e..0000000000 --- a/CodeEdit/Features/Settings/Views/SettingsForm.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// SettingsForm.swift -// CodeEdit -// -// Created by Austin Condiff on 4/8/23. -// - -import SwiftUI -import Introspect - -struct SettingsForm: View { - @Environment(\.colorScheme) - private var colorScheme - @Environment(\.controlActiveState) - private var activeState - @EnvironmentObject var model: SettingsViewModel - @ViewBuilder var content: Content - - var body: some View { - Form { - Section { - EmptyView() - } footer: { - Rectangle() - .frame(height: 0) - .background( - GeometryReader { - Color.clear.preference( - key: ViewOffsetKey.self, - value: -$0.frame(in: .named("scroll")).origin.y - ) - } - ) - .onPreferenceChange(ViewOffsetKey.self) { - if $0 <= -20.0 && !model.scrolledToTop { - withAnimation { - model.scrolledToTop = true - } - } else if $0 > -20.0 && model.scrolledToTop { - withAnimation { - model.scrolledToTop = false - } - } - } - } - content - } - .introspectScrollView { scrollView in - scrollView.scrollerInsets.top = 50 - } - .formStyle(.grouped) - .coordinateSpace(name: "scroll") - .safeAreaInset(edge: .top, spacing: -50) { - EffectView(.menu) - .opacity(!model.scrolledToTop ? 1 : 0) - .transaction { transaction in - transaction.animation = nil - } - .overlay(alignment: .bottom) { - LinearGradient( - gradient: Gradient( - colors: [.black.opacity(colorScheme == .dark ? 1 : 0.17), .black.opacity(0)] - ), - startPoint: .top, - endPoint: .bottom - ) - .frame(height: colorScheme == .dark || activeState == .inactive ? 1 : 2) - .padding(.bottom, colorScheme == .dark || activeState == .inactive ? -1 : -2) - .opacity(!model.scrolledToTop ? 1 : 0) - .transition(.opacity) - } - .ignoresSafeArea() - .frame(height: 0) - } - } -} - -struct ViewOffsetKey: PreferenceKey { - typealias Value = CGFloat - static var defaultValue = CGFloat.zero - static func reduce(value: inout Value, nextValue: () -> Value) { - value += nextValue() - } -} diff --git a/CodeEdit/Features/Settings/Views/SettingsPageView.swift b/CodeEdit/Features/Settings/Views/SettingsPageView.swift deleted file mode 100644 index a8f2fa011f..0000000000 --- a/CodeEdit/Features/Settings/Views/SettingsPageView.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// SettingPageView.swift -// CodeEdit -// -// Created by Austin Condiff on 3/31/23. -// - -import SwiftUI - -struct SettingsPageView: View { - var page: SettingsPage - var searchText: String - - init(_ page: SettingsPage, searchText: String) { - self.page = page - self.searchText = searchText - } - - var body: some View { - NavigationLink(value: page) { - Label { - page.name.rawValue.highlightOccurrences(self.searchText) - .padding(.leading, 2) - } icon: { - Group { - switch page.icon { - case .system(let name): - Image(systemName: name) - .resizable() - .aspectRatio(contentMode: .fit) - case .symbol(let name): - Image(symbol: name) - .resizable() - .aspectRatio(contentMode: .fit) - case .asset(let name): - Image(name) - .resizable() - .aspectRatio(contentMode: .fit) - case .none: EmptyView() - } - } - .shadow(color: Color(NSColor.black).opacity(0.25), radius: 0.5, y: 0.5) - .padding(2.5) - .foregroundColor(.white) - .frame(width: 20, height: 20) - .background( - RoundedRectangle( - cornerRadius: 5, - style: .continuous - ) - .fill((page.baseColor ?? .white).gradient) - .shadow(color: Color(NSColor.black).opacity(0.25), radius: 0.5, y: 0.5) - ) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Views/View+ConstrainHeightToWindow.swift b/CodeEdit/Features/Settings/Views/View+ConstrainHeightToWindow.swift deleted file mode 100644 index 566decd3d9..0000000000 --- a/CodeEdit/Features/Settings/Views/View+ConstrainHeightToWindow.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// View+ConstrainHeightToWindow.swift -// CodeEdit -// -// Created by Austin Condiff on 4/3/23. -// - -import SwiftUI - -extension NSWindow { - var isSettingsWindow: Bool { - self.identifier?.rawValue == SceneID.settings.rawValue - } -} - -extension NSApplication { - var settingsWindow: NSWindow? { - NSApp.windows.first { $0.isSettingsWindow } - } -} - -extension View { - func constrainHeightToWindow() -> some View { - modifier(ConstrainHeightToWindowViewModifier()) - } -} - -struct ConstrainHeightToWindowViewModifier: ViewModifier { - @State var height: CGFloat = 100 - - func body(content: Content) -> some View { - content - .frame(height: height-100) - .onReceive(NSApp.settingsWindow!.publisher(for: \.frame)) { newValue in - height = newValue.height - } - } -} diff --git a/CodeEdit/Features/Settings/Views/View+HideSidebarToggle.swift b/CodeEdit/Features/Settings/Views/View+HideSidebarToggle.swift deleted file mode 100644 index 09b65c858b..0000000000 --- a/CodeEdit/Features/Settings/Views/View+HideSidebarToggle.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// View+HideSidebarToggle.swift -// CodeEdit -// -// Created by Austin Condiff on 4/5/23. -// - -import SwiftUI - -extension View { - func hideSidebarToggle() -> some View { - modifier(HideSidebarToggleViewModifier()) - } -} - -struct HideSidebarToggleViewModifier: ViewModifier { - func body(content: Content) -> some View { - content - .task { - let window = NSApp.windows.first { $0.identifier?.rawValue == SceneID.settings.rawValue }! - let sidebaritem = "com.apple.SwiftUI.navigationSplitView.toggleSidebar" - let index = window.toolbar?.items.firstIndex { $0.itemIdentifier.rawValue == sidebaritem } - if let index { - window.toolbar?.removeItem(at: index) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift b/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift deleted file mode 100644 index f778e0e791..0000000000 --- a/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// View+NavigationBarBackButtonVisible.swift -// CodeEdit -// -// Created by Austin Condiff on 4/8/23. -// - -import SwiftUI - -struct NavigationBarBackButtonVisible: ViewModifier { - @Environment(\.presentationMode) - var presentationMode - @EnvironmentObject var model: SettingsViewModel - - func body(content: Content) -> some View { - content - .toolbar { - ToolbarItem(placement: .navigation) { - Button { - print(self.presentationMode.wrappedValue) - self.presentationMode.wrappedValue.dismiss() - } label: { - Image(systemName: "chevron.left") - } - } - } - .hideSidebarToggle() - .onAppear { - model.backButtonVisible = true - } - } -} - -extension View { - func navigationBarBackButtonVisible() -> some View { - modifier(NavigationBarBackButtonVisible()) - } -} diff --git a/CodeEdit/Features/SplitView/Model/Environment+ContentInsets.swift b/CodeEdit/Features/SplitView/Model/Environment+ContentInsets.swift deleted file mode 100644 index 0c8f577f64..0000000000 --- a/CodeEdit/Features/SplitView/Model/Environment+ContentInsets.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Environment+ContentInsets.swift -// CodeEdit -// -// Created by Wouter Hennen on 24/02/2023. -// - -import SwiftUI - -struct EdgeInsetsEnvironmentKey: EnvironmentKey { - static var defaultValue: EdgeInsets = EdgeInsets(top: 1, leading: 0, bottom: 0, trailing: 0) -} - -extension EnvironmentValues { - var edgeInsets: EdgeInsetsEnvironmentKey.Value { - get { self[EdgeInsetsEnvironmentKey.self] } - set { self[EdgeInsetsEnvironmentKey.self] = newValue } - } -} - -extension EdgeInsets { - var nsEdgeInsets: NSEdgeInsets { - .init(top: top, left: leading, bottom: bottom, right: trailing) - } -} diff --git a/CodeEdit/Features/SplitView/Model/Environment+SplitEditor.swift b/CodeEdit/Features/SplitView/Model/Environment+SplitEditor.swift deleted file mode 100644 index 1fd3eb14ac..0000000000 --- a/CodeEdit/Features/SplitView/Model/Environment+SplitEditor.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Environment+SplitEditor.swift -// CodeEdit -// -// Created by Wouter Hennen on 16/02/2023. -// - -import SwiftUI - -struct SplitEditorEnvironmentKey: EnvironmentKey { - static var defaultValue: (Edge, Editor) -> Void = { _, _ in } -} - -extension EnvironmentValues { - var splitEditor: SplitEditorEnvironmentKey.Value { - get { self[SplitEditorEnvironmentKey.self] } - set { self[SplitEditorEnvironmentKey.self] = newValue } - } -} diff --git a/CodeEdit/Features/SplitView/Model/SplitViewData.swift b/CodeEdit/Features/SplitView/Model/SplitViewData.swift deleted file mode 100644 index a874085764..0000000000 --- a/CodeEdit/Features/SplitView/Model/SplitViewData.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// SplitViewData.swift -// CodeEdit -// -// Created by Wouter Hennen on 16/02/2023. -// - -import SwiftUI - -final class SplitViewData: ObservableObject { - @Published var editorLayouts: [EditorLayout] - - var axis: Axis - - init(_ axis: Axis, editorLayouts: [EditorLayout] = []) { - self.editorLayouts = editorLayouts - self.axis = axis - - editorLayouts.forEach { - if case .one(let editor) = $0 { - editor.parent = self - } - } - } - - /// Splits the editor at a certain index into two separate editors. - /// - Parameters: - /// - direction: direction in which the editor will be split. - /// If the direction is the same as the ancestor direction, - /// the editor is added to the ancestor instead of creating a new split container. - /// - index: index where the divider will be added. - /// - editor: new editor class that will be used for the editor. - func split(_ direction: Edge, at index: Int, new editor: Editor) { - editor.parent = self - switch (axis, direction) { - case (.horizontal, .trailing), (.vertical, .bottom): - editorLayouts.insert(.one(editor), at: index+1) - - case (.horizontal, .leading), (.vertical, .top): - editorLayouts.insert(.one(editor), at: index) - - case (.horizontal, .top): - editorLayouts[index] = .vertical(.init(.vertical, editorLayouts: [.one(editor), editorLayouts[index]])) - - case (.horizontal, .bottom): - editorLayouts[index] = .vertical(.init(.vertical, editorLayouts: [editorLayouts[index], .one(editor)])) - - case (.vertical, .leading): - editorLayouts[index] = .horizontal(.init(.horizontal, editorLayouts: [.one(editor), editorLayouts[index]])) - - case (.vertical, .trailing): - editorLayouts[index] = .horizontal(.init(.horizontal, editorLayouts: [editorLayouts[index], .one(editor)])) - } - } - - /// Closes an Editor. - /// - Parameter id: ID of the Editor. - func closeEditor(with id: Editor.ID) { - editorLayouts.removeAll { editorLayout in - if case .one(let editor) = editorLayout { - if editor.id == id { - return true - } - } - - return false - } - } - - func getEditorLayout(with id: Editor.ID) -> EditorLayout? { - for editorLayout in editorLayouts { - if case .one(let editor) = editorLayout { - if editor.id == id { - return editorLayout - } - } - } - - return nil - } - - /// Flattens the splitviews. - func flatten() { - for index in editorLayouts.indices { - editorLayouts[index].flatten(parent: self) - } - } - - /// Gets flattened splitviews. - func getFlattened() -> [Editor] { - var arr: [Editor] = [] - for index in editorLayouts.indices { - arr += editorLayouts[index].getFlattened(parent: self) - } - return arr - } -} diff --git a/CodeEdit/Features/SplitView/Model/SplitViewItem.swift b/CodeEdit/Features/SplitView/Model/SplitViewItem.swift deleted file mode 100644 index 4229bf5e4f..0000000000 --- a/CodeEdit/Features/SplitView/Model/SplitViewItem.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SplitViewItem.swift -// CodeEdit -// -// Created by Wouter Hennen on 05/03/2023. -// - -import SwiftUI -import Combine - -class SplitViewItem: ObservableObject { - - var id: AnyHashable - var item: NSSplitViewItem - - var collapsed: Binding - - var cancellables: [AnyCancellable] = [] - - var observers: [NSKeyValueObservation] = [] - - init(child: _VariadicView.Children.Element) { - self.id = child.id - self.item = NSSplitViewItem(viewController: NSHostingController(rootView: child)) - self.collapsed = child[SplitViewItemCollapsedViewTraitKey.self] - self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] - self.item.isCollapsed = self.collapsed.wrappedValue - self.item.holdingPriority = child[SplitViewHoldingPriorityTraitKey.self] - // Skip the initial observation via a dispatch to avoid a "updating during view update" error - DispatchQueue.main.async { - self.observers = self.createObservers() - } - } - - private func createObservers() -> [NSKeyValueObservation] { - [ - item.observe(\.isCollapsed) { [weak self] item, _ in - self?.collapsed.wrappedValue = item.isCollapsed - } - ] - } - - /// Updates a SplitViewItem. - /// This will fetch updated binding values and update them if needed. - /// - Parameter child: the view corresponding to the SplitViewItem. - func update(child: _VariadicView.Children.Element) { - self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] - DispatchQueue.main.async { - self.observers = [] - self.item.animator().isCollapsed = child[SplitViewItemCollapsedViewTraitKey.self].wrappedValue - self.item.holdingPriority = child[SplitViewHoldingPriorityTraitKey.self] - self.observers = self.createObservers() - } - } -} diff --git a/CodeEdit/Features/SplitView/Views/SplitView.swift b/CodeEdit/Features/SplitView/Views/SplitView.swift deleted file mode 100644 index e16d45e421..0000000000 --- a/CodeEdit/Features/SplitView/Views/SplitView.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// SplitView.swift -// CodeEdit -// -// Created by Wouter Hennen on 22/02/2023. -// - -import SwiftUI - -struct SplitView: View { - var axis: Axis - var content: Content - - init(axis: Axis, @ViewBuilder content: () -> Content) { - self.axis = axis - self.content = content() - } - - @State var viewController: () -> SplitViewController? = { nil } - - var body: some View { - VStack { - content.variadic { children in - SplitViewControllerView(axis: axis, children: children, viewController: $viewController) - } - } - ._trait(SplitViewControllerLayoutValueKey.self, viewController) - } -} diff --git a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift deleted file mode 100644 index 66b8b157df..0000000000 --- a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// SplitViewControllerView.swift -// CodeEdit -// -// Created by Wouter Hennen on 20/02/2023. -// - -import SwiftUI - -struct SplitViewControllerView: NSViewControllerRepresentable { - - var axis: Axis - var children: _VariadicView.Children - @Binding var viewController: () -> SplitViewController? - - func makeNSViewController(context: Context) -> SplitViewController { - context.coordinator - } - - func updateNSViewController(_ controller: SplitViewController, context: Context) { - updateItems(controller: controller) - } - - private func updateItems(controller: SplitViewController) { - var hasChanged = false - // Reorder viewcontrollers if needed and add new ones. - controller.items = children.map { child in - let item: SplitViewItem - if let foundItem = controller.items.first(where: { $0.id == child.id }) { - item = foundItem - item.update(child: child) - } else { - hasChanged = true - item = SplitViewItem(child: child) - } - return item - } - - controller.splitViewItems = controller.items.map(\.item) - - if hasChanged && controller.splitViewItems.count > 1 { - let splitView = controller.splitView - let numerator = splitView.isVertical ? splitView.frame.width : splitView.frame.height - - for idx in 0.. SplitViewController { - SplitViewController(parent: self, axis: axis) - } -} - -final class SplitViewController: NSSplitViewController { - - var items: [SplitViewItem] = [] - var axis: Axis - var parentView: SplitViewControllerView - - init(parent: SplitViewControllerView, axis: Axis = .horizontal) { - self.axis = axis - self.parentView = parent - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - splitView.isVertical = axis != .vertical - splitView.dividerStyle = .thin - DispatchQueue.main.async { [weak self] in - self?.parentView.viewController = { [weak self] in - self - } - } - } - - override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool { - false - } - - func collapse(for id: AnyHashable, enabled: Bool) { - items.first { $0.id == id }?.item.animator().isCollapsed = enabled - } -} diff --git a/CodeEdit/Features/SplitView/Views/SplitViewModifiers.swift b/CodeEdit/Features/SplitView/Views/SplitViewModifiers.swift deleted file mode 100644 index 3df0c7828b..0000000000 --- a/CodeEdit/Features/SplitView/Views/SplitViewModifiers.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// SplitViewModifiers.swift -// CodeEdit -// -// Created by Wouter Hennen on 05/03/2023. -// - -import SwiftUI - -struct SplitViewControllerLayoutValueKey: _ViewTraitKey { - static var defaultValue: () -> SplitViewController? = { nil } -} - -struct SplitViewItemCollapsedViewTraitKey: _ViewTraitKey { - static var defaultValue: Binding = .constant(false) -} - -struct SplitViewItemCanCollapseViewTraitKey: _ViewTraitKey { - static var defaultValue: Bool = false -} - -struct SplitViewHoldingPriorityTraitKey: _ViewTraitKey { - static var defaultValue: NSLayoutConstraint.Priority = .defaultLow -} - -extension View { - func collapsed(_ value: Binding) -> some View { - self - // Use get/set instead of binding directly, so a view update will be triggered if the binding changes. - ._trait(SplitViewItemCollapsedViewTraitKey.self, .init { - value.wrappedValue - } set: { - value.wrappedValue = $0 - }) - } - - func collapsable() -> some View { - self - ._trait(SplitViewItemCanCollapseViewTraitKey.self, true) - } - - func holdingPriority(_ priority: NSLayoutConstraint.Priority) -> some View { - self - ._trait(SplitViewHoldingPriorityTraitKey.self, priority) - } -} diff --git a/CodeEdit/Features/SplitView/Views/SplitViewReader.swift b/CodeEdit/Features/SplitView/Views/SplitViewReader.swift deleted file mode 100644 index e909edc176..0000000000 --- a/CodeEdit/Features/SplitView/Views/SplitViewReader.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// SplitViewReader.swift -// CodeEdit -// -// Created by Wouter Hennen on 05/03/2023. -// - -import SwiftUI - -struct SplitViewReader: View { - - @ViewBuilder var content: (SplitViewProxy) -> Content - - @State private var viewController: () -> SplitViewController? = { nil } - - private var proxy: SplitViewProxy { - .init(viewController: viewController) - } - - var body: some View { - content(proxy) - .variadic { children in - ForEach(children, id: \.id) { child in - child - .task(id: child[SplitViewControllerLayoutValueKey.self]()) { - viewController = child[SplitViewControllerLayoutValueKey.self] - } - } - } - } -} - -struct SplitViewProxy { - private var viewController: () -> SplitViewController? - - fileprivate init(viewController: @escaping () -> SplitViewController?) { - self.viewController = viewController - } - - /// Set the position of a divider in a splitview. - /// - Parameters: - /// - index: index of the divider. The mostleft / top divider has index 0. - /// - position: position to place the divider. This is a position inside the views width / height. - /// For example, if the splitview has a width of 500, setting the position to 250 - /// will put the divider in the middle of the splitview. - func setPosition(of index: Int, position: CGFloat) { - viewController()?.splitView.setPosition(position, ofDividerAt: index) - } - - /// Collapse a view of the splitview. - /// - Parameters: - /// - id: ID of the view - /// - enabled: true for collapse. - func collapseView(with id: AnyHashable, _ enabled: Bool) { - viewController()?.collapse(for: id, enabled: enabled) - } -} diff --git a/CodeEdit/Features/SplitView/Views/Variadic.swift b/CodeEdit/Features/SplitView/Views/Variadic.swift deleted file mode 100644 index 0253b4f9e1..0000000000 --- a/CodeEdit/Features/SplitView/Views/Variadic.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Variadic.swift -// CodeEdit -// -// Created by Wouter Hennen on 05/03/2023. -// - -import SwiftUI - -struct Helper: _VariadicView_UnaryViewRoot { - // swiftlint:disable:next identifier_name - var _body: (_VariadicView.Children) -> Result - - func body(children: _VariadicView.Children) -> some View { - _body(children) - } -} - -extension View { - - /// Exposes the children of a ViewBuilder so they can be accessed individually. - func variadic(@ViewBuilder process: @escaping (_VariadicView.Children) -> R) -> some View { - _VariadicView.Tree(Helper(_body: process), content: { self }) - } -} diff --git a/CodeEdit/Features/StatusBar/Models/CursorLocation.swift b/CodeEdit/Features/StatusBar/Models/CursorLocation.swift deleted file mode 100644 index a3f7a764f1..0000000000 --- a/CodeEdit/Features/StatusBar/Models/CursorLocation.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// CursorLocation.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 11.05.22. -// - -import Foundation - -/// The location (line, column) of the cursor in the editor view -/// -/// - Note: Not yet implemented -struct CursorLocation { - /// The current line the cursor is located at. - var line: Int - /// The current column the cursor is located at. - var column: Int -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarIcon.swift b/CodeEdit/Features/StatusBar/Views/StatusBarIcon.swift deleted file mode 100644 index 0df163ba9e..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarIcon.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// StatusBarIcon.swift -// CodeEdit -// -// Created by Austin Condiff on 3/23/23. -// - -import SwiftUI - -/// Accessory icon view for status bar. -struct StatusBarIcon: View { - /// Unifies icon font for status bar accessories. - private let iconFont: Font - - enum IconSize: CGFloat { - case small = 11 - case medium = 14.5 - } - - private let icon: Image - private let active: Bool - private let action: () -> Void - - init(icon: Image, size: IconSize = .medium, active: Bool? = false, action: @escaping () -> Void) { - self.icon = icon - self.action = action - self.active = active ?? false - self.iconFont = Font.system(size: size.rawValue, weight: .regular, design: .default) - } - - var body: some View { - Button( - action: action, - label: { - icon - .font(iconFont) - .contentShape(Rectangle()) - } - ) - .buttonStyle(StatusBarIconButtonStyle(isActive: active)) - } -} - -struct StatusBarIconButtonStyle: ButtonStyle { - var isActive: Bool = false - func makeBody(configuration: Self.Configuration) -> some View { - configuration.label - .foregroundColor(isActive ? Color.accentColor : Color.secondary) - .brightness(configuration.isPressed ? 0.5 : 0) - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarBreakpointButton.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarBreakpointButton.swift deleted file mode 100644 index c331b6ddf8..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarBreakpointButton.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// StatusBarBreakpointButton.swift -// CodeEditModules/StatusBar -// -// Created by Stef Kors on 14/04/2022. -// - -import SwiftUI -import CodeEditSymbols - -struct StatusBarBreakpointButton: View { - @EnvironmentObject private var model: UtilityAreaViewModel - - var body: some View { - Button { - model.isBreakpointEnabled.toggle() - } label: { - if model.isBreakpointEnabled { - Image.breakpointFill - .foregroundColor(.accentColor) - } else { - Image.breakpoint - .foregroundColor(.secondary) - } - } - .buttonStyle(.plain) - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorLocationLabel.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorLocationLabel.swift deleted file mode 100644 index af4ee347e7..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorLocationLabel.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// StatusBarCursorLocationLabel.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI -import CodeEditSourceEditor - -struct StatusBarCursorLocationLabel: View { - @Environment(\.controlActiveState) - private var controlActive - @Environment(\.modifierKeys) - private var modifierKeys - - @EnvironmentObject private var model: UtilityAreaViewModel - @EnvironmentObject private var editorManager: EditorManager - - @State private var tab: EditorInstance? - @State private var cursorPositions: [CursorPosition]? - - /// Updates the source of cursor position notifications. - func updateSource() { - tab = editorManager.activeEditor.selectedTab - } - - /// Finds the lines contained by a range in the currently selected document. - /// - Parameter range: The range to query. - /// - Returns: The number of lines in the range. - func getLines(_ range: NSRange) -> Int { - return tab?.rangeTranslator?.linesInRange(range) ?? 0 - } - - /// Create a label string for cursor positions. - /// - Parameter cursorPositions: The cursor positions to create the label for. - /// - Returns: A string describing the user's location in a document. - func getLabel(_ cursorPositions: [CursorPosition]) -> String { - if cursorPositions.isEmpty { - return "" - } - - // More than one selection, display the number of selections. - if cursorPositions.count > 1 { - return "\(cursorPositions.count) selected ranges" - } - - // If the selection is more than just a cursor, return the length. - if cursorPositions[0].range.length > 0 { - // When the option key is pressed display the character range. - if modifierKeys.contains(.option) { - return "Char: \(cursorPositions[0].range.location) Len: \(cursorPositions[0].range.length)" - } - - let lineCount = getLines(cursorPositions[0].range) - - if lineCount > 1 { - return "\(lineCount) lines" - } - - return "\(cursorPositions[0].range.length) characters" - } - - // When the option key is pressed display the character offset. - if modifierKeys.contains(.option) { - return "Char: \(cursorPositions[0].range.location) Len: 0" - } - - // When there's a single cursor, display the line and column. - return "Line: \(cursorPositions[0].line) Col: \(cursorPositions[0].column)" - } - - var body: some View { - Group { - if let currentTab = tab { - Group { - if let cursorPositions = cursorPositions { - Text(getLabel(cursorPositions)) - } else { - EmptyView() - } - } - .onReceive(currentTab.cursorPositions) { val in - cursorPositions = val - } - } else { - EmptyView() - } - } - .font(model.toolbarFont) - .foregroundColor(foregroundColor) - .fixedSize() - .lineLimit(1) - .onHover { isHovering($0) } - .onAppear { - updateSource() - } - .onReceive(editorManager.activeEditor.objectWillChange) { _ in - updateSource() - } - .onChange(of: editorManager.activeEditor) { _ in - updateSource() - } - .onChange(of: editorManager.activeEditor.selectedTab) { _ in - updateSource() - } - } - - private var foregroundColor: Color { - controlActive == .inactive ? Color(nsColor: .disabledControlTextColor) : Color(nsColor: .secondaryLabelColor) - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarEncodingSelector.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarEncodingSelector.swift deleted file mode 100644 index c64212461f..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarEncodingSelector.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// StatusBarEncodingSelector.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI - -struct StatusBarEncodingSelector: View { - - var body: some View { - Menu { - // UTF 8, ASCII, ... - } label: { - Text("UTF 8") - } - .menuStyle(StatusBarMenuStyle()) - .onHover { isHovering($0) } - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarIndentSelector.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarIndentSelector.swift deleted file mode 100644 index fdae627cfd..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarIndentSelector.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// StatusBarIndentSelector.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI - -struct StatusBarIndentSelector: View { - @AppSettings(\.textEditing.defaultTabWidth) - var defaultTabWidth - - var body: some View { - Menu { - Button {} label: { - Text("Use Tabs") - }.disabled(true) - - Button {} label: { - Text("Use Spaces") - }.disabled(true) - - Divider() - - Picker("Tab Width", selection: $defaultTabWidth) { - ForEach(2..<9) { index in - Text("\(index) Spaces") - .tag(index) - } - } - } label: { - Text("\(defaultTabWidth) Spaces") - } - .menuStyle(StatusBarMenuStyle()) - .onHover { isHovering($0) } - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarLineEndSelector.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarLineEndSelector.swift deleted file mode 100644 index bedcf1bb8a..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarLineEndSelector.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// StatusBarLineEndSelector.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI - -struct StatusBarLineEndSelector: View { - - var body: some View { - Menu { - // LF, CRLF - } label: { - Text("LF") - } - .menuStyle(StatusBarMenuStyle()) - .onHover { isHovering($0) } - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarMenuStyle.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarMenuStyle.swift deleted file mode 100644 index 32e484964d..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarMenuStyle.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// StatusBarMenuStyle.swift -// CodeEdit -// -// Created by Austin Condiff on 3/24/23 -// - -import SwiftUI -import CodeEditSymbols - -struct StatusBarMenuStyle: MenuStyle { - @Environment(\.controlActiveState) - private var controlActive - - @Environment(\.colorScheme) - private var colorScheme - - func makeBody(configuration: Configuration) -> some View { - Menu(configuration) - .controlSize(.small) - .menuStyle(.borderlessButton) - .opacity(controlActive == .inactive - ? colorScheme == .dark ? 0.66 : 1 - : colorScheme == .dark ? 0.54 : 0.72) - .fixedSize() - } -} - -extension MenuStyle where Self == StatusBarMenuStyle { - static var statusBar: StatusBarMenuStyle { .init() } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift deleted file mode 100644 index f88d89a13b..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// StatusBarToggleUtilityAreaButton.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI - -internal struct StatusBarToggleUtilityAreaButton: View { - @Environment(\.controlActiveState) - var controlActiveState - - @EnvironmentObject private var model: UtilityAreaViewModel - - internal var body: some View { - Button { - model.togglePanel() - } label: { - Image(systemName: "square.bottomthird.inset.filled") - } - .buttonStyle(.icon) - .keyboardShortcut("Y", modifiers: [.command, .shift]) - .help(model.isCollapsed ? "Show the Utility area" : "Hide the Utility area") - .onHover { isHovering($0) } - .onChange(of: controlActiveState) { newValue in - if newValue == .key { - CommandManager.shared.addCommand( - name: "Toggle Utility Area", - title: "Toggle Utility Area", - id: "open.drawer", - command: CommandClosureWrapper.init(closure: model.togglePanel) - ) - } - } - .onAppear { - CommandManager.shared.addCommand( - name: "Toggle Utility Area", - title: "Toggle Utility Area", - id: "open.drawer", - command: CommandClosureWrapper.init(closure: model.togglePanel) - ) - } - } -} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift deleted file mode 100644 index db703e1ad3..0000000000 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// StatusBarView.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 19.03.22. -// - -import SwiftUI - -/// # StatusBarView -/// -/// A View that lives on the bottom of the window and offers information -/// about compilation errors/warnings, git, cursor position in text, -/// indentation width (in spaces), text encoding and linebreak -/// -/// Additionally it offers a togglable/resizable drawer which can -/// host a terminal or additional debug information -/// -struct StatusBarView: View { - @Environment(\.controlActiveState) - private var controlActive - - @EnvironmentObject private var model: UtilityAreaViewModel - - static let height = 28.0 - - @Environment(\.colorScheme) - private var colorScheme - - var proxy: SplitViewProxy - - static let statusbarID = "statusbarID" - - /// The actual status bar - var body: some View { - HStack(alignment: .center, spacing: 10) { -// StatusBarBreakpointButton() -// StatusBarDivider() - Spacer() - HStack(alignment: .center, spacing: 10) { - StatusBarCursorLocationLabel() - } - StatusBarDivider() - StatusBarToggleUtilityAreaButton() - } - .padding(.horizontal, 10) - .cursor(.resizeUpDown) - .frame(height: Self.height) - .background(.bar) - .padding(.top, 1) - .overlay(alignment: .top) { - Divider() - .overlay(Color(nsColor: colorScheme == .dark ? .black : .clear)) - } - .gesture(dragGesture) - .disabled(controlActive == .inactive) - } - - /// A drag gesture to resize the drawer beneath the status bar - private var dragGesture: some Gesture { - DragGesture(coordinateSpace: .global) - .onChanged { value in - proxy.setPosition(of: 0, position: value.location.y + Self.height / 2) - } - } -} - -struct StatusBarDivider: View { - var body: some View { - Divider() - .frame(maxHeight: 12) -// .padding(.horizontal, 7) - } -} - -extension View { - func cursor(_ cursor: NSCursor) -> some View { - onHover { - if $0 { - cursor.push() - } else { - cursor.pop() - } - } - } -} diff --git a/CodeEdit/Features/TerminalEmulator/TerminalEmulatorView+Coordinator.swift b/CodeEdit/Features/TerminalEmulator/TerminalEmulatorView+Coordinator.swift deleted file mode 100644 index cf88289fa3..0000000000 --- a/CodeEdit/Features/TerminalEmulator/TerminalEmulatorView+Coordinator.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// TerminalEmulatorView+Coordinator.swift -// CodeEditModules/TerminalEmulator -// -// Created by Lukas Pistrol on 24.03.22. -// - -import SwiftUI -import SwiftTerm - -extension TerminalEmulatorView { - final class Coordinator: NSObject, LocalProcessTerminalViewDelegate { - - @State private var url: URL - - public var onTitleChange: (_ title: String) -> Void - - init(url: URL, onTitleChange: @escaping (_ title: String) -> Void) { - self._url = .init(wrappedValue: url) - self.onTitleChange = onTitleChange - super.init() - } - - func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {} - - func sizeChanged(source: LocalProcessTerminalView, newCols: Int, newRows: Int) {} - - func setTerminalTitle(source: LocalProcessTerminalView, title: String) { - onTitleChange(title) - } - - func processTerminated(source: TerminalView, exitCode: Int32?) { - guard let exitCode else { - return - } - source.feed(text: "Exit code: \(exitCode)\n\r\n") - source.feed(text: "To open a new session close and reopen the terminal drawer") - TerminalEmulatorView.lastTerminal[url.path] = nil - } - } -} diff --git a/CodeEdit/Features/TerminalEmulator/TerminalEmulatorView.swift b/CodeEdit/Features/TerminalEmulator/TerminalEmulatorView.swift deleted file mode 100644 index 1e0fd9ded4..0000000000 --- a/CodeEdit/Features/TerminalEmulator/TerminalEmulatorView.swift +++ /dev/null @@ -1,328 +0,0 @@ -// -// TerminalEmulatorView.swift -// CodeEditModules/TerminalEmulator -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI -import SwiftTerm - -/// # TerminalEmulatorView -/// -/// A terminal emulator view. -/// -/// Wraps a `LocalProcessTerminalView` from `SwiftTerm` inside a `NSViewRepresentable` -/// for use in SwiftUI. -/// -struct TerminalEmulatorView: NSViewRepresentable { - @AppSettings(\.terminal) - var terminalSettings - @AppSettings(\.textEditing.font) - var fontSettings - - @StateObject private var themeModel: ThemeModel = .shared - - static var lastTerminal: [String: LocalProcessTerminalView] = [:] - - @State var terminal: LocalProcessTerminalView - - private var font: NSFont { - if terminalSettings.useTextEditorFont { - return fontSettings.current - } else { - return terminalSettings.font.current - } - } - - private var url: URL - - public var shellType: String - - public var onTitleChange: (_ title: String) -> Void - - init(url: URL, shellType: String? = nil, onTitleChange: @escaping (_ title: String) -> Void) { - self.url = url - self.shellType = shellType ?? "" - self.onTitleChange = onTitleChange - self._terminal = State(initialValue: TerminalEmulatorView.lastTerminal[url.path] ?? .init(frame: .zero)) - } - - /// Returns a string of a shell path to use - /// - /// Default implementation pulled from Example app from "SwiftTerm": - /// ```swift - /// let bufsize = sysconf(_SC_GETPW_R_SIZE_MAX) - /// guard bufsize != -1 else { return "/bin/bash" } - /// let buffer = UnsafeMutablePointer.allocate(capacity: bufsize) - /// defer { - /// buffer.deallocate() - /// } - /// var pwd = passwd() - /// var result: UnsafeMutablePointer? = UnsafeMutablePointer.allocate(capacity: 1) - /// - /// if getpwuid_r(getuid(), &pwd, buffer, bufsize, &result) != 0 { return "/bin/bash" } - /// return String(cString: pwd.pw_shell) - /// ``` - private func getShell() -> String { - if shellType != ""{ - return shellType - } - switch terminalSettings.shell { - case .system: - return autoDetectDefaultShell() - case .bash: - return "/bin/bash" - case .zsh: - return "/bin/zsh" - } - } - - private func getTerminalCursor() -> CursorStyle { - let blink = terminalSettings.cursorBlink - switch terminalSettings.cursorStyle { - case .block: - return blink ? .blinkBlock : .steadyBlock - case .underline: - return blink ? .blinkUnderline : .steadyUnderline - case .bar: - return blink ? .blinkBar : .steadyBar - } - } - - /// Gets the default shell from the current user and returns the string of the shell path. - private func autoDetectDefaultShell() -> String { - let bufsize = sysconf(_SC_GETPW_R_SIZE_MAX) - guard bufsize != -1 else { return "/bin/bash" } - let buffer = UnsafeMutablePointer.allocate(capacity: bufsize) - defer { - buffer.deallocate() - } - var pwd = passwd() - var result: UnsafeMutablePointer? = UnsafeMutablePointer.allocate(capacity: 1) - - if getpwuid_r(getuid(), &pwd, buffer, bufsize, &result) != 0 { return "/bin/bash" } - return String(cString: pwd.pw_shell) - } - - /// Check if the source command for shell integration already exists - /// Returns true if it already exists or encountered an error, no new commands will be added to user's source file - /// Returns false if it's not there, new commands will be added to user's source file - private func shellIntegrationInstalled(sourceScriptPath: String, command: String) -> Bool { - do { - // Get user's shell's source file - let sourceScript = try String(contentsOfFile: sourceScriptPath) - let sourceScriptSeperatedByLines = sourceScript.components(separatedBy: .newlines) - // Check line by line - for line in sourceScriptSeperatedByLines where line == command { - // If one line matches the command, no new commands are needed - return true - } - // If no line matches the command, new command is needed - return false - } catch { - if let error = error as NSError? { - switch error._code { - case 260: - // If error 260 is thrown, it's just the source file is missing - // Create a new file and add new command - FileManager.default.createFile(atPath: sourceScriptPath, contents: nil, attributes: nil) - return false - default: - // Otherwise just abort the shell integration setup - print("Cannot setup shell integration, error: \(error)") - return true - } - } - } - } - - /// Configure shell integration script - private func setupShellIntegration(shell: String, environment: [String]) { - // Get user's home dir - var homePath: String = "" - environment.forEach { value in - if value.starts(with: "HOME=") { - homePath = value - } - } - homePath.removeSubrange(homePath.startIndex.. LocalProcessTerminalView { - terminal.processDelegate = context.coordinator - setupSession() - return terminal - } - - func setupSession() { - terminal.getTerminal().silentLog = true - if TerminalEmulatorView.lastTerminal[url.path] == nil { - let shell = getShell() - let shellName = NSString(string: shell).lastPathComponent - onTitleChange(shellName) - let shellIdiom = "-" + shellName - - // changes working directory to project root - // TODO: Get rid of FileManager shared instance to prevent problems - // using shared instance of FileManager might lead to problems when using - // multiple workspaces. This works for now but most probably will need - // to be changed later on - FileManager.default.changeCurrentDirectoryPath(url.path) - - var terminalEnvironment: [String] = Terminal.getEnvironmentVariables() - terminalEnvironment.append("TERM_PROGRAM=CodeEditApp_Terminal") - - setupShellIntegration(shell: shellName, environment: terminalEnvironment) - - terminal.startProcess(executable: shell, environment: terminalEnvironment, execName: shellIdiom) - terminal.font = font - terminal.configureNativeColors() - terminal.installColors(self.colors) - terminal.caretColor = cursorColor - terminal.selectedTextBackgroundColor = selectionColor - terminal.nativeForegroundColor = textColor - terminal.nativeBackgroundColor = terminalSettings.useThemeBackground ? backgroundColor : .clear - terminal.cursorStyleChanged(source: terminal.getTerminal(), newStyle: getTerminalCursor()) - terminal.layer?.backgroundColor = .clear - terminal.optionAsMetaKey = optionAsMeta - } - terminal.appearance = colorAppearance - scroller?.isHidden = true - TerminalEmulatorView.lastTerminal[url.path] = terminal - } - - private var scroller: NSScroller? { - for subView in terminal.subviews { - if let scroller = subView as? NSScroller { - return scroller - } - } - return nil - } - - func updateNSView(_ view: LocalProcessTerminalView, context: Context) { - if view.font != font { // Fixes Memory leak - view.font = font - } - view.configureNativeColors() - view.installColors(self.colors) - view.caretColor = cursorColor - view.selectedTextBackgroundColor = selectionColor - view.nativeForegroundColor = textColor - view.nativeBackgroundColor = terminalSettings.useThemeBackground ? backgroundColor : .clear - view.layer?.backgroundColor = .clear - view.optionAsMetaKey = optionAsMeta - view.cursorStyleChanged(source: view.getTerminal(), newStyle: getTerminalCursor()) - view.appearance = colorAppearance - if TerminalEmulatorView.lastTerminal[url.path] != nil { - TerminalEmulatorView.lastTerminal[url.path] = view - } - view.getTerminal().softReset() - view.feed(text: "") // send empty character to force colors to be redrawn - } - - func makeCoordinator() -> Coordinator { - Coordinator(url: url, onTitleChange: onTitleChange) - } -} diff --git a/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift b/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift deleted file mode 100644 index eeb92b3552..0000000000 --- a/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// UtilityAreaDebugView.swift -// CodeEdit -// -// Created by Austin Condiff on 5/25/23. -// - -import SwiftUI - -struct UtilityAreaDebugView: View { - @EnvironmentObject private var model: UtilityAreaViewModel - - @State var tabSelection = 0 - - var body: some View { - UtilityAreaTabView(model: model.tabViewModel) { _ in - Text("Nothing to debug") - .font(.system(size: 16)) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .paneToolbar { - EmptyView() - } - } leadingSidebar: { _ in - List(selection: $tabSelection) { - EmptyView() - } - .listStyle(.automatic) - .accentColor(.secondary) - .paneToolbar { -// Button { -// // add -// } label: { -// Image(systemName: "plus") -// } -// Button { -// // remove -// } label: { -// Image(systemName: "minus") -// } - } - } - } -} diff --git a/CodeEdit/Features/UtilityArea/Models/UtilityAreaTab.swift b/CodeEdit/Features/UtilityArea/Models/UtilityAreaTab.swift deleted file mode 100644 index ff3248161e..0000000000 --- a/CodeEdit/Features/UtilityArea/Models/UtilityAreaTab.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// UtilityAreaTab.swift -// CodeEdit -// -// Created by Wouter Hennen on 02/06/2023. -// - -import SwiftUI - -enum UtilityAreaTab: AreaTab, CaseIterable { - var id: Self { self } - - case terminal - case debugConsole - case output - - var title: String { - switch self { - case .terminal: - return "Terminal" - case .debugConsole: - return "Debug Console" - case .output: - return "Output" - } - } - - var systemImage: String { - switch self { - case .terminal: - return "terminal" - case .debugConsole: - return "ladybug" - case .output: - return "list.bullet.indent" - } - } - - var body: some View { - switch self { - case .terminal: - UtilityAreaTerminalView() - case .debugConsole: - UtilityAreaDebugView() - case .output: - UtilityAreaOutputView() - } - } -} diff --git a/CodeEdit/Features/UtilityArea/OutputUtility/UtilityAreaOutputView.swift b/CodeEdit/Features/UtilityArea/OutputUtility/UtilityAreaOutputView.swift deleted file mode 100644 index c1786e024f..0000000000 --- a/CodeEdit/Features/UtilityArea/OutputUtility/UtilityAreaOutputView.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// UtilityAreaDebugView.swift -// CodeEdit -// -// Created by Austin Condiff on 5/25/23. -// - -import SwiftUI -import LogStream - -struct UtilityAreaOutputView: View { - @EnvironmentObject private var model: UtilityAreaViewModel - - @ObservedObject var extensionManager = ExtensionManager.shared - - @State var output: [LogMessage] = [] - - @State private var filterText = "" - - @State var selectedOutputSource: ExtensionInfo? - - var filteredOutput: [LogMessage] { - output.filter { item in - return filterText == "" ? true : item.message.contains(filterText) - } - } - - var body: some View { - UtilityAreaTabView(model: model.tabViewModel) { _ in - Group { - if selectedOutputSource == nil { - Text("No output") - .font(.system(size: 16)) - .foregroundColor(.secondary) - .frame(maxHeight: .infinity) - } else { - if let ext = selectedOutputSource { - ScrollView { - VStack(alignment: .leading) { - ForEach(filteredOutput, id: \.self) { item in - HStack { - Text(item.message) - .fontWeight(.semibold) - .fontDesign(.monospaced) - .foregroundColor(item.type.color) - Spacer() - } - .padding(.leading, 2) - } - } - .padding(5) - .frame(maxWidth: .infinity) - .rotationEffect(.radians(.pi)) - .scaleEffect(x: -1, y: 1, anchor: .center) - } - .rotationEffect(.radians(.pi)) - .scaleEffect(x: -1, y: 1, anchor: .center) - .task(id: ext.pid) { - output = [] - for await item in LogStream.logs(for: ext.pid, flags: [.info, .historical, .processOnly]) { - output.append(item) - } - } - } - } - } - .paneToolbar { - Picker("Output Source", selection: $selectedOutputSource) { - Text("All Sources") - .tag(nil as ExtensionInfo?) - ForEach(extensionManager.extensions) { - Text($0.name) - .tag($0 as ExtensionInfo?) - } - } - .buttonStyle(.borderless) - .labelsHidden() - .controlSize(.small) - Spacer() - FilterTextField(title: "Filter", text: $filterText) - .frame(maxWidth: 175) - Button { - output = [] - } label: { - Image(systemName: "trash") - } - } - } - } -} diff --git a/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalTab.swift b/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalTab.swift deleted file mode 100644 index c7a310c93f..0000000000 --- a/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalTab.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// UtilityAreaTerminalTab.swift -// CodeEdit -// -// Created by Austin Condiff on 5/26/23. -// - -import SwiftUI - -struct UtilityAreaTerminalTab: View { - @ObservedObject var terminal: UtilityAreaTerminal - - var removeTerminals: (_ ids: Set) -> Void - - var isSelected: Bool - - var selectedIDs: Set - - @FocusState private var isFocused: Bool - - var body: some View { - let terminalTitle = Binding( - get: { - self.terminal.title - }, set: { - if $0.trimmingCharacters(in: .whitespaces) == "" && !isFocused { - self.terminal.title = self.terminal.terminalTitle - self.terminal.customTitle = false - } else { - self.terminal.title = $0 - self.terminal.customTitle = true - } - } - ) - - Label { - if #available(macOS 14, *) { - // Fix the icon misplacement issue introduced since macOS 14 - TextField("Name", text: terminalTitle) - .focused($isFocused) - } else { - // A padding is needed for macOS 13 - TextField("Name", text: terminalTitle) - .focused($isFocused) - .padding(.leading, -8) - } - } icon: { - Image(systemName: "terminal") - } - .contextMenu { - Button("Rename...") { - isFocused = true - } - Button("Kill Terminal") { - if isSelected { removeTerminals([terminal.id]) } - } - } - } -} diff --git a/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift b/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift deleted file mode 100644 index 815d1a16f2..0000000000 --- a/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift +++ /dev/null @@ -1,279 +0,0 @@ -// -// DebuggerAreaTerminal.swift -// CodeEdit -// -// Created by Austin Condiff on 5/25/23. -// - -import SwiftUI - -final class UtilityAreaTerminal: ObservableObject, Identifiable, Equatable { - let id: UUID - @Published var url: URL? - @Published var title: String - @Published var terminalTitle: String - @Published var shell: String - @Published var customTitle: Bool - - init(id: UUID, url: URL, title: String, shell: String) { - self.id = id - self.title = title - self.terminalTitle = title - self.url = url - self.shell = shell - self.customTitle = false - } - - static func == (lhs: UtilityAreaTerminal, rhs: UtilityAreaTerminal) -> Bool { - lhs.id == rhs.id - } -} - -struct UtilityAreaTerminalView: View { - @AppSettings(\.theme.matchAppearance) - private var matchAppearance - @AppSettings(\.terminal.darkAppearance) - private var darkAppearance - @AppSettings(\.theme.useThemeBackground) - private var useThemeBackground - - @Environment(\.colorScheme) - private var colorScheme - - @EnvironmentObject private var workspace: WorkspaceDocument - - @EnvironmentObject private var model: UtilityAreaViewModel - - @State private var sidebarIsCollapsed = false - - @StateObject private var themeModel: ThemeModel = .shared - - @State private var isMenuVisible = false - - @State private var popoverSource: CGRect = .zero - - private func initializeTerminals() { - let id = UUID() - - model.terminals = [ - UtilityAreaTerminal( - id: id, - url: workspace.workspaceFileManager?.folderUrl ?? URL(filePath: "/"), - title: "terminal", - shell: "" - ) - ] - - model.selectedTerminals = [id] - } - - private func addTerminal(shell: String? = nil) { - let id = UUID() - - model.terminals.append( - UtilityAreaTerminal( - id: id, - url: URL(filePath: "\(id)"), - title: "terminal", - shell: shell ?? "" - ) - ) - - model.selectedTerminals = [id] - } - - private func getTerminal(_ id: UUID) -> UtilityAreaTerminal? { - return model.terminals.first(where: { $0.id == id }) ?? nil - } - - private func updateTerminal(_ id: UUID, title: String? = nil) { - let terminalIndex = model.terminals.firstIndex(where: { $0.id == id }) - if terminalIndex != nil { - updateTerminalByReference(of: &model.terminals[terminalIndex!], title: title) - } - } - - func updateTerminalByReference( - of terminal: inout UtilityAreaTerminal, - title: String? = nil - ) { - if let newTitle = title { - if !terminal.customTitle { - terminal.title = newTitle - } - terminal.terminalTitle = newTitle - } - } - - func handleTitleChange(id: UUID, title: String) { - updateTerminal(id, title: title) - } - - /// Returns the `background` color of the selected theme - private var backgroundColor: NSColor { - if let selectedTheme = matchAppearance && darkAppearance - ? themeModel.selectedDarkTheme - : themeModel.selectedTheme, - let index = themeModel.themes.firstIndex(of: selectedTheme) { - return NSColor(themeModel.themes[index].terminal.background.swiftColor) - } - return .windowBackgroundColor - } - - func moveItems(from source: IndexSet, to destination: Int) { - model.terminals.move(fromOffsets: source, toOffset: destination) - } - - var body: some View { - UtilityAreaTabView(model: model.tabViewModel) { tabState in - ZStack { - if model.selectedTerminals.isEmpty { - CEContentUnavailableView("No Selection") - } - ForEach(model.terminals) { terminal in - TerminalEmulatorView( - url: terminal.url!, - shellType: terminal.shell, - onTitleChange: { newTitle in - // This can be called whenever, even in a view update so it needs to be dispatched. - DispatchQueue.main.async { - handleTitleChange(id: terminal.id, title: newTitle) - } - } - ) - .padding(.top, 10) - .padding(.horizontal, 10) - .contentShape(Rectangle()) - .disabled(terminal.id != model.selectedTerminals.first) - .opacity(terminal.id == model.selectedTerminals.first ? 1 : 0) - } - } - .paneToolbar { - PaneToolbarSection { - UtilityAreaTerminalPicker(selectedIDs: $model.selectedTerminals, terminals: model.terminals) - .opacity(tabState.leadingSidebarIsCollapsed ? 1 : 0) - } - Spacer() - PaneToolbarSection { - Button { - // clear logs - } label: { - Image(systemName: "trash") - } - Button { - // split terminal - } label: { - Image(systemName: "square.split.2x1") - } - } - } - .background { - if model.selectedTerminals.isEmpty { - EffectView(.contentBackground) - } else if useThemeBackground { - Color(nsColor: backgroundColor) - } else { - if colorScheme == .dark { - EffectView(.underPageBackground) - } else { - EffectView(.contentBackground) - } - } - } - .colorScheme( - model.selectedTerminals.isEmpty - ? colorScheme - : matchAppearance && darkAppearance - ? themeModel.selectedDarkTheme?.appearance == .dark ? .dark : .light - : themeModel.selectedTheme?.appearance == .dark ? .dark : .light - ) - } leadingSidebar: { _ in - List(selection: $model.selectedTerminals) { - ForEach(model.terminals, id: \.self.id) { terminal in - UtilityAreaTerminalTab( - terminal: terminal, - removeTerminals: model.removeTerminals, - isSelected: model.selectedTerminals.contains(terminal.id), - selectedIDs: model.selectedTerminals - ) - .tag(terminal.id) - .listRowSeparator(.hidden) - } - .onMove(perform: moveItems) - } - .focusedObject(model) - .listStyle(.automatic) - .accentColor(.secondary) - .contextMenu { - Button("New Terminal") { - addTerminal() - } - Menu("New Terminal With Profile") { - Button("Default") { - addTerminal() - } - Divider() - Button("Bash") { - addTerminal(shell: "/bin/bash") - } - Button("ZSH") { - addTerminal(shell: "/bin/zsh") - } - } - } - .onChange(of: model.terminals) { newValue in - if newValue.isEmpty { - addTerminal() - } - } - .paneToolbar { - PaneToolbarSection { - Button { - addTerminal() - } label: { - Image(systemName: "plus") - } - Button { - model.removeTerminals(model.selectedTerminals) - } label: { - Image(systemName: "minus") - } - .disabled(model.terminals.count <= 1) - .opacity(model.terminals.count <= 1 ? 0.5 : 1) - } - Spacer() - } - } - .onAppear(perform: initializeTerminals) - } -} - -struct UtilityAreaTerminalPicker: View { - @Binding var selectedIDs: Set - var terminals: [UtilityAreaTerminal] - - var selectedID: Binding { - Binding( - get: { - selectedIDs.first - }, - set: { newValue in - if let selectedID = newValue { - selectedIDs = [selectedID] - } - } - ) - } - - var body: some View { - Picker("Terminal Tab", selection: selectedID) { - ForEach(terminals, id: \.self.id) { terminal in - Text(terminal.title) - .tag(terminal.id as UUID?) - } - } - .labelsHidden() - .controlSize(.small) - .buttonStyle(.borderless) - } -} diff --git a/CodeEdit/Features/UtilityArea/Toolbar/FilterTextField.swift b/CodeEdit/Features/UtilityArea/Toolbar/FilterTextField.swift deleted file mode 100644 index 20de5db6f8..0000000000 --- a/CodeEdit/Features/UtilityArea/Toolbar/FilterTextField.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// FilterTextField.swift -// CodeEditModules/StatusBar -// -// Created by Stef Kors on 12/04/2022. -// - -import SwiftUI - -struct FilterTextField: View { - let title: String - - @Binding var text: String - - @FocusState private var isFocused: Bool - - var body: some View { - HStack(spacing: 5) { - Image(systemName: "line.3.horizontal.decrease.circle") - .foregroundColor(Color(nsColor: .labelColor)) - .font(.system(size: 13, weight: .regular)) - .padding(.leading, -1) - .padding(.trailing, -2) - textField - if !text.isEmpty { clearButton } - } - .padding(.horizontal, 5) - .frame(height: 22) - .background(Color(nsColor: isFocused ? .textBackgroundColor : .quaternaryLabelColor)) - .cornerRadius(7) - .overlay( - RoundedRectangle(cornerRadius: 7) - .stroke(isFocused ? .secondary : .tertiary, lineWidth: 0.75).cornerRadius(7) - ) - } - - private var textField: some View { - TextField(title, text: $text) - .font(.system(size: 11, weight: .regular)) - .disableAutocorrection(true) - .textFieldStyle(PlainTextFieldStyle()) - .focused($isFocused) - } - - private var clearButton: some View { - Button { - self.text = "" - } label: { - Image(systemName: "xmark.circle.fill") - } - .foregroundColor(.secondary) - .buttonStyle(PlainButtonStyle()) - } -} diff --git a/CodeEdit/Features/UtilityArea/Toolbar/StatusBarClearButton.swift b/CodeEdit/Features/UtilityArea/Toolbar/StatusBarClearButton.swift deleted file mode 100644 index 74e174d24f..0000000000 --- a/CodeEdit/Features/UtilityArea/Toolbar/StatusBarClearButton.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// StatusBarClearButton.swift -// CodeEditModules/StatusBar -// -// Created by Stef Kors on 12/04/2022. -// - -import SwiftUI - -struct StatusBarClearButton: View { - @EnvironmentObject private var model: UtilityAreaViewModel - - var body: some View { - Button { - // Clear terminal - } label: { - Image(systemName: "trash") - .foregroundColor(.secondary) - } - .buttonStyle(.plain) - } -} diff --git a/CodeEdit/Features/UtilityArea/Toolbar/StatusBarMaximizeButton.swift b/CodeEdit/Features/UtilityArea/Toolbar/StatusBarMaximizeButton.swift deleted file mode 100644 index ac87eeb894..0000000000 --- a/CodeEdit/Features/UtilityArea/Toolbar/StatusBarMaximizeButton.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// StatusBarMaximizeButton.swift -// CodeEditModules/StatusBar -// -// Created by Stef Kors on 12/04/2022. -// - -import SwiftUI - -struct StatusBarMaximizeButton: View { - @EnvironmentObject private var model: UtilityAreaViewModel - - var body: some View { - Button { - model.isMaximized.toggle() - } label: { - Image(systemName: "arrowtriangle.up.square") - .foregroundColor(model.isMaximized ? .accentColor : .secondary) - } - .buttonStyle(.plain) - } -} diff --git a/CodeEdit/Features/UtilityArea/Toolbar/StatusBarSplitTerminalButton.swift b/CodeEdit/Features/UtilityArea/Toolbar/StatusBarSplitTerminalButton.swift deleted file mode 100644 index 71e1288410..0000000000 --- a/CodeEdit/Features/UtilityArea/Toolbar/StatusBarSplitTerminalButton.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// StatusBarSplitTerminalButton.swift -// CodeEditModules/StatusBar -// -// Created by Stef Kors on 14/04/2022. -// - -import SwiftUI - -struct StatusBarSplitTerminalButton: View { - @EnvironmentObject private var model: UtilityAreaViewModel - - var body: some View { - Button { - // todo - } label: { - Image(systemName: "square.split.2x1") - .foregroundColor(.secondary) - } - .buttonStyle(.plain) - } -} diff --git a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaTabViewModel.swift b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaTabViewModel.swift deleted file mode 100644 index 5ae7998cfb..0000000000 --- a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaTabViewModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// UtilityAreaTabViewModel.swift -// CodeEdit -// -// Created by Austin Condiff on 5/31/23. -// - -import SwiftUI - -class UtilityAreaTabViewModel: ObservableObject { - @Published var leadingSidebarIsCollapsed: Bool = false - - @Published var trailingSidebarIsCollapsed: Bool = false - - @Published var hasLeadingSidebar: Bool = false - - @Published var hasTrailingSidebar: Bool = false -} diff --git a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift deleted file mode 100644 index 542def1310..0000000000 --- a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// UtilityAreaViewModel.swift -// CodeEdit -// -// Created by Lukas Pistrol on 20.03.22. -// - -import SwiftUI - -/// # UtilityAreaViewModel -/// -/// A model class to host and manage data for the ``StatusBarView`` -/// -class UtilityAreaViewModel: ObservableObject { - /// Returns the current location of the cursor in an editing view - @Published var cursorLocation: CursorLocation = .init(line: 1, column: 1) // Implementation needed!! - - @Published var terminals: [UtilityAreaTerminal] = [] - - @Published var selectedTerminals: Set = [] - - /// Indicates whether debugger is collapse or not - @Published var isCollapsed: Bool = false - - /// Returns true when the drawer is visible - @Published var isMaximized: Bool = false - - /// The current height of the drawer. Zero if hidden - @Published var currentHeight: Double = 0 - - /// Indicates whether the drawer is being resized or not - @Published var isDragging: Bool = false - - /// Indicates whether the breakpoint is enabled or not - @Published var isBreakpointEnabled: Bool = true - - /// Search value to filter in drawer - @Published var searchText: String = "" - - /// The tab bar items for the DebugAreaView - @Published var tabItems: [UtilityAreaTab] = UtilityAreaTab.allCases - - /// The tab bar view model for UtilityAreaTabView - @Published var tabViewModel = UtilityAreaTabViewModel() - - /// Returns the font for status bar items to use - private(set) var toolbarFont: Font = .system(size: 11, weight: .medium) - - func removeTerminals(_ ids: Set) { - terminals.removeAll(where: { terminal in - ids.contains(terminal.id) - }) - - selectedTerminals = [terminals.last?.id ?? UUID()] - } - - func restoreFromState(_ workspace: WorkspaceDocument) { - isCollapsed = workspace.getFromWorkspaceState(.utilityAreaCollapsed) as? Bool ?? false - currentHeight = workspace.getFromWorkspaceState(.utilityAreaHeight) as? Double ?? 300.0 - isMaximized = workspace.getFromWorkspaceState(.utilityAreaMaximized) as? Bool ?? false - } - - func saveRestorationState(_ workspace: WorkspaceDocument) { - workspace.addToWorkspaceState(key: .utilityAreaCollapsed, value: isCollapsed) - workspace.addToWorkspaceState(key: .utilityAreaHeight, value: currentHeight) - workspace.addToWorkspaceState(key: .utilityAreaMaximized, value: isMaximized) - } - - func togglePanel() { - withAnimation { - self.isCollapsed.toggle() - } - } -} diff --git a/CodeEdit/Features/UtilityArea/Views/OSLogType+Color.swift b/CodeEdit/Features/UtilityArea/Views/OSLogType+Color.swift deleted file mode 100644 index 94535886df..0000000000 --- a/CodeEdit/Features/UtilityArea/Views/OSLogType+Color.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// OSLogType+Color.swift -// CodeEdit -// -// Created by Wouter Hennen on 22/05/2023. -// - -import OSLog -import SwiftUI - -extension OSLogType { - var color: Color { - switch self { - case .error: - return .orange - case .debug, .default: - return .primary - case .fault: - return .red - case .info: - return .cyan - default: - return .green - } - } -} diff --git a/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift b/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift deleted file mode 100644 index 3ea22fd54a..0000000000 --- a/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// PaneToolbar.swift -// CodeEdit -// -// Created by Austin Condiff on 5/31/23. -// - -import SwiftUI - -struct PaneToolbar: View { - @ViewBuilder var content: Content - @EnvironmentObject var model: UtilityAreaTabViewModel - @Environment(\.paneArea) - var paneArea: PaneArea? - - var body: some View { - HStack(spacing: 5) { - if model.hasLeadingSidebar - && ( - ((paneArea == .main || paneArea == .mainLeading) - && model.leadingSidebarIsCollapsed) - || paneArea == .leading - ) { - PaneToolbarSection { - Spacer() - .frame(width: 24) - } - .opacity(0) - Divider().opacity(0) - } - content - if model.hasTrailingSidebar - && ( - ((paneArea == .main || paneArea == .mainTrailing) - && model.trailingSidebarIsCollapsed) - || paneArea == .trailing - ) || !model.hasTrailingSidebar { - Divider().opacity(0) - PaneToolbarSection { - if model.hasTrailingSidebar { - Spacer() - .frame(width: 24) - } - Spacer() - .frame(width: 24) - } - .opacity(0) - } - } - .buttonStyle(.icon(size: 24)) - .padding(.horizontal, 5) - .padding(.vertical, 8) - .frame(maxHeight: 27) - } -} diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift deleted file mode 100644 index bb36bcc5ab..0000000000 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// UtilityAreaTabView.swift -// CodeEdit -// -// Created by Austin Condiff on 5/30/23. -// - -import SwiftUI - -struct UtilityAreaTabView: View { - @ObservedObject var model: UtilityAreaTabViewModel - - let content: (UtilityAreaTabViewModel) -> Content - let leadingSidebar: (UtilityAreaTabViewModel) -> LeadingSidebar? - let trailingSidebar: (UtilityAreaTabViewModel) -> TrailingSidebar? - - let hasLeadingSidebar: Bool - let hasTrailingSidebar: Bool - - init( - model: UtilityAreaTabViewModel, - @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content, - @ViewBuilder leadingSidebar: @escaping (UtilityAreaTabViewModel) -> LeadingSidebar, - @ViewBuilder trailingSidebar: @escaping (UtilityAreaTabViewModel) -> TrailingSidebar, - hasLeadingSidebar: Bool = true, - hasTrailingSidebar: Bool = true - ) { - self.model = model - - self.content = content - self.leadingSidebar = leadingSidebar - self.trailingSidebar = trailingSidebar - - self.hasLeadingSidebar = hasLeadingSidebar - self.hasTrailingSidebar = hasTrailingSidebar - } - - init( - model: UtilityAreaTabViewModel, - @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content - ) where - LeadingSidebar == EmptyView, - TrailingSidebar == EmptyView { - self.init( - model: model, - content: content, - leadingSidebar: { _ in EmptyView() }, - trailingSidebar: { _ in EmptyView() }, - hasLeadingSidebar: false, - hasTrailingSidebar: false - ) - } - - init( - model: UtilityAreaTabViewModel, - @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content, - @ViewBuilder leadingSidebar: @escaping (UtilityAreaTabViewModel) -> LeadingSidebar - ) where TrailingSidebar == EmptyView { - self.init( - model: model, - content: content, - leadingSidebar: leadingSidebar, - trailingSidebar: { _ in EmptyView() }, - hasTrailingSidebar: false - ) - } - - init( - model: UtilityAreaTabViewModel, - @ViewBuilder content: @escaping (UtilityAreaTabViewModel) -> Content, - @ViewBuilder trailingSidebar: @escaping (UtilityAreaTabViewModel) -> TrailingSidebar - ) where LeadingSidebar == EmptyView { - self.init( - model: model, - content: content, - leadingSidebar: { _ in EmptyView() }, - trailingSidebar: trailingSidebar, - hasLeadingSidebar: false - ) - } - - var body: some View { - SplitView(axis: .horizontal) { - // Leading Sidebar - if model.hasLeadingSidebar { - leadingSidebar(model) - .collapsable() - .collapsed($model.leadingSidebarIsCollapsed) - .frame(minWidth: 200, idealWidth: 240, maxWidth: 400) - .environment(\.paneArea, .leading) - } - - // Content Area - content(model) - .holdingPriority(.init(1)) - .environment(\.paneArea, .main) - - // Trailing Sidebar - if model.hasTrailingSidebar { - trailingSidebar(model) - .collapsable() - .collapsed($model.trailingSidebarIsCollapsed) - .frame(minWidth: 200, idealWidth: 240, maxWidth: 400) - .environment(\.paneArea, .trailing) - } - } - .animation(.default, value: model.leadingSidebarIsCollapsed) - .animation(.default, value: model.trailingSidebarIsCollapsed) - .frame(maxHeight: .infinity) - .overlay(alignment: .bottomLeading) { - if model.hasLeadingSidebar { - PaneToolbar { - PaneToolbarSection { - Button { - model.leadingSidebarIsCollapsed.toggle() - } label: { - Image(systemName: "square.leadingthird.inset.filled") - } - .buttonStyle(.icon(isActive: !model.leadingSidebarIsCollapsed)) - } - Divider() - } - } - } - .overlay(alignment: .bottomTrailing) { - if model.hasTrailingSidebar { - PaneToolbar { - Divider() - PaneToolbarSection { - Button { - model.trailingSidebarIsCollapsed.toggle() - } label: { - Image(systemName: "square.trailingthird.inset.filled") - } - .buttonStyle(.icon(isActive: !model.trailingSidebarIsCollapsed)) - Spacer() - .frame(width: 24) - } - } - } - } - .environmentObject(model) - .onAppear { - model.hasLeadingSidebar = hasLeadingSidebar - model.hasTrailingSidebar = hasTrailingSidebar - } - } -} - -enum PaneArea: String { - case leading - case main - case mainLeading - case mainCenter - case mainTrailing - case trailing -} - -private struct PaneAreaKey: EnvironmentKey { - static let defaultValue: PaneArea? = nil -} - -extension EnvironmentValues { - var paneArea: PaneArea? { - get { self[PaneAreaKey.self] } - set { self[PaneAreaKey.self] = newValue } - } -} - -struct PaneToolbarSection: View { - @ViewBuilder var content: Content - - var body: some View { - HStack(spacing: 0) { - content - } - } -} diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift deleted file mode 100644 index fc611b03ac..0000000000 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// UtilityAreaView.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI - -struct UtilityAreaView: View { - @AppSettings(\.theme.matchAppearance) - private var matchAppearance - - @AppSettings(\.terminal.darkAppearance) - private var darkAppearance - - @Environment(\.colorScheme) - private var colorScheme - - @EnvironmentObject private var model: UtilityAreaViewModel - - @StateObject private var themeModel: ThemeModel = .shared - - @State var selection: UtilityAreaTab? = .terminal - - var body: some View { - VStack(spacing: 0) { - if let selection { - selection - } else { - Text("Tab not found") - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - } - .safeAreaInset(edge: .leading, spacing: 0) { - HStack(spacing: 0) { - AreaTabBar(items: $model.tabItems, selection: $selection, position: .side) - Divider() - .overlay(Color(nsColor: colorScheme == .dark ? .black : .clear)) - } - } - .overlay(alignment: .bottomTrailing) { - HStack(spacing: 5) { - Divider() - HStack(spacing: 0) { - Button { - model.isMaximized.toggle() - } label: { - Image(systemName: "arrowtriangle.up.square") - } - .buttonStyle(.icon(isActive: model.isMaximized, size: 24)) - } - } - .colorScheme( - model.selectedTerminals.isEmpty - ? colorScheme - : matchAppearance && darkAppearance - ? themeModel.selectedDarkTheme?.appearance == .dark ? .dark : .light - : themeModel.selectedTheme?.appearance == .dark ? .dark : .light - ) - .padding(.horizontal, 5) - .padding(.vertical, 8) - .frame(maxHeight: 27) - } - } -} diff --git a/CodeEdit/Features/UtilityArea/Views/View+paneToolbar.swift b/CodeEdit/Features/UtilityArea/Views/View+paneToolbar.swift deleted file mode 100644 index 000329cc3d..0000000000 --- a/CodeEdit/Features/UtilityArea/Views/View+paneToolbar.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// View+paneToolbar.swift -// CodeEdit -// -// Created by Austin Condiff on 5/31/23. -// - -import SwiftUI - -extension View { - func paneToolbar(@ViewBuilder content: () -> Content) -> some View { - self - .clipped() - .safeAreaInset(edge: .bottom, spacing: 0) { - PaneToolbar { - content() - } - } - } -} diff --git a/CodeEdit/Features/WindowCommands/CodeEditCommands.swift b/CodeEdit/Features/WindowCommands/CodeEditCommands.swift deleted file mode 100644 index d035499a0c..0000000000 --- a/CodeEdit/Features/WindowCommands/CodeEditCommands.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// CodeEditCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 11/03/2023. -// - -import SwiftUI - -struct CodeEditCommands: Commands { - var body: some Commands { - MainCommands() - FileCommands() - ViewCommands() - FindCommands() - NavigateCommands() - WindowCommands() - HelpCommands() - ExtensionCommands() - } -} diff --git a/CodeEdit/Features/WindowCommands/ExtensionCommands.swift b/CodeEdit/Features/WindowCommands/ExtensionCommands.swift deleted file mode 100644 index 58155a7941..0000000000 --- a/CodeEdit/Features/WindowCommands/ExtensionCommands.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ExtensionCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 24/03/2023. -// - -import SwiftUI -import CodeEditKit - -struct ExtensionCommands: Commands { - @FocusedObject var manager: ExtensionManager? - - @Environment(\.openWindow) - var openWindow - - var body: some Commands { - CommandMenu("Extensions") { - Button("Open Extensions Window") { - openWindow(sceneID: .extensions) - } - } - } -} diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift deleted file mode 100644 index 117fe2048d..0000000000 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// FileCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/03/2023. -// - -import SwiftUI - -struct FileCommands: Commands { - - @FocusedObject var utilityAreaViewModel: UtilityAreaViewModel? - - var body: some Commands { - CommandGroup(replacing: .newItem) { - Group { - Button("New") { - NSDocumentController.shared.newDocument(nil) - } - .keyboardShortcut("n") - - Button("Open...") { - NSDocumentController.shared.openDocument(nil) - } - .keyboardShortcut("o") - - // Leave this empty, is done through a hidden API in WindowCommands/Utils/CommandsFixes.swift - // This can't be done in SwiftUI Commands yet, as they don't support images in menu items. - Menu("Open Recent") {} - - Button("Open Quickly") { - NSApp.sendAction(#selector(CodeEditWindowController.openQuickly(_:)), to: nil, from: nil) - } - .keyboardShortcut("o", modifiers: [.command, .shift]) - } - } - - CommandGroup(replacing: .saveItem) { - Button("Close Tab") { - if NSApp.target(forAction: #selector(CodeEditWindowController.closeCurrentTab(_:))) != nil { - NSApp.sendAction(#selector(CodeEditWindowController.closeCurrentTab(_:)), to: nil, from: nil) - } else { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) - } - } - .keyboardShortcut("w") - - Button("Close Editor") { - if NSApp.target(forAction: #selector(CodeEditWindowController.closeActiveEditor(_:))) != nil { - NSApp.sendAction( - #selector(CodeEditWindowController.closeActiveEditor(_:)), - to: nil, - from: nil - ) - } else { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) - } - } - .keyboardShortcut("w", modifiers: [.control, .shift, .command]) - - Button("Close Window") { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) - } - .keyboardShortcut("w", modifiers: [.shift, .command]) - - Button("Close Workspace") { - guard let keyWindow = NSApplication.shared.keyWindow else { return } - NSApp.sendAction(#selector(NSWindow.close), to: keyWindow, from: nil) - } - .keyboardShortcut("w", modifiers: [.control, .option, .command]) - .disabled(!(NSApplication.shared.keyWindow?.windowController is CodeEditWindowController)) - - if let utilityAreaViewModel { - Button("Close Terminal") { - utilityAreaViewModel.removeTerminals(utilityAreaViewModel.selectedTerminals) - } - .keyboardShortcut(.delete) - } - - Divider() - - Button("Save") { - NSApp.sendAction(#selector(CodeEditWindowController.saveDocument(_:)), to: nil, from: nil) - } - .keyboardShortcut("s") - } - } -} diff --git a/CodeEdit/Features/WindowCommands/FindCommands.swift b/CodeEdit/Features/WindowCommands/FindCommands.swift deleted file mode 100644 index 0bb3c7ee12..0000000000 --- a/CodeEdit/Features/WindowCommands/FindCommands.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// FindCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/03/2023. -// - -import SwiftUI - -struct FindCommands: Commands { - - @FirstResponder var responder - - static let selector = #selector(NSTextView.performFindPanelAction(_:)) - - var hasResponder: Bool { - responder?.responds(to: Self.selector) ?? false - } - - var body: some Commands { - CommandMenu("Find") { - Group { - Button("Find...") { - send(.showFindPanel) - } - .keyboardShortcut("f") - - Button("Find and Replace...") { - send(.init(rawValue: 12)!) - } - .keyboardShortcut("f", modifiers: [.option, .command]) - - Button("Find Next") { - send(.next) - } - .keyboardShortcut("g") - - Button("Find Previous") { - send(.previous) - } - .keyboardShortcut("g", modifiers: [.shift, .command]) - - Button("Use Selection for Find") { - send(.setFindString) - } - .keyboardShortcut("e") - - Button("Jump to Selection") { - NSApp.sendAction(#selector(NSTextView.centerSelectionInVisibleArea(_:)), to: nil, from: nil) - } - .keyboardShortcut("j") - } - .disabled(!hasResponder) - } - } - - func send(_ action: NSFindPanelAction) { - let item = NSMenuItem() - item.tag = Int(action.rawValue) - responder?.perform(Self.selector, with: item) - } -} diff --git a/CodeEdit/Features/WindowCommands/HelpCommands.swift b/CodeEdit/Features/WindowCommands/HelpCommands.swift deleted file mode 100644 index d12387470c..0000000000 --- a/CodeEdit/Features/WindowCommands/HelpCommands.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// HelpCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 14/03/2023. -// - -import SwiftUI - -struct HelpCommands: Commands { - var body: some Commands { - CommandGroup(after: .help) { - Button("What's New in CodeEdit") { - - } - .disabled(true) - - Button("Release Notes") { - } - .disabled(true) - - Button("Report an Issue") { - NSApp.sendAction(#selector(AppDelegate.openFeedback(_:)), to: nil, from: nil) - } - } - } -} diff --git a/CodeEdit/Features/WindowCommands/MainCommands.swift b/CodeEdit/Features/WindowCommands/MainCommands.swift deleted file mode 100644 index d5f82c5c2f..0000000000 --- a/CodeEdit/Features/WindowCommands/MainCommands.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// MainCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/03/2023. -// - -import SwiftUI -import Sparkle - -struct MainCommands: Commands { - @Environment(\.openWindow) - var openWindow - - var body: some Commands { - CommandGroup(replacing: .appInfo) { - Button("About CodeEdit") { - openWindow(sceneID: .about) - } - - Button("Check for updates...") { - NSApp.sendAction(#selector(SPUStandardUpdaterController.checkForUpdates(_:)), to: nil, from: nil) - } - } - - CommandGroup(replacing: .appSettings) { - Button("Settings...") { - openWindow(sceneID: .settings) - } - .keyboardShortcut(",") - } - } -} diff --git a/CodeEdit/Features/WindowCommands/NavigateCommands.swift b/CodeEdit/Features/WindowCommands/NavigateCommands.swift deleted file mode 100644 index 4757ece15c..0000000000 --- a/CodeEdit/Features/WindowCommands/NavigateCommands.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// NavigateCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/03/2023. -// - -import SwiftUI - -struct NavigateCommands: Commands { - - @FocusedObject var editor: Editor? - - var body: some Commands { - CommandMenu("Navigate") { - Group { - Button("Reveal in Project Navigator") { - NSApp.sendAction(#selector(ProjectNavigatorViewController.revealFile(_:)), to: nil, from: nil) - } - .keyboardShortcut("j", modifiers: [.shift, .command]) - - Button("Reveal Changes in Navigator") { - - } - .keyboardShortcut("m", modifiers: [.shift, .command]) - .disabled(true) - - Button("Open in Next Editor") { - - } - .keyboardShortcut(",", modifiers: [.option, .command]) - .disabled(true) - - Button("Open in...") { - - } - .disabled(true) - - Divider() - - } - - Group { - Button("Show Previous Tab") { - - } - .disabled(true) - - Button("Show Next Tab") { - - } - .disabled(true) - - Divider() - - Button("Go Forward") { - editor?.goForwardInHistory() - } - .disabled(!(editor?.canGoForwardInHistory ?? false)) - - Button("Go Back") { - editor?.goBackInHistory() - } - .disabled(!(editor?.canGoBackInHistory ?? false)) - } - .disabled(editor == nil) - } - } -} diff --git a/CodeEdit/Features/WindowCommands/Utils/CommandsFixes.swift b/CodeEdit/Features/WindowCommands/Utils/CommandsFixes.swift deleted file mode 100644 index 6da317fcc9..0000000000 --- a/CodeEdit/Features/WindowCommands/Utils/CommandsFixes.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// CommandsFixes.swift -// CodeEdit -// -// Created by Wouter Hennen on 11/03/2023. -// - -import SwiftUI - -extension EventModifiers { - static var hidden: EventModifiers = .numericPad -} - -extension NSMenuItem { - @objc - fileprivate func fixAlternate(_ newValue: NSEvent.ModifierFlags) { - - if newValue.contains(.numericPad) { - isAlternate = true - fixAlternate(newValue.subtracting(.numericPad)) - } - - fixAlternate(newValue) - - if self.title == "Open Recent" { - let openRecentMenu = NSMenu(title: "Open Recent") - openRecentMenu.perform(NSSelectorFromString("_setMenuName:"), with: "NSRecentDocumentsMenu") - self.submenu = openRecentMenu - NSDocumentController.shared.value(forKey: "_installOpenRecentMenus") - } - - if self.title == "OpenWindowAction" || self.title.isEmpty { - self.isHidden = true - self.allowsKeyEquivalentWhenHidden = true - } - } - - static func swizzle() { - let origSelector = #selector(setter: NSMenuItem.keyEquivalentModifierMask) - let swizzledSelector = #selector(fixAlternate) - let originalMethodSet = class_getInstanceMethod(self as AnyClass, origSelector) - let swizzledMethodSet = class_getInstanceMethod(self as AnyClass, swizzledSelector) - - method_exchangeImplementations(originalMethodSet!, swizzledMethodSet!) - } -} diff --git a/CodeEdit/Features/WindowCommands/Utils/FirstResponderPropertyWrapper.swift b/CodeEdit/Features/WindowCommands/Utils/FirstResponderPropertyWrapper.swift deleted file mode 100644 index b2b4ef5862..0000000000 --- a/CodeEdit/Features/WindowCommands/Utils/FirstResponderPropertyWrapper.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// FirstResponderPropertyWrapper.swift -// CodeEdit -// -// Created by Wouter Hennen on 14/03/2023. -// - -import SwiftUI - -/// A property wrapper which allows for easy access to the current first responder. -/// This differs from the SwiftUI Focus System, as you get AppKit NSResponders, which you can call methods on. -/// It can also be easily checked if the current first selector accepts some event. -@propertyWrapper -struct FirstResponder: DynamicProperty { - @StateObject var helper = HelperClass() - - var wrappedValue: NSResponder? { - helper.responder - } - - class HelperClass: ObservableObject { - @Published var responder: NSResponder? = NSApp.keyWindow?.firstResponder - - init() { - NSApp.publisher(for: \.keyWindow?.firstResponder).assign(to: &$responder) - } - } -} diff --git a/CodeEdit/Features/WindowCommands/ViewCommands.swift b/CodeEdit/Features/WindowCommands/ViewCommands.swift deleted file mode 100644 index cd46e5e4b2..0000000000 --- a/CodeEdit/Features/WindowCommands/ViewCommands.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// ViewCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/03/2023. -// - -import SwiftUI - -struct ViewCommands: Commands { - @AppSettings(\.textEditing.font.size) - var editorFontSize - @AppSettings(\.terminal.font.size) - var terminalFontSize - @AppSettings(\.general.showEditorPathBar) - var showEditorPathBar - @AppSettings(\.general.dimEditorsWithoutFocus) - var dimEditorsWithoutFocus - - @State var windowController: CodeEditWindowController? - - private let documentController: CodeEditDocumentController = CodeEditDocumentController() - private let statusBarViewModel: UtilityAreaViewModel = UtilityAreaViewModel() - - @FocusedBinding(\.navigationSplitViewVisibility) - var navigationSplitViewVisibility - - @FocusedBinding(\.inspectorVisibility) - var inspectorVisibility - - var navigatorCollapsed: Bool { - windowController?.navigatorCollapsed ?? false - } - - var inspectorCollapsed: Bool { - windowController?.navigatorCollapsed ?? false - } - - var body: some Commands { - CommandGroup(after: .toolbar) { - Button("Show Command Palette") { - NSApp.sendAction(#selector(CodeEditWindowController.openCommandPalette(_:)), to: nil, from: nil) - } - .keyboardShortcut("p", modifiers: [.shift, .command]) - - Menu("Font Size") { - Button("Increase") { - if editorFontSize < 288 { - editorFontSize += 1 - } - if terminalFontSize < 288 { - terminalFontSize += 1 - } - } - .keyboardShortcut("+") - - Button("Decrease") { - if editorFontSize > 1 { - editorFontSize -= 1 - } - if terminalFontSize > 1 { - terminalFontSize -= 1 - } - } - .keyboardShortcut("-") - - Divider() - - Button("Reset") { - editorFontSize = 12 - terminalFontSize = 12 - } - .keyboardShortcut("0", modifiers: [.command, .control]) - } - .disabled(windowController == nil) - - Button("Customize Toolbar...") { - - } - .disabled(true) - - Divider() - - Button("\(navigatorCollapsed ? "Show" : "Hide") Navigator") { - windowController?.toggleFirstPanel() - } - .disabled(windowController == nil) - .keyboardShortcut("0", modifiers: [.command]) - .onReceive(NSApp.publisher(for: \.keyWindow)) { window in - windowController = window?.windowController as? CodeEditWindowController - } - - Button("\(inspectorCollapsed ? "Show" : "Hide") Inspector") { - windowController?.toggleLastPanel() - } - .disabled(windowController == nil) - .keyboardShortcut("i", modifiers: [.control, .command]) - - Button("\(inspectorCollapsed ? "Show" : "Hide") Utility Area") { - CommandManager.shared.executeCommand("open.drawer") - } - .disabled(windowController == nil) - .keyboardShortcut("y", modifiers: [.shift, .command]) - - Divider() - - Button("\(showEditorPathBar ? "Hide" : "Show") Path Bar") { - showEditorPathBar.toggle() - } - - Toggle("Dim editors without focus", isOn: $dimEditorsWithoutFocus) - - Divider() - - if let model = windowController?.navigatorSidebarViewModel { - Divider() - NavigatorCommands(model: model) - } - } - } -} - -extension ViewCommands { - struct NavigatorCommands: View { - @ObservedObject var model: NavigatorSidebarViewModel - - var body: some View { - Menu("Navigators", content: { - ForEach(Array(model.tabItems.prefix(9).enumerated()), id: \.element) { index, tab in - Button(tab.title) { - model.setNavigatorTab(tab: tab) - } - .keyboardShortcut(KeyEquivalent(Character(String(index + 1)))) - } - }) - } - } -} diff --git a/CodeEdit/Features/WindowCommands/WindowCommands.swift b/CodeEdit/Features/WindowCommands/WindowCommands.swift deleted file mode 100644 index 4a0e188144..0000000000 --- a/CodeEdit/Features/WindowCommands/WindowCommands.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// WindowCommands.swift -// CodeEdit -// -// Created by Wouter Hennen on 13/03/2023. -// - -import SwiftUI - -struct WindowCommands: Commands { - @Environment(\.openWindow) - var openWindow - - var body: some Commands { - CommandGroup(replacing: .singleWindowList) { - Button("Welcome to CodeEdit") { - openWindow(sceneID: .welcome) - } - .keyboardShortcut("1", modifiers: [.shift, .command]) - - Button("About CodeEdit") { - openWindow(sceneID: .about) - } - .keyboardShortcut("2", modifiers: [.shift, .command]) - - Button("Manage Extensions") { - openWindow(sceneID: .extensions) - } - .keyboardShortcut("3", modifiers: [.shift, .command]) - } - } -} diff --git a/CodeEdit/Utils/Environment/Env+IsFullscreen.swift b/CodeEdit/Utils/Environment/Env+IsFullscreen.swift deleted file mode 100644 index eb1c93bf63..0000000000 --- a/CodeEdit/Utils/Environment/Env+IsFullscreen.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Env+IsFullscreen.swift -// CodeEdit -// -// Created by Wouter Hennen on 14/01/2023. -// - -import SwiftUI - -private struct WorkspaceFullscreenStateEnvironmentKey: EnvironmentKey { - static let defaultValue: Bool = false -} - -extension EnvironmentValues { - var isFullscreen: Bool { - get { self[WorkspaceFullscreenStateEnvironmentKey.self] } - set { self[WorkspaceFullscreenStateEnvironmentKey.self] = newValue } - } -} diff --git a/CodeEdit/Utils/Environment/Env+Window.swift b/CodeEdit/Utils/Environment/Env+Window.swift deleted file mode 100644 index 5a18ba4d45..0000000000 --- a/CodeEdit/Utils/Environment/Env+Window.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Env+Window.swift -// CodeEdit -// -// Created by Wouter Hennen on 14/01/2023. -// - -import SwiftUI - -struct NSWindowEnvironmentKey: EnvironmentKey { - static var defaultValue = NSWindow() -} - -extension EnvironmentValues { - var window: NSWindowEnvironmentKey.Value { - get { self[NSWindowEnvironmentKey.self] } - set { self[NSWindowEnvironmentKey.self] = newValue } - } -} diff --git a/CodeEdit/Utils/Extensions/Array/Array+SortURLs.swift b/CodeEdit/Utils/Extensions/Array/Array+SortURLs.swift deleted file mode 100644 index 45b97437d7..0000000000 --- a/CodeEdit/Utils/Extensions/Array/Array+SortURLs.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Array+FileSystem.FileItem.swift -// CodeEdit -// -// Created by Matthijs Eikelenboom on 07/02/2023. -// - -import Foundation - -fileprivate extension URL { - var isFolder: Bool { - (try? resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false - } -} - -extension Array where Element == URL { - - /// Sorts the elements in alphabetical order. - /// - Parameter foldersOnTop: if set to `true` folders will always be on top of files. - /// - Returns: A sorted array of `URL` - func sortItems(foldersOnTop: Bool) -> Self { - var alphabetically = sorted { $0.lastPathComponent < $1.lastPathComponent } - - if foldersOnTop { - var foldersOnTop = alphabetically.filter { $0.isFolder } - alphabetically.removeAll { $0.isFolder } - - foldersOnTop.append(contentsOf: alphabetically) - - return foldersOnTop - } else { - return alphabetically - } - } -} - -extension Array where Element: Hashable { - - /// Checks the difference between two given items. - /// - Parameter other: Other element - /// - Returns: symmetricDifference - func difference(from other: [Element]) -> [Element] { - let thisSet = Set(self) - let otherSet = Set(other) - return Array(thisSet.symmetricDifference(otherSet)) - } - -} diff --git a/CodeEdit/Utils/Extensions/Bundle/Bundle+Info.swift b/CodeEdit/Utils/Extensions/Bundle/Bundle+Info.swift deleted file mode 100644 index 9cc0a15ea6..0000000000 --- a/CodeEdit/Utils/Extensions/Bundle/Bundle+Info.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Bundle+Info.swift -// CodeEditModules/CodeEditUtils -// -// Created by Lukas Pistrol on 01.05.22. -// - -import Foundation - -extension Bundle { - - static var copyrightString: String? { - Bundle.main.object(forInfoDictionaryKey: "NSHumanReadableCopyright") as? String - } - - /// Returns the main bundle's version string if available (e.g. 1.0.0) - static var versionString: String? { - Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String - } - - /// Returns the main bundle's build string if available (e.g. 123) - static var buildString: String? { - Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String - } - - static var versionPostfix: String? { - Bundle.main.object(forInfoDictionaryKey: "CE_VERSION_POSTFIX") as? String - } -} diff --git a/CodeEdit/Utils/Extensions/Color/Color+HEX.swift b/CodeEdit/Utils/Extensions/Color/Color+HEX.swift deleted file mode 100644 index 8a258d9dd6..0000000000 --- a/CodeEdit/Utils/Extensions/Color/Color+HEX.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Color+HEX.swift -// CodeEditModules/CodeEditUtils -// -// Created by Lukas Pistrol on 23.03.22. -// - -import SwiftUI - -extension Color { - - /// Initializes a `Color` from a HEX String (e.g.: `#1D2E3F`) and an optional alpha value. - /// - Parameters: - /// - hex: A String of a HEX representation of a color (format: `#1D2E3F`) - /// - alpha: A Double indicating the alpha value from `0.0` to `1.0` - init(hex: String, alpha: Double = 1.0) { - let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - self.init(hex: Int(int), alpha: alpha) - } - - /// Initializes a `Color` from an Int (e.g.: `0x1D2E3F`)and an optional alpha value. - /// - Parameters: - /// - hex: An Int of a HEX representation of a color (format: `0x1D2E3F`) - /// - alpha: A Double indicating the alpha value from `0.0` to `1.0` - init(hex: Int, alpha: Double = 1.0) { - let red = (hex >> 16) & 0xFF - let green = (hex >> 8) & 0xFF - let blue = hex & 0xFF - self.init(.sRGB, red: Double(red) / 255, green: Double(green) / 255, blue: Double(blue) / 255, opacity: alpha) - } - - /// Returns an Int representing the `Color` in hex format (e.g.: 0x112233) - var hex: Int { - guard let components = cgColor?.components, components.count >= 3 else { return 0 } - - let red = lround((Double(components[0]) * 255.0)) << 16 - let green = lround((Double(components[1]) * 255.0)) << 8 - let blue = lround((Double(components[2]) * 255.0)) - - return red | green | blue - } - - /// Returns a HEX String representing the `Color` (e.g.: #112233) - var hexString: String { - let color = self.hex - - return "#" + String(format: "%06x", color) - } - - /// The alpha (opacity) component of the Color (0.0 - 1.0) - var alphaComponent: Double { - NSColor(self).alphaComponent - } -} - -extension NSColor { - - /// Initializes a `NSColor` from a HEX String (e.g.: `#1D2E3F`) and an optional alpha value. - /// - Parameters: - /// - hex: A String of a HEX representation of a color (format: `#1D2E3F`) - /// - alpha: A Double indicating the alpha value from `0.0` to `1.0` - convenience init(hex: String, alpha: Double = 1.0) { - let hex = hex.trimmingCharacters(in: .alphanumerics.inverted) - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - self.init(hex: Int(int), alpha: alpha) - } - - /// Initializes a `NSColor` from an Int (e.g.: `0x1D2E3F`)and an optional alpha value. - /// - Parameters: - /// - hex: An Int of a HEX representation of a color (format: `0x1D2E3F`) - /// - alpha: A Double indicating the alpha value from `0.0` to `1.0` - convenience init(hex: Int, alpha: Double = 1.0) { - let red = (hex >> 16) & 0xFF - let green = (hex >> 8) & 0xFF - let blue = hex & 0xFF - self.init(srgbRed: Double(red) / 255, green: Double(green) / 255, blue: Double(blue) / 255, alpha: alpha) - } - - /// Returns an Int representing the `NSColor` in hex format (e.g.: 0x112233) - var hex: Int { - guard let components = cgColor.components, components.count >= 3 else { return 0 } - - let red = lround((Double(components[0]) * 255.0)) << 16 - let green = lround((Double(components[1]) * 255.0)) << 8 - let blue = lround((Double(components[2]) * 255.0)) - - return red | green | blue - } - - /// Returns a HEX String representing the `NSColor` (e.g.: #112233) - var hexString: String { - let color = self.hex - - return "#" + String(format: "%06x", color) - } -} diff --git a/CodeEdit/Utils/Extensions/Date/Date+Formatted.swift b/CodeEdit/Utils/Extensions/Date/Date+Formatted.swift deleted file mode 100644 index 0d1f2d1056..0000000000 --- a/CodeEdit/Utils/Extensions/Date/Date+Formatted.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// Date+Formatted.swift -// CodeEditModules/CodeEditUtils -// -// Created by Lukas Pistrol on 20.04.22. -// - -import Foundation - -extension Date { - - /// Returns a formatted & localized string of a relative duration compared to the current date & time - /// when the date is in `today` or `yesterday`. Otherwise it returns a formatted date in `short` - /// format. The time is omitted. - /// - Parameter locale: The locale. Defaults to `Locale.current` - /// - Returns: A localized formatted string - func relativeStringToNow(locale: Locale = .current) -> String { - if Calendar.current.isDateInToday(self) || - Calendar.current.isDateInYesterday(self) { - var style = RelativeFormatStyle( - presentation: .named, - unitsStyle: .abbreviated, - locale: .current, - calendar: .current, - capitalizationContext: .standalone - ) - - style.locale = locale - - return self.formatted(style) - } - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .none - formatter.locale = locale - - return formatter.string(from: self) - } -} diff --git a/CodeEdit/Utils/Extensions/NSTableView/NSTableView+Background.swift b/CodeEdit/Utils/Extensions/NSTableView/NSTableView+Background.swift deleted file mode 100644 index 146d3d166e..0000000000 --- a/CodeEdit/Utils/Extensions/NSTableView/NSTableView+Background.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// NSTableView+Background.swift -// CodeEdit -// -// Created by Lukas Pistrol on 20.04.22. -// - -import SwiftUI - -extension NSTableView { - /// Allows to set a lists background color in SwiftUI - override open func viewDidMoveToWindow() { - super.viewDidMoveToWindow() - - backgroundColor = NSColor.clear - enclosingScrollView?.drawsBackground = false - } -} diff --git a/CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift b/CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift deleted file mode 100644 index 67155ef85b..0000000000 --- a/CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// String+HighlightOccurrences.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 13/06/23. -// - -import Foundation -import SwiftUI - -extension String { - /// Highlights occurences of a substring in a string and returns text highlighted as such - func highlightOccurrences(_ ofSearch: String) -> some View { - if ofSearch.isEmpty { - return Text(self) - } - - let ranges = self.rangesOfSubstring(ofSearch.lowercased()) - - var currentIndex = self.startIndex - var highlightedText = Text("") - - for range in ranges { - let nonHighlightedText = self[currentIndex.. [Range] { - var ranges = [Range]() - var currentIndex = self.startIndex - - while let range = self.range( - of: substring, - options: .caseInsensitive, - range: currentIndex.. String { - var string = "" - var foundLines = 0 - var totalLength = 0 - for char in self.lazy { - if char.isNewline { - foundLines += 1 - } - totalLength += 1 - if foundLines >= lines || totalLength >= maxLength { - break - } - string.append(char) - } - return string - } - - /// Calculates the last `n` lines and returns them as a new string. - /// - Parameters: - /// - lines: The number of lines to return. - /// - maxLength: The maximum number of characters to copy. - /// - Returns: A new string containing the lines. - func getLastLines(_ lines: Int = 1, maxLength: Int = 512) -> String { - var string = "" - var foundLines = 0 - var totalLength = 0 - for char in self.lazy.reversed() { - if char.isNewline { - foundLines += 1 - } - totalLength += 1 - if foundLines >= lines || totalLength >= maxLength { - break - } - string = String(char) + string - } - return string - } -} diff --git a/CodeEdit/Utils/Extensions/String/String+MD5.swift b/CodeEdit/Utils/Extensions/String/String+MD5.swift deleted file mode 100644 index ac0f416831..0000000000 --- a/CodeEdit/Utils/Extensions/String/String+MD5.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// String+MD5.swift -// CodeEditModules/CodeEditUtils -// -// Created by Nanashi Li on 2022/04/19. -// - -import Foundation -import CryptoKit - -extension String { - - /// Returns a MD5 encrypted String of the input String - /// - /// - Parameters: - /// - trim: If `true` the input string will be trimmed from whitespaces and new-lines. Defaults to `false`. - /// - caseSensitive: If `false` the input string will be converted to lowercase characters. Defaults to `true`. - /// - Returns: A String in HEX format - func md5(trim: Bool = false, caseSensitive: Bool = true) -> String { - var string = self - - // trim whitespaces & new lines if specifiedå - if trim { string = string.trimmingCharacters(in: .whitespacesAndNewlines) } - - // make string lowercased if not case sensitive - if !caseSensitive { string = string.lowercased() } - - // compute the hash - // (note that `String.data(using: .utf8)!` is safe since it will never fail) - let computed = Insecure.MD5.hash(data: string.data(using: .utf8)!) - - // map the result to a hex string and return - return computed.compactMap { String(format: "%02x", $0) }.joined() - } -} diff --git a/CodeEdit/Utils/Extensions/String/String+Ranges.swift b/CodeEdit/Utils/Extensions/String/String+Ranges.swift deleted file mode 100644 index 26a84adcf8..0000000000 --- a/CodeEdit/Utils/Extensions/String/String+Ranges.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// String+Ranges.swift -// CodeEdit -// -// Created by Ziyuan Zhao on 2022/3/21. -// - -import Foundation - -extension StringProtocol where Index == String.Index { - func ranges( - of substring: T, - options: String.CompareOptions = [], - locale: Locale? = nil - ) -> [Range] { - var ranges: [Range] = [] - while let result = range( - of: substring, - options: options, - range: (ranges.last?.upperBound ?? startIndex).. String { - self.replacingOccurrences(of: "\n", with: "") - } - - /// Removes all `space` characters in a `String` - /// - Returns: A String - func removingSpaces() -> String { - self.replacingOccurrences(of: " ", with: "") - } -} diff --git a/CodeEdit/Utils/Extensions/String/String+SHA256.swift b/CodeEdit/Utils/Extensions/String/String+SHA256.swift deleted file mode 100644 index 89c368a032..0000000000 --- a/CodeEdit/Utils/Extensions/String/String+SHA256.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// String+SHA256.swift -// CodeEditModules/CodeEditUtils -// -// Created by Debdut Karmakar on 6/9/22. -// - -import Foundation -import CryptoKit - -extension String { - - /// Returns a SHA256 encrypted String of the input String - /// - /// - Parameters: - /// - trim: If `true` the input string will be trimmed from whitespaces and new-lines. Defaults to `false`. - /// - caseSensitive: If `false` the input string will be converted to lowercase characters. Defaults to `true`. - /// - Returns: A String in HEX format - func sha256(trim: Bool = false, caseSensitive: Bool = true) -> String { - var string = self - - // trim whitespaces & new lines if specified - if trim { string = string.trimmingCharacters(in: .whitespacesAndNewlines) } - - // make string lowercased if not case sensitive - if !caseSensitive { string = string.lowercased() } - - // compute the hash - // (note that `String.data(using: .utf8)!` is safe since it will never fail) - let computed = SHA256.hash(data: string.data(using: .utf8)!) - - // map the result to a hex string and return - return computed.compactMap { String(format: "%02x", $0) }.joined() - } -} diff --git a/CodeEdit/Utils/Extensions/SwiftTerm/Color/SwiftTerm+Color+Init.swift b/CodeEdit/Utils/Extensions/SwiftTerm/Color/SwiftTerm+Color+Init.swift deleted file mode 100644 index adb21c044a..0000000000 --- a/CodeEdit/Utils/Extensions/SwiftTerm/Color/SwiftTerm+Color+Init.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// SwiftTerm+Color+Init.swift -// CodeEditModules/TerminalEmulator -// -// Created by Lukas Pistrol on 24.03.22. -// - -import Foundation -import SwiftTerm - -extension SwiftTerm.Color { - /// 0.0-1.0 - convenience init(dRed red: Double, green: Double, blue: Double) { - let multiplier: Double = 65535 - self.init( - red: UInt16(red * multiplier), - green: UInt16(green * multiplier), - blue: UInt16(blue * multiplier) - ) - } - - /// 0-255 - convenience init(iRed red: UInt8, green: UInt8, blue: UInt8) { - let divisor: Double = 255 - self.init( - dRed: Double(red) / divisor, - green: Double(green) / divisor, - blue: Double(blue) / divisor - ) - } - - /// 0x000000 - 0xFFFFFF - convenience init(hex: Int) { - let red = UInt8((hex >> 16) & 0xFF) - let green = UInt8((hex >> 8) & 0xFF) - let blue = UInt8(hex & 0xFF) - self.init(iRed: red, green: green, blue: blue) - } - - /// 0x000000 - 0xFFFFFF - convenience init(hex: String) { - let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - self.init(hex: Int(int)) - } -} diff --git a/CodeEdit/Utils/Extensions/Text/Font+Caption3.swift b/CodeEdit/Utils/Extensions/Text/Font+Caption3.swift deleted file mode 100644 index cfab298d99..0000000000 --- a/CodeEdit/Utils/Extensions/Text/Font+Caption3.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Font+Caption3.swift -// CodeEdit -// -// Created by Wouter Hennen on 19/01/2023. -// - -import SwiftUI - -extension Font { - static var caption3: Font { .system(size: 11, weight: .medium) } -} diff --git a/CodeEdit/Utils/Extensions/URL/URL+isImage.swift b/CodeEdit/Utils/Extensions/URL/URL+isImage.swift deleted file mode 100644 index 6e3dea3372..0000000000 --- a/CodeEdit/Utils/Extensions/URL/URL+isImage.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// URL+isImage.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 14/05/23. -// - -import Foundation - -extension URL { - func isImage() -> Bool { - let ext: String = self.pathExtension.lowercased() - - // A list of supported file types by QLPreviewItem - // Some of the image file types (in UTType) are not supported by QLPreviewItem - let quickLookImageFileTypes: [String] = [ - "png", - "jpg", - "jpeg", - "bmp", - "pdf", - "heic", - "webp", - "tiff", - "gif", - "tga", - "avif", - "psd", - "svg" - ] - - if quickLookImageFileTypes.contains(ext) { - return true - } else { - return false - } - } -} diff --git a/CodeEdit/Utils/Extensions/View/View+focusedValue.swift b/CodeEdit/Utils/Extensions/View/View+focusedValue.swift deleted file mode 100644 index 8e46de9017..0000000000 --- a/CodeEdit/Utils/Extensions/View/View+focusedValue.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// View+focusedValue.swift -// CodeEdit -// -// Created by Wouter Hennen on 18/06/2023. -// - -import SwiftUI - -extension View { - func focusedValue( - _ keyPath: WritableKeyPath, - disabled: Bool, - _ value: Value - ) -> some View { - focusedValue(keyPath, disabled ? nil : value) - } -} diff --git a/CodeEdit/Utils/Extensions/View/View+isHovering.swift b/CodeEdit/Utils/Extensions/View/View+isHovering.swift deleted file mode 100644 index 570f574c31..0000000000 --- a/CodeEdit/Utils/Extensions/View/View+isHovering.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// View+isHovering.swift -// CodeEditModules/StatusBar -// -// Created by Lukas Pistrol on 22.03.22. -// - -import SwiftUI - -extension View { - - /// Changes the cursor appearance when hovering attached View - /// - Parameters: - /// - active: onHover() value - /// - isDragging: indicate that dragging is happening. If true this will not change the cursor. - /// - cursor: the cursor to display on hover - func isHovering(_ active: Bool, isDragging: Bool = false, cursor: NSCursor = .arrow) { - if isDragging { return } - if active { - cursor.push() - } else { - NSCursor.pop() - } - } -} diff --git a/CodeEdit/Utils/FocusedValues.swift b/CodeEdit/Utils/FocusedValues.swift deleted file mode 100644 index c1e9839386..0000000000 --- a/CodeEdit/Utils/FocusedValues.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// FocusedValues.swift -// CodeEdit -// -// Created by Wouter Hennen on 18/06/2023. -// - -import SwiftUI - -extension FocusedValues { - var navigationSplitViewVisibility: Binding? { - get { self[NavSplitViewVisibilityFocusedValueKey.self] } - set { self[NavSplitViewVisibilityFocusedValueKey.self] = newValue } - } - - var inspectorVisibility: Binding? { - get { self[InspectorVisibilityFocusedValueKey.self] } - set { self[InspectorVisibilityFocusedValueKey.self] = newValue } - } - - private struct NavSplitViewVisibilityFocusedValueKey: FocusedValueKey { - typealias Value = Binding - } - - private struct InspectorVisibilityFocusedValueKey: FocusedValueKey { - typealias Value = Binding - } -} diff --git a/CodeEdit/Utils/KeyChain/CodeEditKeychain.swift b/CodeEdit/Utils/KeyChain/CodeEditKeychain.swift deleted file mode 100644 index adf10ec9c0..0000000000 --- a/CodeEdit/Utils/KeyChain/CodeEditKeychain.swift +++ /dev/null @@ -1,293 +0,0 @@ -// -// CodeEditKeychain.swift -// CodeEditModules/CodeEditUtils -// -// Created by Nanashi Li on 2022/04/14. -// - -import Foundation -import Security - -// TODO: DOCS (Nanashi Li) -class CodeEditKeychain { - - var lastQueryParameters: [String: Any]? // Used by the unit tests - - /// Contains result code from the last operation. Value is noErr (0) for a successful result. - var lastResultCode: OSStatus = noErr - - var keyPrefix = "" // Can be useful in test. - - /** - Specify an access group that will be used to access keychain items. - Access groups can be used to share keychain items between applications. - When access group value is nil all application access groups are being accessed. - Access group name is used by all functions: set, get, delete and clear. - */ - var accessGroup: String? - - private let lock = NSLock() - - init() { } - - /** - - parameter keyPrefix: a prefix that is added before the key in get/set methods. - Note that `clear` method still clears everything from the Keychain. - */ - init(keyPrefix: String) { - self.keyPrefix = keyPrefix - } - - /** - Stores the text value in the keychain item under the given key. - - parameter key: Key under which the text value is stored in the keychain. - - parameter value: Text string to be written to the keychain. - - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. - By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only - while the device is unlocked by the user. - - returns: True if the text was successfully written to the keychain. - */ - @discardableResult - func set( - _ value: String, - forKey key: String, - withAccess access: CodeEditKeychainAccessOptions? = nil - ) -> Bool { - if let value = value.data(using: String.Encoding.utf8) { - return set(value, forKey: key, withAccess: access) - } - return false - } - - /** - Stores the data in the keychain item under the given key. - - parameter key: Key under which the data is stored in the keychain. - - parameter value: Data to be written to the keychain. - - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. - By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed - only while the device is unlocked by the user. - - returns: True if the text was successfully written to the keychain. - */ - @discardableResult - func set( - _ value: Data, - forKey key: String, - withAccess access: CodeEditKeychainAccessOptions? = nil - ) -> Bool { - // The lock prevents the code to be run simultaneously - // from multiple threads which may result in crashing - lock.lock() - defer { lock.unlock() } - - deleteNoLock(key) // Delete any existing key before saving it - let accessible = access?.value ?? CodeEditKeychainAccessOptions.defaultOption.value - - let prefixedKey = keyWithPrefix(key) - - var query: [String: Any] = [ - CodeEditKeychainConstants.class: kSecClassGenericPassword, - CodeEditKeychainConstants.attrAccount: prefixedKey, - CodeEditKeychainConstants.valueData: value, - CodeEditKeychainConstants.accessible: accessible - ] - - query = addAccessGroupWhenPresent(query) - lastQueryParameters = query - - lastResultCode = SecItemAdd(query as CFDictionary, nil) - - return lastResultCode == noErr - } - - /** - Stores the boolean value in the keychain item under the given key. - - parameter key: Key under which the value is stored in the keychain. - - parameter value: Boolean to be written to the keychain. - - parameter withAccess: Value that indicates when your app needs access to the value in the keychain item. - By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed - only while the device is unlocked by the user. - - returns: True if the value was successfully written to the keychain. - */ - @discardableResult - func set( - _ value: Bool, - forKey key: String, - withAccess access: CodeEditKeychainAccessOptions? = nil - ) -> Bool { - let bytes: [UInt8] = value ? [1] : [0] - let data = Data(bytes) - - return set(data, forKey: key, withAccess: access) - } - - /** - Retrieves the text value from the keychain that corresponds to the given key. - - parameter key: The key that is used to read the keychain item. - - returns: The text value from the keychain. Returns nil if unable to read the item. - */ - func get(_ key: String) -> String? { - if let data = getData(key) { - - if let currentString = String(data: data, encoding: .utf8) { - return currentString - } - - lastResultCode = -67853 // errSecInvalidEncoding - } - - return nil - } - - /** - Retrieves the data from the keychain that corresponds to the given key. - - parameter key: The key that is used to read the keychain item. - - parameter asReference: If true, returns the data as reference (needed for things like NEVPNProtocol). - - returns: The text value from the keychain. Returns nil if unable to read the item. - */ - func getData(_ key: String, asReference: Bool = false) -> Data? { - // The lock prevents the code to be run simultaneously - // from multiple threads which may result in crashing - lock.lock() - defer { lock.unlock() } - - let prefixedKey = keyWithPrefix(key) - - var query: [String: Any] = [ - CodeEditKeychainConstants.class: kSecClassGenericPassword, - CodeEditKeychainConstants.attrAccount: prefixedKey, - CodeEditKeychainConstants.matchLimit: kSecMatchLimitOne - ] - - if asReference { - query[CodeEditKeychainConstants.returnReference] = kCFBooleanTrue - } else { - query[CodeEditKeychainConstants.returnData] = kCFBooleanTrue - } - - query = addAccessGroupWhenPresent(query) - lastQueryParameters = query - - var result: AnyObject? - - lastResultCode = withUnsafeMutablePointer(to: &result) { - SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) - } - - if lastResultCode == noErr { - return result as? Data - } - - return nil - } - - /** - Retrieves the boolean value from the keychain that corresponds to the given key. - - parameter key: The key that is used to read the keychain item. - - returns: The boolean value from the keychain. Returns nil if unable to read the item. - */ - func getBool(_ key: String) -> Bool? { - guard let data = getData(key) else { return nil } - guard let firstBit = data.first else { return nil } - return firstBit == 1 - } - - /** - Deletes the single keychain item specified by the key. - - parameter key: The key that is used to delete the keychain item. - - returns: True if the item was successfully deleted. - */ - @discardableResult - func delete(_ key: String) -> Bool { - // The lock prevents the code to be run simultaneously - // from multiple threads which may result in crashing - lock.lock() - defer { lock.unlock() } - - return deleteNoLock(key) - } - - /** - Return all keys from keychain - - returns: An string array with all keys from the keychain. - */ - var allKeys: [String] { - var query: [String: Any] = [ - CodeEditKeychainConstants.class: kSecClassGenericPassword, - CodeEditKeychainConstants.returnData: true, - CodeEditKeychainConstants.returnAttributes: true, - CodeEditKeychainConstants.returnReference: true, - CodeEditKeychainConstants.matchLimit: CodeEditKeychainConstants.secMatchLimitAll - ] - - query = addAccessGroupWhenPresent(query) - - var result: AnyObject? - - let lastResultCode = withUnsafeMutablePointer(to: &result) { - SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) - } - - if lastResultCode == noErr { - return (result as? [[String: Any]])?.compactMap { - $0[CodeEditKeychainConstants.attrAccount] as? String } ?? [] - } - - return [] - } - - /** - Same as `delete` but is only accessed internally, since it is not thread safe. - - parameter key: The key that is used to delete the keychain item. - - returns: True if the item was successfully deleted. - */ - @discardableResult - func deleteNoLock(_ key: String) -> Bool { - let prefixedKey = keyWithPrefix(key) - - var query: [String: Any] = [ - CodeEditKeychainConstants.class: kSecClassGenericPassword, - CodeEditKeychainConstants.attrAccount: prefixedKey - ] - - query = addAccessGroupWhenPresent(query) - lastQueryParameters = query - - lastResultCode = SecItemDelete(query as CFDictionary) - - return lastResultCode == noErr - } - - /** - Deletes all Keychain items used by the app. - Note that this method deletes all items regardless of the prefix settings used for initializing the class. - - returns: True if the keychain items were successfully deleted. - */ - @discardableResult - func clear() -> Bool { - // The lock prevents the code to be run simultaneously - // from multiple threads which may result in crashing - lock.lock() - defer { lock.unlock() } - - var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword ] - query = addAccessGroupWhenPresent(query) - lastQueryParameters = query - - lastResultCode = SecItemDelete(query as CFDictionary) - - return lastResultCode == noErr - } - - /// Returns the key with currently set prefix. - func keyWithPrefix(_ key: String) -> String { - "\(keyPrefix)\(key)" - } - - func addAccessGroupWhenPresent(_ items: [String: Any]) -> [String: Any] { - guard let accessGroup else { return items } - - var result: [String: Any] = items - result[CodeEditKeychainConstants.accessGroup] = accessGroup - return result - } -} diff --git a/CodeEdit/Utils/KeyChain/CodeEditKeychainConstants.swift b/CodeEdit/Utils/KeyChain/CodeEditKeychainConstants.swift deleted file mode 100644 index 6171c999c8..0000000000 --- a/CodeEdit/Utils/KeyChain/CodeEditKeychainConstants.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// CodeEditKeychainConstants.swift -// CodeEditModules/CodeEditUtils -// -// Created by Nanashi Li on 2022/04/14. -// - -import Foundation -import Security - -/// Constants used by the library -enum CodeEditKeychainConstants { - /// Specifies a Keychain access group. Used for sharing Keychain items between apps. - static var accessGroup: String { toString(kSecAttrAccessGroup) } - - /** - A value that indicates when your app needs access to the data in a keychain item. - The default value is AccessibleWhenUnlocked. - For a list of possible values, see CodeEditKeychainAccessOptions. - */ - static var accessible: String { toString(kSecAttrAccessible) } - - /// Used for specifying a String key when setting/getting a Keychain value. - static var attrAccount: String { toString(kSecAttrAccount) } - - /// Used for specifying synchronization of keychain items between devices. - static var attrSynchronizable: String { toString(kSecAttrSynchronizable) } - - /// An item class key used to construct a Keychain search dictionary. - static var `class`: String { toString(kSecClass) } - - /// Specifies the number of values returned from the keychain. The library only supports single values. - static var matchLimit: String { toString(kSecMatchLimit) } - - /// A return data type used to get the data from the Keychain. - static var returnData: String { toString(kSecReturnData) } - - /// Used for specifying a value when setting a Keychain value. - static var valueData: String { toString(kSecValueData) } - - /// Used for returning a reference to the data from the keychain - static var returnReference: String { toString(kSecReturnPersistentRef) } - - /// A key whose value is a Boolean indicating whether or not to return item attributes - static var returnAttributes: String { toString(kSecReturnAttributes) } - - /// A value that corresponds to matching an unlimited number of items - static var secMatchLimitAll: String { toString(kSecMatchLimitAll) } - - static func toString(_ value: CFString) -> String { - value as String - } -} diff --git a/CodeEdit/Utils/KeyChain/KeychainSwiftAccessOptions.swift b/CodeEdit/Utils/KeyChain/KeychainSwiftAccessOptions.swift deleted file mode 100644 index d98b746364..0000000000 --- a/CodeEdit/Utils/KeyChain/KeychainSwiftAccessOptions.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// CodeEditKeychainAccessOptions.swift -// CodeEditModules/CodeEditUtils -// -// Created by Nanashi Li on 2022/04/14. -// - -import Security - -/** - These options are used to determine when a keychain item should be readable. - The default value is AccessibleWhenUnlocked. - */ -enum CodeEditKeychainAccessOptions { - - /** - The data in the keychain item can be accessed only while the device is unlocked by the user. - - This is recommended for items that need to be accessible only while the application is in the foreground. - Items with this attribute migrate to a new device when using encrypted backups. - - This is the default value for keychain items added without explicitly setting an accessibility constant. - */ - case accessibleWhenUnlocked - - /** - The data in the keychain item can be accessed only while the device is unlocked by the user. - - This is recommended for items that need to be accessible only while the application is in the foreground. - Items with this attribute do not migrate to a new device. - Thus, after restoring from a backup of a different device, these items will not be present. - */ - case accessibleWhenUnlockedThisDeviceOnly - - /** - The data in the keychain item cannot be accessed after a restart until the device has - been unlocked once by the user. - - After the first unlock, the data remains accessible until the next restart. - This is recommended for items that need to be accessed by background applications. - Items with this attribute migrate to a new device when using encrypted backups. - */ - case accessibleAfterFirstUnlock - - /** - The data in the keychain item cannot be accessed after a restart until the device has - been unlocked once by the user. - - After the first unlock, the data remains accessible until the next restart. - This is recommended for items that need to be accessed by background applications. - Items with this attribute do not migrate to a new device. - Thus, after restoring from a backup of a different device, these items will not be present. - */ - case accessibleAfterFirstUnlockThisDeviceOnly - - /** - The data in the keychain can only be accessed when the device is unlocked. - Only available if a passcode is set on the device. - - This is recommended for items that only need to be accessible while the application is in the foreground. - Items with this attribute never migrate to a new device. - After a backup is restored to a new device, these items are missing. - No items can be stored in this class on devices without a passcode. - Disabling the device passcode causes all items in this class to be deleted. - */ - case accessibleWhenPasscodeSetThisDeviceOnly - - static var defaultOption: CodeEditKeychainAccessOptions { - .accessibleWhenUnlocked - } - - var value: String { - switch self { - case .accessibleWhenUnlocked: - return toString(kSecAttrAccessibleWhenUnlocked) - - case .accessibleWhenUnlockedThisDeviceOnly: - return toString(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) - - case .accessibleAfterFirstUnlock: - return toString(kSecAttrAccessibleAfterFirstUnlock) - - case .accessibleAfterFirstUnlockThisDeviceOnly: - return toString(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) - - case .accessibleWhenPasscodeSetThisDeviceOnly: - return toString(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) - } - } - - func toString(_ value: CFString) -> String { - CodeEditKeychainConstants.toString(value) - } -} diff --git a/CodeEdit/Utils/Protocols/Loopable.swift b/CodeEdit/Utils/Protocols/Loopable.swift deleted file mode 100644 index 85a8af3e5b..0000000000 --- a/CodeEdit/Utils/Protocols/Loopable.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Loopable.swift -// CodeEditModules/Settings -// -// Created by Lukas Pistrol on 03.04.22. -// - -import Foundation - -/// Loopable protocol implements a method that will return all child -/// properties and their associated values of a `Type` -protocol Loopable { - func allProperties() throws -> [String: Any] -} - -extension Loopable { - - /// returns all child properties and their associated values of `self` - /// - /// **Example:** - /// ```swift - /// struct Author: Loopable { - /// var name: String = "Steve" - /// var books: Int = 4 - /// } - /// - /// let author = Author() - /// print(author.allProperties()) - /// - /// // returns - /// ["name": "Steve", "books": 4] - /// ``` - func allProperties() throws -> [String: Any] { - var result: [String: Any] = [:] - - let mirror = Mirror(reflecting: self) - - guard let style = mirror.displayStyle, style == .struct || style == .class else { - // TODO: Throw a proper error - throw NSError() // swiftlint:disable:this discouraged_direct_init - } - - for (property, value) in mirror.children { - guard let property else { - continue - } - - result[property] = value - } - - return result - } -} diff --git a/CodeEdit/Utils/Protocols/SearchableSettingsPage.swift b/CodeEdit/Utils/Protocols/SearchableSettingsPage.swift deleted file mode 100644 index 9784091e77..0000000000 --- a/CodeEdit/Utils/Protocols/SearchableSettingsPage.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SearchableSettingsPage.swift -// CodeEdit -// -// Created by Raymond Vleeshouwer on 07/07/23. -// - -import Foundation - -protocol SearchableSettingsPage { - var searchKeys: [String] { get } -} diff --git a/CodeEdit/Utils/ShellClient/Models/ShellClient.swift b/CodeEdit/Utils/ShellClient/Models/ShellClient.swift deleted file mode 100644 index 70c1eaad6a..0000000000 --- a/CodeEdit/Utils/ShellClient/Models/ShellClient.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// ShellClient.swift -// CodeEdit -// -// Created by Matthijs Eikelenboom on 25/11/2022. -// - -import Combine -import Foundation - -/// Shell Client -/// Run commands in shell -class ShellClient { - /// Generate a process and pipe to run commands - /// - Parameter args: commands to run - /// - Returns: command output - func generateProcessAndPipe(_ args: [String]) -> (Process, Pipe) { - var arguments = ["-c"] - arguments.append(contentsOf: args) - let task = Process() - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe - task.arguments = arguments - task.executableURL = URL(fileURLWithPath: "/bin/zsh") - return (task, pipe) - } - - /// Cancellable tasks - var cancellables: [UUID: AnyCancellable] = [:] - - /// Run a command - /// - Parameter args: command to run - /// - Returns: command output - @discardableResult - func run(_ args: String...) throws -> String { - let (task, pipe) = generateProcessAndPipe(args) - try task.run() - let data = pipe.fileHandleForReading.readDataToEndOfFile() - return String(data: data, encoding: .utf8) ?? "" - } - - /// Run a command with Publisher - /// - Parameter args: command to run - /// - Returns: command output - @discardableResult - func runLive(_ args: String...) -> AnyPublisher { - let subject = PassthroughSubject() - let (task, pipe) = generateProcessAndPipe(args) - let outputHandler = pipe.fileHandleForReading - // wait for the data to come in and then notify - // the Notification with Name: `NSFileHandleDataAvailable` - outputHandler.waitForDataInBackgroundAndNotify() - let id = UUID() - self.cancellables[id] = NotificationCenter - .default - .publisher(for: .NSFileHandleDataAvailable, object: outputHandler) - .sink { _ in - let data = outputHandler.availableData - guard !data.isEmpty else { - // if no data is available anymore - // we should cancel this cancellable - // and mark the subject as finished - self.cancellables.removeValue(forKey: id) - subject.send(completion: .finished) - return - } - if let line = String(data: data, encoding: .utf8)? - .split(whereSeparator: \.isNewline) { - line - .map(String.init) - .forEach(subject.send(_:)) - } - outputHandler.waitForDataInBackgroundAndNotify() - } - task.launch() - return subject.eraseToAnyPublisher() - } - - /// Run a command with AsyncStream - /// - Parameter args: command to run - /// - Returns: async stream of command output - func runAsync(_ args: String...) -> AsyncThrowingStream { - let (task, pipe) = generateProcessAndPipe(args) - - return AsyncThrowingStream { continuation in - pipe.fileHandleForReading.readabilityHandler = { [unowned pipe] fileHandle in - let data = fileHandle.availableData - if !data.isEmpty { - if let line = String(data: data, encoding: .utf8)?.split(whereSeparator: \.isNewline) { - line.map(String.init).forEach({ continuation.yield($0) }) - } - } else { - if !task.isRunning && task.terminationStatus != 0 { - continuation.finish( - throwing: NSError(domain: "ShellClient", code: Int(task.terminationStatus)) - ) - } else { - continuation.finish() - } - - // Clean up the handler to prevent repeated calls and continuation finishes for the same - // process. - pipe.fileHandleForReading.readabilityHandler = nil - } - } - - do { - try task.run() - } catch { - continuation.finish(throwing: error) - } - } - } - - /// Shell client - /// - Returns: description - static func live() -> ShellClient { - return ShellClient() - } -} diff --git a/CodeEdit/WindowObserver.swift b/CodeEdit/WindowObserver.swift deleted file mode 100644 index b37c04ecd8..0000000000 --- a/CodeEdit/WindowObserver.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// WindowObserver.swift -// CodeEdit -// -// Created by Wouter Hennen on 14/01/2023. -// - -import SwiftUI - -struct WindowObserver: View { - - var window: NSWindow - - @ViewBuilder var content: Content - - /// The fullscreen state of the NSWindow. - /// This will be passed into all child views as an environment variable. - @State private var isFullscreen = false - - @AppSettings(\.general.tabBarStyle) - var tabBarStyle - - @State var modifierFlags: NSEvent.ModifierFlags = [] - - var body: some View { - content - .environment(\.modifierKeys, modifierFlags.intersection(.deviceIndependentFlagsMask)) - .onReceive(NSEvent.publisher(scope: .local, matching: .flagsChanged)) { output in - modifierFlags = output.modifierFlags - } - .environment(\.window, window) - .environment(\.isFullscreen, isFullscreen) - .onReceive(NotificationCenter.default.publisher(for: NSWindow.didEnterFullScreenNotification)) { _ in - self.isFullscreen = true - } - .onReceive(NotificationCenter.default.publisher(for: NSWindow.willExitFullScreenNotification)) { _ in - self.isFullscreen = false - } - // When tab bar style is changed, update NSWindow configuration as follows. - .onChange(of: tabBarStyle) { newStyle in - DispatchQueue.main.async { - if newStyle == .native { - window.titlebarSeparatorStyle = .none - } else { - window.titlebarSeparatorStyle = .automatic - } - } - } - } -} diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift deleted file mode 100644 index 94f4db792b..0000000000 --- a/CodeEdit/WorkspaceView.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// WorkspaceView.swift -// CodeEdit -// -// Created by Austin Condiff on 3/10/22. -// - -import SwiftUI - -struct WorkspaceView: View { - @Environment(\.window) - private var window: NSWindow - - @Environment(\.colorScheme) - private var colorScheme - - @FocusState var focusedEditor: Editor? - - @AppSettings(\.theme.matchAppearance) - var matchAppearance - - @EnvironmentObject private var workspace: WorkspaceDocument - @EnvironmentObject private var editorManager: EditorManager - @EnvironmentObject private var utilityAreaModel: UtilityAreaViewModel - - @StateObject private var themeModel: ThemeModel = .shared - - @State private var showingAlert = false - @State private var terminalCollapsed = true - @State private var editorCollapsed = false - - private var keybindings: KeybindingManager = .shared - - var body: some View { - if workspace.workspaceFileManager != nil { - VStack { - SplitViewReader { proxy in - SplitView(axis: .vertical) { - EditorLayoutView( - layout: editorManager.isFocusingActiveEditor - ? editorManager.activeEditor.getEditorLayout() ?? editorManager.editorLayout - : editorManager.editorLayout, - focus: $focusedEditor - ) - .collapsable() - .collapsed($utilityAreaModel.isMaximized) - .frame(minHeight: 170 + 29 + 29) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .holdingPriority(.init(1)) - .safeAreaInset(edge: .bottom, spacing: 0) { - StatusBarView(proxy: proxy) - } - UtilityAreaView() - .collapsable() - .collapsed($utilityAreaModel.isCollapsed) - .frame(idealHeight: 260) - .frame(minHeight: 100) - } - .edgesIgnoringSafeArea(.top) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .onChange(of: focusedEditor) { newValue in - /// update active tab group only if the new one is not the same with it. - if let newValue, editorManager.activeEditor != newValue { - editorManager.activeEditor = newValue - } - } - .onChange(of: editorManager.activeEditor) { newValue in - if newValue != focusedEditor { - focusedEditor = newValue - } - } - .onChange(of: colorScheme) { newValue in - if matchAppearance { - themeModel.selectedTheme = newValue == .dark - ? themeModel.selectedDarkTheme - : themeModel.selectedLightTheme - } - } - .onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)) { output in - if let window = output.object as? NSWindow, self.window == window { - workspace.addToWorkspaceState( - key: .workspaceWindowSize, - value: NSStringFromRect(window.frame) - ) - } - } - } - } - .background(EffectView(.contentBackground)) - } - } -} diff --git a/CodeEdit/World.swift b/CodeEdit/World.swift deleted file mode 100644 index 107f4dce09..0000000000 --- a/CodeEdit/World.swift +++ /dev/null @@ -1,6 +0,0 @@ -var currentWorld: World = .init() - -// Inspired by: https://vimeo.com/291588126 -struct World { - var shellClient: ShellClient = .live() -} diff --git a/OpenWithCodeEdit/FinderSync.swift b/OpenWithCodeEdit/FinderSync.swift index 56463a5bed..fb497853e1 100644 --- a/OpenWithCodeEdit/FinderSync.swift +++ b/OpenWithCodeEdit/FinderSync.swift @@ -11,6 +11,7 @@ * open "console.app" to debug, */ +#if os(macOS) import Cocoa import FinderSync import os.log @@ -97,3 +98,4 @@ class CEOpenWith: FIFinderSync { return menu } } +#endif