From 8874011cad3ded4ab19b697303ccf79cf1954689 Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Fri, 29 Mar 2024 10:17:21 +0100 Subject: [PATCH 01/10] Added workspace settings related files --- CodeEdit.xcodeproj/project.pbxproj | 72 ++++ .../xcshareddata/swiftpm/Package.resolved | 3 +- CodeEdit/CodeEditApp.swift | 2 + .../CEWorkspaceSettingsWindow.swift | 24 ++ ...orkspaceSettingsData+ProjectSettings.swift | 334 ++++++++++++++++++ ...EWorkspaceSettingsData+TasksSettings.swift | 188 ++++++++++ .../Models/CEWorkspaceSettings.swift | 94 +++++ .../Models/CEWorkspaceSettingsData.swift | 57 +++ .../Models/CEWorkspaceSettingsPage.swift | 51 +++ .../CEWorkspaceSettingsSearchResult.swift | 25 ++ .../Models/PageAndCEWorkspaceSettings.swift | 19 + .../Views/CEWorkspaceSettingsPageView.swift | 57 +++ .../Views/CEWorkspaceSettingsView.swift | 51 +++ .../ProjectCEWorkspaceSettingsView.swift | 32 ++ .../Pages/TasksCEWorkspaceSettingsView.swift | 64 ++++ .../WindowCommands/FileCommands.swift | 15 + CodeEdit/SceneID.swift | 1 + 17 files changed, 1088 insertions(+), 1 deletion(-) create mode 100644 CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift create mode 100644 CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift create mode 100644 CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift create mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift create mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift create mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift create mode 100644 CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift create mode 100644 CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsPageView.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 3e9c94bc4f..fdd3905f65 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -368,6 +368,18 @@ 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 */; }; + 77A01E1D2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E1C2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift */; }; + 77A01E1F2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E1E2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift */; }; + 77A01E232BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E222BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift */; }; + 77A01E252BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */; }; + 77A01E272BB4249800F0EA38 /* CEWorkspaceSettingsData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E262BB4249800F0EA38 /* CEWorkspaceSettingsData.swift */; }; + 77A01E2A2BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E292BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift */; }; + 77A01E2C2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E2B2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift */; }; + 77A01E2E2BB4261200F0EA38 /* CEWorkspaceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E2D2BB4261200F0EA38 /* CEWorkspaceSettings.swift */; }; + 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E2F2BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift */; }; + 77A01E322BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */; }; + 77A01E342BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */; }; + 77A01E362BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.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 */; }; @@ -893,6 +905,18 @@ 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 = ""; }; + 77A01E1C2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsWindow.swift; sourceTree = ""; }; + 77A01E1E2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsView.swift; sourceTree = ""; }; + 77A01E222BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsPage.swift; sourceTree = ""; }; + 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageAndCEWorkspaceSettings.swift; sourceTree = ""; }; + 77A01E262BB4249800F0EA38 /* CEWorkspaceSettingsData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsData.swift; sourceTree = ""; }; + 77A01E292BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CEWorkspaceSettingsData+ProjectSettings.swift"; sourceTree = ""; }; + 77A01E2B2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CEWorkspaceSettingsData+TasksSettings.swift"; sourceTree = ""; }; + 77A01E2D2BB4261200F0EA38 /* CEWorkspaceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettings.swift; sourceTree = ""; }; + 77A01E2F2BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectCEWorkspaceSettingsView.swift; sourceTree = ""; }; + 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksCEWorkspaceSettingsView.swift; sourceTree = ""; }; + 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsSearchResult.swift; sourceTree = ""; }; + 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsPageView.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 = ""; }; @@ -2051,6 +2075,9 @@ isa = PBXGroup; children = ( 588847652992A35800996D95 /* Models */, + 77A01E1A2BB33F1E00F0EA38 /* Views */, + 77A01E1B2BB33F3D00F0EA38 /* Extensions */, + 77A01E1C2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift */, ); path = CEWorkspace; sourceTree = ""; @@ -2063,7 +2090,12 @@ 58A2E40629C3975D005CB615 /* CEWorkspaceFileIcon.swift */, 58710158298EB80000951BA4 /* CEWorkspaceFileManager.swift */, 6CB52DC82AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift */, + 77A01E2D2BB4261200F0EA38 /* CEWorkspaceSettings.swift */, + 77A01E262BB4249800F0EA38 /* CEWorkspaceSettingsData.swift */, + 77A01E222BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift */, + 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */, 6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */, + 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */, ); path = Models; sourceTree = ""; @@ -2424,6 +2456,34 @@ path = Text; sourceTree = ""; }; + 77A01E1A2BB33F1E00F0EA38 /* Views */ = { + isa = PBXGroup; + children = ( + 77A01E372BB4291700F0EA38 /* Pages */, + 77A01E1E2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift */, + 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 77A01E1B2BB33F3D00F0EA38 /* Extensions */ = { + isa = PBXGroup; + children = ( + 77A01E292BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift */, + 77A01E2B2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 77A01E372BB4291700F0EA38 /* Pages */ = { + isa = PBXGroup; + children = ( + 77A01E2F2BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift */, + 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */, + ); + path = Pages; + sourceTree = ""; + }; 85CD0C5D2A10CC2500E531FD /* URL */ = { isa = PBXGroup; children = ( @@ -3201,6 +3261,7 @@ B65B11012B09D5D4002852CF /* GitClient+Pull.swift in Sources */, 2072FA13280D74ED00C7F8D4 /* HistoryInspectorModel.swift in Sources */, 852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */, + 77A01E1F2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift in Sources */, 587B9DA029300ABD00AC7927 /* PanelDivider.swift in Sources */, 58822534292C280D00E83CDE /* CursorLocation.swift in Sources */, 201169E52837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift in Sources */, @@ -3231,11 +3292,13 @@ 613899B92B6E704500A5CAF6 /* String+Normalise.swift in Sources */, 04BA7C192AE2D7C600584E1C /* GitClient+Branches.swift in Sources */, 587B9E8829301D8F00AC7927 /* GitHubFiles.swift in Sources */, + 77A01E232BB423A800F0EA38 /* CEWorkspaceSettingsPage.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 */, + 77A01E2E2BB4261200F0EA38 /* CEWorkspaceSettings.swift in Sources */, 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */, 587B9E6F29301D8F00AC7927 /* GitLabProjectAccess.swift in Sources */, 587B9E6929301D8F00AC7927 /* GitLabEvent.swift in Sources */, @@ -3282,6 +3345,7 @@ B628B7B72B223BAD00F9775A /* FindModePicker.swift in Sources */, 587B9E6E29301D8F00AC7927 /* GitLabProject.swift in Sources */, 58798234292E30B90085B254 /* FeedbackIssueArea.swift in Sources */, + 77A01E2A2BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift in Sources */, 852C7E332A587279006BA599 /* SearchableSettingsPage.swift in Sources */, 587B9E5F29301D8F00AC7927 /* GitLabProjectRouter.swift in Sources */, 587B9E7329301D8F00AC7927 /* GitRouter.swift in Sources */, @@ -3330,6 +3394,7 @@ 613899B12B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift in Sources */, 581550D129FBD30400684881 /* TextTableViewCell.swift in Sources */, 587B9E6629301D8F00AC7927 /* GitLabProjectHook.swift in Sources */, + 77A01E1D2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift in Sources */, 587B9E9329301D8F00AC7927 /* BitBucketOAuthConfiguration.swift in Sources */, 6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */, 58F2EB0A292FB2B0004A9BDE /* SettingsData.swift in Sources */, @@ -3337,6 +3402,7 @@ B6EA1FE729DA341D001BF195 /* Theme.swift in Sources */, 587B9E7529301D8F00AC7927 /* String+QueryParameters.swift in Sources */, B60718312B15A9A3009CDAB4 /* CEOutlineGroup.swift in Sources */, + 77A01E252BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift in Sources */, 58798219292D92370085B254 /* SearchModeModel.swift in Sources */, 6C5C891B2A3F736500A94FE1 /* FocusedValues.swift in Sources */, 611192062B08CCF600D4459B /* SearchIndexer+Add.swift in Sources */, @@ -3346,6 +3412,7 @@ 58D01C9D293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift in Sources */, B6E41C8B29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift in Sources */, 6C2C155A29B4F4CC00EA60A5 /* Variadic.swift in Sources */, + 77A01E322BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift in Sources */, B6E41C8F29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift in Sources */, 5882252B292C280D00E83CDE /* StatusBarCursorLocationLabel.swift in Sources */, 58798252292E78D80085B254 /* ImageFileView.swift in Sources */, @@ -3404,6 +3471,7 @@ 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */, B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */, 58822529292C280D00E83CDE /* StatusBarLineEndSelector.swift in Sources */, + 77A01E342BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift in Sources */, 5C4BB1E128212B1E00A92FB2 /* World.swift in Sources */, 581550D029FBD30400684881 /* FileSystemTableViewCell.swift in Sources */, B607183F2B17DB07009CDAB4 /* SourceControlNavigatorRepositoryView+contextMenu.swift in Sources */, @@ -3521,6 +3589,7 @@ B6EA1FFD29DB792C001BF195 /* ThemeSettingsColorPreview.swift in Sources */, 2806E904297958B9000040F4 /* ContributorRowView.swift in Sources */, 6C578D8C29CD372700DC73B2 /* ExtensionCommands.swift in Sources */, + 77A01E272BB4249800F0EA38 /* CEWorkspaceSettingsData.swift in Sources */, B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */, B6EA1FF829DB78DB001BF195 /* ThemeSettingThemeRow.swift in Sources */, 587B9E7629301D8F00AC7927 /* GitTime.swift in Sources */, @@ -3557,6 +3626,7 @@ 5878DAB2291D627C00DD95A3 /* EditorPathBarView.swift in Sources */, 04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */, 6C6BD70129CD172700235D17 /* ExtensionsListView.swift in Sources */, + 77A01E362BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift in Sources */, 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */, 58F2EAEC292FB2B0004A9BDE /* IgnoredFiles.swift in Sources */, 6CD03B6A29FC773F001BD1D0 /* SettingsInjector.swift in Sources */, @@ -3590,6 +3660,8 @@ 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */, 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */, + 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */, + 77A01E2C2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift in Sources */, 5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */, 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */, 58F2EB0B292FB2B0004A9BDE /* AccountsSettings.swift in Sources */, diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 14106a2645..b0f3a974ca 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "3f6921a5ec30d1ecb6d6b205cf27a816c318246bb00f0ea367b997cc66527d32", "pins" : [ { "identity" : "anycodable", @@ -199,5 +200,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/CodeEdit/CodeEditApp.swift b/CodeEdit/CodeEditApp.swift index 9cc4626d47..709790ec88 100644 --- a/CodeEdit/CodeEditApp.swift +++ b/CodeEdit/CodeEditApp.swift @@ -35,6 +35,8 @@ struct CodeEditApp: App { .commands { CodeEditCommands() } + + CEWorkspaceSettingsWindow() } .environment(\.settings, settings.preferences) // Add settings to each window environment } diff --git a/CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift b/CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift new file mode 100644 index 0000000000..1cc5f5fe1c --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift @@ -0,0 +1,24 @@ +// +// CEWorkspaceSettingsWindow.swift +// CodeEdit +// +// Created by Axel Martinez on 26/3/24. +// + +import SwiftUI + +struct CEWorkspaceSettingsWindow: Scene { + var body: some Scene { + Window("Workspace Settings", id: SceneID.workspaceSettings.rawValue) { + CEWorkspaceSettingsView() + .frame(minWidth: 715, maxWidth: 715) + .task { + let window = NSApp.windows.first { $0.identifier?.rawValue == SceneID.workspaceSettings.rawValue }! + window.titlebarAppearsTransparent = true + } + } + .windowStyle(.automatic) + .windowToolbarStyle(.unified) + .windowResizability(.contentSize) + } +} diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift new file mode 100644 index 0000000000..92aad50b35 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift @@ -0,0 +1,334 @@ +// +// GeneralCEWorkspaceSettings.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import SwiftUI + +extension CEWorkspaceSettingsData { + + /// 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/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift new file mode 100644 index 0000000000..d6e0a37b81 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift @@ -0,0 +1,188 @@ +// +// CEWorkspaceSettingsData+TasksSettings.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import Foundation + +extension CEWorkspaceSettingsData { + + /// The general global setting + struct TasksSettings: 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 + } +} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift new file mode 100644 index 0000000000..1a3c67c07e --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift @@ -0,0 +1,94 @@ +// +// CEWorkspaceSettings.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +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 CEWorkspaceSettings: ObservableObject { + /// The publicly available singleton instance of ``SettingsModel`` + static let shared: CEWorkspaceSettings = .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: CEWorkspaceSettingsData + + /// Load and construct ``Settings`` model from + /// `~/Library/Application Support/CodeEdit/settings.json` + private func loadSettings() -> CEWorkspaceSettingsData { + 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(CEWorkspaceSettingsData.self, from: json) + else { + return .init() + } + return prefs + } + + /// Save``Settings`` model to + /// `~/Library/Application Support/CodeEdit/settings.json` + private func savePreferences(_ data: CEWorkspaceSettingsData) 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/CEWorkspace/Models/CEWorkspaceSettingsData.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift new file mode 100644 index 0000000000..ae64ad20b0 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift @@ -0,0 +1,57 @@ +// +// CEWorkspaceSettingsData.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +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 CEWorkspaceSettingsData: Codable, Hashable { + + /// The general global settings + var general: GeneralSettings = .init() + + /// The global settings for accounts + var tasks: TasksSettings = .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.tasks = try container.decodeIfPresent(TasksSettings.self, forKey: .tasks) ?? .init() + } + + // swiftlint:disable cyclomatic_complexity + func propertiesOf(_ name: CEWorkspaceSettingsPage.Name) -> [CEWorkspaceSettingsPage] { + var settings: [CEWorkspaceSettingsPage] = [] + + switch name { + case .general: + general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .tasks: + tasks.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + } + + return settings + } + // swiftlint:enable cyclomatic_complexity +} + diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift new file mode 100644 index 0000000000..d9d2f2617b --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift @@ -0,0 +1,51 @@ +// +// CEWorkspaceSettingsPage.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import Foundation +import SwiftUI + +/// A struct for a settings page +struct CEWorkspaceSettingsPage: 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 tasks = "Tasks" + } + + 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/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift new file mode 100644 index 0000000000..f36d8c8a45 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift @@ -0,0 +1,25 @@ +// +// CEWorkpaceSettingsSearchResult.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import Foundation +import SwiftUI + +// TODO: Extend this struct further to support setting "flashing" +class CEWorkspaceSettingsSearchResult: Identifiable { + init( + pageFound: Bool, + pages: [CEWorkspaceSettingsPage] + ) { + self.pageFound = pageFound + self.pages = pages + } + + let id: UUID = UUID() + + let pageFound: Bool + let pages: [CEWorkspaceSettingsPage] +} diff --git a/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift new file mode 100644 index 0000000000..91b081c8fc --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift @@ -0,0 +1,19 @@ +// +// PageAndCEWorkspaceSettings.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import Foundation + +struct PageAndCEWorkspaceSettings: Identifiable, Equatable { + let id: UUID = UUID() + let page: CEWorkspaceSettingsPage + let settings: [CEWorkspaceSettingsPage] + + init(_ page: CEWorkspaceSettingsPage) { + self.page = page + self.settings = CEWorkspaceSettingsData().propertiesOf(page.name) + } +} diff --git a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsPageView.swift b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsPageView.swift new file mode 100644 index 0000000000..367fe14276 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsPageView.swift @@ -0,0 +1,57 @@ +// +// CEWorkspaceSettingsPageView.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import SwiftUI + +struct CEWorkspaceSettingsPageView: View { + var page: CEWorkspaceSettingsPage + var searchText: String + + init(_ page: CEWorkspaceSettingsPage, 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/CEWorkspace/Views/CEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift new file mode 100644 index 0000000000..ecae2206f6 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift @@ -0,0 +1,51 @@ +// +// CEWorkspaceSettingsView.swift +// CodeEdit +// +// Created by Axel Martinez on 26/3/24. +// + +import SwiftUI +import CodeEditSymbols + +/// A struct for settings +struct CEWorkspaceSettingsView { + @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: CEWorkspaceSettingsPage = Self.pages[0].page + @State private var searchText: String = "" + + @Environment(\.presentationMode) + var presentationMode + + static var pages: [PageAndCEWorkspaceSettings] = [ + .init( + CEWorkspaceSettingsPage( + .general, + baseColor: .gray, + icon: .system("gear") + ) + ), + .init( + CEWorkspaceSettingsPage( + .tasks, + baseColor: .blue, + icon: .system("at") + ) + ), + ] + + @ObservedObject private var settings: CEWorkspaceSettings = .shared + + var body: some View { + ProjectCEWorkspaceSettingsView() + TasksCEWorkspaceSettingsView() + .environmentObject(model) + .onAppear { + selectedPage = Self.pages[0].page + } + } +} diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift new file mode 100644 index 0000000000..e651fc245a --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift @@ -0,0 +1,32 @@ +// +// GeneralCEWorkspaceSettingsView.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import SwiftUI + +/// A view that implements the `Project` worksppace settings page +struct ProjectCEWorkspaceSettingsView: View { + @AppSettings(\.sourceControl.git) + var settings + + var body: some View { + SettingsForm { + Section { + projectName + } + } + } +} + +/// The extension of the view with all the preferences +private extension ProjectCEWorkspaceSettingsView { + private var projectName: some View { + TextField(text: $settings.projectedValue) { + Text("Name") + } + } +} + diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift new file mode 100644 index 0000000000..9e222a91fa --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift @@ -0,0 +1,64 @@ +// +// TasksCEWorkspaceSettingsView.swift +// CodeEdit +// +// Created by Axel Martinez on 27/3/24. +// + +import SwiftUI + +struct TasksCEWorkspaceSettingsView: 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/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index 117fe2048d..ebf8ecd845 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -8,6 +8,10 @@ import SwiftUI struct FileCommands: Commands { + @Environment(\.openWindow) + private var openWindow + + @State var windowController: CodeEditWindowController? @FocusedObject var utilityAreaViewModel: UtilityAreaViewModel? @@ -79,6 +83,17 @@ struct FileCommands: Commands { Divider() + Button("Workspace Settings") { + openWindow(sceneID: SceneID.workspaceSettings) + } + //.keyboardShortcut("w", modifiers: [.control, .option, .command]) + .disabled(windowController?.workspace == nil) + .onReceive(NSApp.publisher(for: \.keyWindow)) { window in + windowController = window?.windowController as? CodeEditWindowController + } + + Divider() + Button("Save") { NSApp.sendAction(#selector(CodeEditWindowController.saveDocument(_:)), to: nil, from: nil) } diff --git a/CodeEdit/SceneID.swift b/CodeEdit/SceneID.swift index 3e213ab628..2a0b29b575 100644 --- a/CodeEdit/SceneID.swift +++ b/CodeEdit/SceneID.swift @@ -12,4 +12,5 @@ enum SceneID: String, CaseIterable { case about case extensions case settings + case workspaceSettings } From 449ddf05a6faa83ead8ad2d4ec36f6e81d4adbeb Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Mon, 1 Apr 2024 09:20:28 +0200 Subject: [PATCH 02/10] Small update --- CodeEdit.xcodeproj/project.pbxproj | 4 + ...orkspaceSettingsData+ProjectSettings.swift | 343 +----------------- ...EWorkspaceSettingsData+TasksSettings.swift | 167 +-------- .../Models/CEWorkspaceSettings.swift | 2 +- .../Models/CEWorkspaceSettingsData.swift | 8 +- .../Models/WorkspaceSettings.swift | 52 +++ .../Views/CEWorkspaceSettingsView.swift | 6 +- .../ProjectCEWorkspaceSettingsView.swift | 4 +- .../Pages/TasksCEWorkspaceSettingsView.swift | 53 +-- 9 files changed, 99 insertions(+), 540 deletions(-) create mode 100644 CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index fdd3905f65..8a0e2142a0 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -380,6 +380,7 @@ 77A01E322BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */; }; 77A01E342BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */; }; 77A01E362BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */; }; + 77A88BE42BB70FD9008B1293 /* WorkspaceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A88BE32BB70FD9008B1293 /* WorkspaceSettings.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 */; }; @@ -917,6 +918,7 @@ 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksCEWorkspaceSettingsView.swift; sourceTree = ""; }; 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsSearchResult.swift; sourceTree = ""; }; 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsPageView.swift; sourceTree = ""; }; + 77A88BE32BB70FD9008B1293 /* WorkspaceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSettings.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 = ""; }; @@ -2096,6 +2098,7 @@ 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */, 6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */, 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */, + 77A88BE32BB70FD9008B1293 /* WorkspaceSettings.swift */, ); path = Models; sourceTree = ""; @@ -3532,6 +3535,7 @@ 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, 286471AB27ED51FD0039369D /* ProjectNavigatorView.swift in Sources */, B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */, + 77A88BE42BB70FD9008B1293 /* WorkspaceSettings.swift in Sources */, B62AEDB52A1FE295009A9F52 /* UtilityAreaDebugView.swift in Sources */, 6C049A372A49E2DB00D42923 /* DirectoryEventStream.swift in Sources */, 04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */, diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift index 92aad50b35..e454f8fc56 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift @@ -1,5 +1,5 @@ // -// GeneralCEWorkspaceSettings.swift +// ProjectCEWorkspaceSettings.swift // CodeEdit // // Created by Axel Martinez on 27/3/24. @@ -9,326 +9,23 @@ import SwiftUI extension CEWorkspaceSettingsData { - /// 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" - } - } - } + /// The project setting + struct ProjectSettings: Codable, Hashable, SearchableSettingsPage { + + /// The project name + var projectName: String = "" + + /// 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.projectName = try container.decodeIfPresent( + String.self, + forKey: .projectName + ) ?? "" + } + // swiftlint:enable function_body_length } - diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift index d6e0a37b81..ba4e5531fc 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift @@ -9,95 +9,10 @@ import Foundation extension CEWorkspaceSettingsData { - /// The general global setting + /// The tasks setting struct TasksSettings: 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 + var tasksEnabled: Bool = true /// Default initializer init() {} @@ -105,82 +20,10 @@ extension CEWorkspaceSettingsData { // 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( + let container = try decoder.container(keyedBy: CodingKeys.self + self.tasksEnabled = try container.decodeIfPresent( Bool.self, - forKey: .isAutoSaveOn + forKey: .tasksEnabled ) ?? true } // swiftlint:enable function_body_length diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift index 1a3c67c07e..83b0274f77 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift @@ -17,7 +17,7 @@ import Combine /// private var prefs: SettingsModel = .shared /// ``` final class CEWorkspaceSettings: ObservableObject { - /// The publicly available singleton instance of ``SettingsModel`` + /// The publicly available singleton instance of ``CEWorkspaceSettingsModel`` static let shared: CEWorkspaceSettings = .init() private var storeTask: AnyCancellable! diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift index ae64ad20b0..74a5ca1c7e 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift @@ -8,7 +8,7 @@ import SwiftUI import Foundation -/// # Settings +/// # Workspace Settings /// /// The model structure of settings for `CodeEdit` /// @@ -24,7 +24,7 @@ import Foundation struct CEWorkspaceSettingsData: Codable, Hashable { /// The general global settings - var general: GeneralSettings = .init() + var project: ProjectSettings = .init() /// The global settings for accounts var tasks: TasksSettings = .init() @@ -35,7 +35,7 @@ struct CEWorkspaceSettingsData: Codable, Hashable { /// 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.project = try container.decodeIfPresent(ProjectSettings.self, forKey: .project) ?? .init() self.tasks = try container.decodeIfPresent(TasksSettings.self, forKey: .tasks) ?? .init() } @@ -44,7 +44,7 @@ struct CEWorkspaceSettingsData: Codable, Hashable { var settings: [CEWorkspaceSettingsPage] = [] switch name { - case .general: + case .project: general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .tasks: tasks.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } diff --git a/CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift new file mode 100644 index 0000000000..d4551c7529 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift @@ -0,0 +1,52 @@ +// +// WorkspaceSettings.swift +// CodeEdit +// +// Created by Axel Martinez on 29/3/24. +// + +import Foundation +import SwiftUI + +@propertyWrapper +struct WorkspaceSettings: DynamicProperty where T: Equatable { + + var workspaceSettings: Environment + + let keyPath: WritableKeyPath + + init(_ keyPath: WritableKeyPath) { + self.keyPath = keyPath + let settingsKeyPath = (\EnvironmentValues.workspaceSettings).appending(path: keyPath) + self.workspaceSettings = Environment(settingsKeyPath) + } + + var wrappedValue: T { + get { + CEWorkspaceSettings.shared.preferences[keyPath: keyPath] + } + nonmutating set { + CEWorkspaceSettings.shared.preferences[keyPath: keyPath] = newValue + } + } + + var projectedValue: Binding { + Binding { + CEWorkspaceSettings.shared.preferences[keyPath: keyPath] + } set: { + CEWorkspaceSettings.shared.preferences[keyPath: keyPath] = $0 + } + } +} + +struct CEWorkspaceeSettingsDataEnvironmentKey: EnvironmentKey { + static var defaultValue: SettingsData = .init() +} + +extension EnvironmentValues { + var workspaceSettings: CEWorkspaceeSettingsDataEnvironmentKey.Value { + get { self[CEWorkspaceeSettingsDataEnvironmentKey.self] } + set { self[CEWorkspaceeSettingsDataEnvironmentKey.self] = newValue } + } +} + diff --git a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift index ecae2206f6..b63a02af1c 100644 --- a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift @@ -11,10 +11,8 @@ import CodeEditSymbols /// A struct for settings struct CEWorkspaceSettingsView { @StateObject var model = SettingsViewModel() - @Environment(\.colorScheme) - private var colorScheme - /// Variables for the selected Page, the current search text and software updater + /// Variables for the selected Page and the current search text @State private var selectedPage: CEWorkspaceSettingsPage = Self.pages[0].page @State private var searchText: String = "" @@ -24,7 +22,7 @@ struct CEWorkspaceSettingsView { static var pages: [PageAndCEWorkspaceSettings] = [ .init( CEWorkspaceSettingsPage( - .general, + .project, baseColor: .gray, icon: .system("gear") ) diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift index e651fc245a..b1bf7aa705 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift @@ -9,7 +9,7 @@ import SwiftUI /// A view that implements the `Project` worksppace settings page struct ProjectCEWorkspaceSettingsView: View { - @AppSettings(\.sourceControl.git) + @WorkspaceSettings(\.project) var settings var body: some View { @@ -24,7 +24,7 @@ struct ProjectCEWorkspaceSettingsView: View { /// The extension of the view with all the preferences private extension ProjectCEWorkspaceSettingsView { private var projectName: some View { - TextField(text: $settings.projectedValue) { + TextField(text: $settings.projectName) { Text("Name") } } diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift index 9e222a91fa..d62c8641fa 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift @@ -8,57 +8,22 @@ import SwiftUI struct TasksCEWorkspaceSettingsView: View { - @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) - var gitAccounts - - @State private var addAccountSheetPresented: Bool = false - @State private var selectedProvider: SourceControlAccount.Provider? + @WorkspaceSettings(\.tasks) + var settings + + @WorkspaceSettings(\.accounts.sourceControlAccounts.gitAccounts) + var tasks 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) + Toggle("Tasks", isOn: $settings.tasksEnabled) } - } - } - - private var implementationNeeded: some View { - VStack(spacing: 20) { - Text("This git client is currently not supported.") - HStack { - Button("Close") { - addAccountSheetPresented.toggle() - selectedProvider = nil + Section { + ForEach(tasks) { + } - .buttonStyle(.borderedProminent) } - .frame(maxWidth: .infinity, alignment: .trailing) } - .padding(20) } } From ef7f42eb05e1f8fa984abc4b74f09aa254b3d27d Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Sat, 13 Apr 2024 12:52:33 +0200 Subject: [PATCH 03/10] Finished implementation --- CodeEdit.xcodeproj/project.pbxproj | 32 +++-- CodeEdit/CodeEditApp.swift | 2 - .../CEWorkspaceSettingsWindow.swift | 24 ---- ...orkspaceSettingsData+ProjectSettings.swift | 20 +-- ...EWorkspaceSettingsData+TasksSettings.swift | 45 ++++--- .../Extensions/NSWindow+Child.swift | 25 ++++ .../Features/CEWorkspace/Models/CETask.swift | 69 ++++++++++ .../Models/CEWorkspaceSettings.swift | 123 ++++++++---------- .../Models/CEWorkspaceSettingsData.swift | 10 +- .../Models/CEWorkspaceSettingsPage.swift | 68 +++++----- .../CEWorkspaceSettingsSearchResult.swift | 20 +-- .../Models/PageAndCEWorkspaceSettings.swift | 16 +-- .../Models/WorkspaceSettings.swift | 52 -------- .../CEWorkspace/Views/AddCETaskView.swift | 48 +++++++ .../CEWorkspace/Views/CETaskFormView.swift | 79 +++++++++++ .../Views/CEWorkspaceSettingsView.swift | 36 +++-- .../CEWorkspace/Views/EditCETaskView.swift | 41 ++++++ .../Views/EnvironmentVariableListItem.swift | 69 ++++++++++ .../ProjectCEWorkspaceSettingsView.swift | 4 +- .../Pages/TasksCEWorkspaceSettingsView.swift | 73 +++++++++-- .../CodeEditWindowController.swift | 4 + .../CodeEditWindowControllerExtensions.swift | 17 +++ .../WindowCommands/FileCommands.swift | 2 +- CodeEdit/SceneID.swift | 1 - 24 files changed, 612 insertions(+), 268 deletions(-) delete mode 100644 CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift create mode 100644 CodeEdit/Features/CEWorkspace/Extensions/NSWindow+Child.swift create mode 100644 CodeEdit/Features/CEWorkspace/Models/CETask.swift delete mode 100644 CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/AddCETaskView.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/EditCETaskView.swift create mode 100644 CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 8a0e2142a0..8f0dd2a9ff 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -368,7 +368,6 @@ 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 */; }; - 77A01E1D2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E1C2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift */; }; 77A01E1F2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E1E2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift */; }; 77A01E232BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E222BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift */; }; 77A01E252BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */; }; @@ -380,7 +379,12 @@ 77A01E322BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */; }; 77A01E342BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */; }; 77A01E362BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */; }; - 77A88BE42BB70FD9008B1293 /* WorkspaceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A88BE32BB70FD9008B1293 /* WorkspaceSettings.swift */; }; + 77A01E432BBC3A2800F0EA38 /* CETask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E422BBC3A2800F0EA38 /* CETask.swift */; }; + 77A01E582BBD7ECE00F0EA38 /* AddCETaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E572BBD7ECE00F0EA38 /* AddCETaskView.swift */; }; + 77A01E6D2BC3EA2A00F0EA38 /* NSWindow+Child.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E6C2BC3EA2A00F0EA38 /* NSWindow+Child.swift */; }; + 77A01E802BC5101200F0EA38 /* EnvironmentVariableListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E7F2BC5101200F0EA38 /* EnvironmentVariableListItem.swift */; }; + 77A01E8E2BC9A09C00F0EA38 /* EditCETaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E8D2BC9A09C00F0EA38 /* EditCETaskView.swift */; }; + 77A01E912BC9A33600F0EA38 /* CETaskFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A01E902BC9A33600F0EA38 /* CETaskFormView.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 */; }; @@ -906,7 +910,6 @@ 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 = ""; }; - 77A01E1C2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsWindow.swift; sourceTree = ""; }; 77A01E1E2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsView.swift; sourceTree = ""; }; 77A01E222BB423A800F0EA38 /* CEWorkspaceSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsPage.swift; sourceTree = ""; }; 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageAndCEWorkspaceSettings.swift; sourceTree = ""; }; @@ -918,7 +921,12 @@ 77A01E312BB4274B00F0EA38 /* TasksCEWorkspaceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksCEWorkspaceSettingsView.swift; sourceTree = ""; }; 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsSearchResult.swift; sourceTree = ""; }; 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CEWorkspaceSettingsPageView.swift; sourceTree = ""; }; - 77A88BE32BB70FD9008B1293 /* WorkspaceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSettings.swift; sourceTree = ""; }; + 77A01E422BBC3A2800F0EA38 /* CETask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CETask.swift; sourceTree = ""; }; + 77A01E572BBD7ECE00F0EA38 /* AddCETaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCETaskView.swift; sourceTree = ""; }; + 77A01E6C2BC3EA2A00F0EA38 /* NSWindow+Child.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWindow+Child.swift"; sourceTree = ""; }; + 77A01E7F2BC5101200F0EA38 /* EnvironmentVariableListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentVariableListItem.swift; sourceTree = ""; }; + 77A01E8D2BC9A09C00F0EA38 /* EditCETaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCETaskView.swift; sourceTree = ""; }; + 77A01E902BC9A33600F0EA38 /* CETaskFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CETaskFormView.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 = ""; }; @@ -2079,7 +2087,6 @@ 588847652992A35800996D95 /* Models */, 77A01E1A2BB33F1E00F0EA38 /* Views */, 77A01E1B2BB33F3D00F0EA38 /* Extensions */, - 77A01E1C2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift */, ); path = CEWorkspace; sourceTree = ""; @@ -2087,6 +2094,7 @@ 588847652992A35800996D95 /* Models */ = { isa = PBXGroup; children = ( + 77A01E422BBC3A2800F0EA38 /* CETask.swift */, 588847622992A2A200996D95 /* CEWorkspaceFile.swift */, 5894E59629FEF7740077E59C /* CEWorkspaceFile+Recursion.swift */, 58A2E40629C3975D005CB615 /* CEWorkspaceFileIcon.swift */, @@ -2098,7 +2106,6 @@ 77A01E332BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift */, 6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */, 77A01E242BB4245300F0EA38 /* PageAndCEWorkspaceSettings.swift */, - 77A88BE32BB70FD9008B1293 /* WorkspaceSettings.swift */, ); path = Models; sourceTree = ""; @@ -2463,8 +2470,12 @@ isa = PBXGroup; children = ( 77A01E372BB4291700F0EA38 /* Pages */, + 77A01E572BBD7ECE00F0EA38 /* AddCETaskView.swift */, + 77A01E902BC9A33600F0EA38 /* CETaskFormView.swift */, 77A01E1E2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift */, 77A01E352BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift */, + 77A01E8D2BC9A09C00F0EA38 /* EditCETaskView.swift */, + 77A01E7F2BC5101200F0EA38 /* EnvironmentVariableListItem.swift */, ); path = Views; sourceTree = ""; @@ -2474,6 +2485,7 @@ children = ( 77A01E292BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift */, 77A01E2B2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift */, + 77A01E6C2BC3EA2A00F0EA38 /* NSWindow+Child.swift */, ); path = Extensions; sourceTree = ""; @@ -3331,6 +3343,7 @@ D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */, 6CE6226E2A2A1CDE0013085C /* NavigatorTab.swift in Sources */, 041FC6AD2AE437CE00C1F65A /* SourceControlNavigatorNewBranchView.swift in Sources */, + 77A01E432BBC3A2800F0EA38 /* CETask.swift in Sources */, 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */, 6CED16E42A3E660D000EC962 /* String+Lines.swift in Sources */, 587B9E6B29301D8F00AC7927 /* GitLabAvatarURL.swift in Sources */, @@ -3397,7 +3410,6 @@ 613899B12B6E6FDC00A5CAF6 /* Collection+FuzzySearch.swift in Sources */, 581550D129FBD30400684881 /* TextTableViewCell.swift in Sources */, 587B9E6629301D8F00AC7927 /* GitLabProjectHook.swift in Sources */, - 77A01E1D2BB33F7700F0EA38 /* CEWorkspaceSettingsWindow.swift in Sources */, 587B9E9329301D8F00AC7927 /* BitBucketOAuthConfiguration.swift in Sources */, 6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */, 58F2EB0A292FB2B0004A9BDE /* SettingsData.swift in Sources */, @@ -3432,6 +3444,7 @@ 587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */, 201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */, 850C631029D6B01D00E1444C /* SettingsView.swift in Sources */, + 77A01E6D2BC3EA2A00F0EA38 /* NSWindow+Child.swift in Sources */, DE6405A62817734700881FDF /* EditorTabBarNative.swift in Sources */, 581550CF29FBD30400684881 /* StandardTableViewCell.swift in Sources */, B62AEDB82A1FE2DC009A9F52 /* UtilityAreaOutputView.swift in Sources */, @@ -3472,6 +3485,7 @@ 58D01C96293167DC00C5B6B4 /* Date+Formatted.swift in Sources */, B66A4E5629C918A0004573B4 /* SceneID.swift in Sources */, 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */, + 77A01E912BC9A33600F0EA38 /* CETaskFormView.swift in Sources */, B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */, 58822529292C280D00E83CDE /* StatusBarLineEndSelector.swift in Sources */, 77A01E342BB4282900F0EA38 /* CEWorkspaceSettingsSearchResult.swift in Sources */, @@ -3487,6 +3501,7 @@ 587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */, 6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */, 285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */, + 77A01E582BBD7ECE00F0EA38 /* AddCETaskView.swift in Sources */, 6CB9144B29BEC7F100BC47F2 /* (null) in Sources */, 587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */, 61538B902B111FE800A88846 /* String+AppearancesOfSubstring.swift in Sources */, @@ -3535,7 +3550,6 @@ 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, 286471AB27ED51FD0039369D /* ProjectNavigatorView.swift in Sources */, B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */, - 77A88BE42BB70FD9008B1293 /* WorkspaceSettings.swift in Sources */, B62AEDB52A1FE295009A9F52 /* UtilityAreaDebugView.swift in Sources */, 6C049A372A49E2DB00D42923 /* DirectoryEventStream.swift in Sources */, 04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */, @@ -3589,11 +3603,13 @@ B6041F4D29D7A4E9000F3454 /* SettingsPageView.swift in Sources */, 587B9E9A29301D8F00AC7927 /* GitType.swift in Sources */, B65B10F82B081A34002852CF /* SourceControlNavigatorNoRemotesView.swift in Sources */, + 77A01E8E2BC9A09C00F0EA38 /* EditCETaskView.swift in Sources */, 58D01C97293167DC00C5B6B4 /* String+SHA256.swift in Sources */, B6EA1FFD29DB792C001BF195 /* ThemeSettingsColorPreview.swift in Sources */, 2806E904297958B9000040F4 /* ContributorRowView.swift in Sources */, 6C578D8C29CD372700DC73B2 /* ExtensionCommands.swift in Sources */, 77A01E272BB4249800F0EA38 /* CEWorkspaceSettingsData.swift in Sources */, + 77A01E802BC5101200F0EA38 /* EnvironmentVariableListItem.swift in Sources */, B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */, B6EA1FF829DB78DB001BF195 /* ThemeSettingThemeRow.swift in Sources */, 587B9E7629301D8F00AC7927 /* GitTime.swift in Sources */, diff --git a/CodeEdit/CodeEditApp.swift b/CodeEdit/CodeEditApp.swift index 709790ec88..9cc4626d47 100644 --- a/CodeEdit/CodeEditApp.swift +++ b/CodeEdit/CodeEditApp.swift @@ -35,8 +35,6 @@ struct CodeEditApp: App { .commands { CodeEditCommands() } - - CEWorkspaceSettingsWindow() } .environment(\.settings, settings.preferences) // Add settings to each window environment } diff --git a/CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift b/CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift deleted file mode 100644 index 1cc5f5fe1c..0000000000 --- a/CodeEdit/Features/CEWorkspace/CEWorkspaceSettingsWindow.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// CEWorkspaceSettingsWindow.swift -// CodeEdit -// -// Created by Axel Martinez on 26/3/24. -// - -import SwiftUI - -struct CEWorkspaceSettingsWindow: Scene { - var body: some Scene { - Window("Workspace Settings", id: SceneID.workspaceSettings.rawValue) { - CEWorkspaceSettingsView() - .frame(minWidth: 715, maxWidth: 715) - .task { - let window = NSApp.windows.first { $0.identifier?.rawValue == SceneID.workspaceSettings.rawValue }! - window.titlebarAppearsTransparent = true - } - } - .windowStyle(.automatic) - .windowToolbarStyle(.unified) - .windowResizability(.contentSize) - } -} diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift index e454f8fc56..8b2f0a6668 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift @@ -8,24 +8,28 @@ import SwiftUI extension CEWorkspaceSettingsData { + /// The project setting + struct ProjectSettings: Codable, Hashable, SearchableSettingsPage { + var searchKeys: [String] { + [ + "Project Name", + ] + .map { NSLocalizedString($0, comment: "") } + } - /// The project setting - struct ProjectSettings: Codable, Hashable, SearchableSettingsPage { + /// The project name + var projectName: String = "" - /// The project name - var projectName: String = "" - /// 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 + let container = try decoder.container(keyedBy: CodingKeys.self) self.projectName = try container.decodeIfPresent( String.self, forKey: .projectName ) ?? "" } - // swiftlint:enable function_body_length + } } diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift index ba4e5531fc..b68cf87508 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift @@ -6,26 +6,37 @@ // import Foundation +import Collections extension CEWorkspaceSettingsData { + /// The tasks setting + struct TasksSettings: Codable, Hashable, SearchableSettingsPage { + var items: [CETask] = [] - /// The tasks setting - struct TasksSettings: Codable, Hashable, SearchableSettingsPage { - /// The show live issues behavior of the app - var tasksEnabled: Bool = true + var searchKeys: [String] { + [ + "Tasks" + ] + .map { NSLocalizedString($0, comment: "") } + } - /// Default initializer - init() {} + /// The show live issues behavior of the app + var enabled: Bool = true - // 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.tasksEnabled = try container.decodeIfPresent( - Bool.self, - forKey: .tasksEnabled - ) ?? true - } - // swiftlint:enable function_body_length - } + /// 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.items = try container.decodeIfPresent( + [CETask].self, + forKey: .items + ) ?? [] + self.enabled = try container.decodeIfPresent( + Bool.self, + forKey: .enabled + ) ?? true + } + } } diff --git a/CodeEdit/Features/CEWorkspace/Extensions/NSWindow+Child.swift b/CodeEdit/Features/CEWorkspace/Extensions/NSWindow+Child.swift new file mode 100644 index 0000000000..2686fb13d3 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Extensions/NSWindow+Child.swift @@ -0,0 +1,25 @@ +// +// NSWindow+Child.swift +// CodeEdit +// +// Created by Axel Martinez on 8/4/24. +// + +import AppKit + +extension NSWindow { + func addCenteredChildWindow(_ childWindow: NSWindow, over parentWindow: NSWindow) { + let parentFrame = parentWindow.frame + let parentCenterX = parentFrame.origin.x + (parentFrame.size.width / 2) + let parentCenterY = parentFrame.origin.y + (parentFrame.size.height / 2) + + let childWidth = childWindow.frame.size.width + let childHeight = childWindow.frame.size.height + let newChildOriginX = parentCenterX - (childWidth / 2) + let newChildOriginY = parentCenterY - (childHeight / 2) + + childWindow.setFrameOrigin(NSPoint(x: newChildOriginX, y: newChildOriginY)) + + parentWindow.addChildWindow(childWindow, ordered: .above) + } +} diff --git a/CodeEdit/Features/CEWorkspace/Models/CETask.swift b/CodeEdit/Features/CEWorkspace/Models/CETask.swift new file mode 100644 index 0000000000..b4c76a02cf --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Models/CETask.swift @@ -0,0 +1,69 @@ +// +// CETask.swift +// CodeEdit +// +// Created by Axel Martinez on 2/4/24. +// + +import SwiftUI + +struct CETask: Identifiable, Hashable, Codable { + var id = UUID() + var name: String = "" + var target: String = "" + var workingDirectory: String = "" + var command: String = "" + var env: [EnvironmentVariable] = [] + + var isInvalid: Bool { + name.isEmpty || + command.isEmpty || + target.isEmpty || + workingDirectory.isEmpty + } + + enum CodingKeys: String, CodingKey { + case name + case target + case workingDirectory + case command + case env + } +} + +struct EnvironmentVariable: Identifiable, Hashable, Codable { + var id = UUID() + var name: String = "" + var value: String = "" + + private struct CodingKeys: CodingKey { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + } + + init() { + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + for key in container.allKeys { + name = key.stringValue + value = try container.decode(String.self, forKey: key) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: CodingKeys(stringValue: name)!) + } +} diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift index 83b0274f77..c32d42f433 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift @@ -9,86 +9,69 @@ 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 CEWorkspaceSettings: ObservableObject { - /// The publicly available singleton instance of ``CEWorkspaceSettingsModel`` - static let shared: CEWorkspaceSettings = .init() + @ObservedObject private var workspace: WorkspaceDocument + @Published public var preferences: CEWorkspaceSettingsData = .init() - private var storeTask: AnyCancellable! + private var savedSettings = false + private var storeTask: AnyCancellable! + private let filemanager = FileManager.default - private init() { - self.preferences = .init() - self.preferences = loadSettings() + private var folderURL: URL? { + guard let workspaceURL = workspace.fileURL else { + return nil + } - self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink { - try? self.savePreferences($0) - } - } + return workspaceURL + .appendingPathComponent(".codeedit", isDirectory: true) + } - static subscript(_ path: WritableKeyPath, suite: Settings = .shared) -> T { - get { - suite.preferences[keyPath: path] - } - set { - suite.preferences[keyPath: path] = newValue - } - } + private var settingsURL: URL? { + folderURL? + .appendingPathComponent("settings") + .appendingPathExtension("json") + } - /// Published instance of the ``Settings`` model. - /// - /// Changes are saved automatically. - @Published var preferences: CEWorkspaceSettingsData + init(workspaceDocument: WorkspaceDocument) { + self.workspace = workspaceDocument - /// Load and construct ``Settings`` model from - /// `~/Library/Application Support/CodeEdit/settings.json` - private func loadSettings() -> CEWorkspaceSettingsData { - if !filemanager.fileExists(atPath: settingsURL.path) { - try? filemanager.createDirectory(at: baseURL, withIntermediateDirectories: false) - return .init() - } + loadSettings() - guard let json = try? Data(contentsOf: settingsURL), - let prefs = try? JSONDecoder().decode(CEWorkspaceSettingsData.self, from: json) - else { - return .init() - } - return prefs - } + self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink { + if !self.savedSettings, let folderURL = self.folderURL { + try? self.filemanager.createDirectory(at: folderURL, withIntermediateDirectories: false) + self.savedSettings = true + } - /// Save``Settings`` model to - /// `~/Library/Application Support/CodeEdit/settings.json` - private func savePreferences(_ data: CEWorkspaceSettingsData) 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) - } + try? self.savePreferences($0) + } + } - /// Default instance of the `FileManager` - private let filemanager = FileManager.default + /// Load and construct ``Settings`` model from + /// `.codeedit/settings.json` + private func loadSettings() { + if let settingsURL = settingsURL { + if filemanager.fileExists(atPath: settingsURL.path) { + guard let json = try? Data(contentsOf: settingsURL), + let prefs = try? JSONDecoder().decode(CEWorkspaceSettingsData.self, from: json) + else { + return + } + self.savedSettings = true + self.preferences = prefs + } + } + return + } - /// The base URL of settings. - /// - /// Points to `~/Library/Application Support/CodeEdit/` - internal var baseURL: URL { - filemanager - .homeDirectoryForCurrentUser - .appendingPathComponent("Library/Application Support/CodeEdit", isDirectory: true) - } + /// Save``Settings`` model to + /// `.codeedit/settings.json` + private func savePreferences(_ data: CEWorkspaceSettingsData) throws { + guard let settingsURL = settingsURL else { return } - /// 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") - } + 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) + } } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift index 74a5ca1c7e..049e27e7fd 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift @@ -22,11 +22,10 @@ import Foundation /// [`decodeIfPresent`](https://developer.apple.com/documentation/swift/keyeddecodingcontainer/2921389-decodeifpresent) /// and providing a default value. Otherwise all settings get overridden. struct CEWorkspaceSettingsData: Codable, Hashable { - - /// The general global settings + /// The project global settings var project: ProjectSettings = .init() - /// The global settings for accounts + /// The tasks settings var tasks: TasksSettings = .init() /// Default initializer @@ -39,19 +38,16 @@ struct CEWorkspaceSettingsData: Codable, Hashable { self.tasks = try container.decodeIfPresent(TasksSettings.self, forKey: .tasks) ?? .init() } - // swiftlint:disable cyclomatic_complexity func propertiesOf(_ name: CEWorkspaceSettingsPage.Name) -> [CEWorkspaceSettingsPage] { var settings: [CEWorkspaceSettingsPage] = [] switch name { case .project: - general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + project.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .tasks: tasks.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } } return settings } - // swiftlint:enable cyclomatic_complexity } - diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift index d9d2f2617b..cf4bdb3e40 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift @@ -10,42 +10,42 @@ import SwiftUI /// A struct for a settings page struct CEWorkspaceSettingsPage: 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) - } + /// 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 tasks = "Tasks" - } + /// An enum of all the settings pages + enum Name: String { + case project = "Project" + case tasks = "Tasks" + } - let id: UUID = UUID() + 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? + 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 - } + /// 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/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift index f36d8c8a45..bad360acdd 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift @@ -10,16 +10,16 @@ import SwiftUI // TODO: Extend this struct further to support setting "flashing" class CEWorkspaceSettingsSearchResult: Identifiable { - init( - pageFound: Bool, - pages: [CEWorkspaceSettingsPage] - ) { - self.pageFound = pageFound - self.pages = pages - } + init( + pageFound: Bool, + pages: [CEWorkspaceSettingsPage] + ) { + self.pageFound = pageFound + self.pages = pages + } - let id: UUID = UUID() + let id: UUID = UUID() - let pageFound: Bool - let pages: [CEWorkspaceSettingsPage] + let pageFound: Bool + let pages: [CEWorkspaceSettingsPage] } diff --git a/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift index 91b081c8fc..deaf35c715 100644 --- a/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift @@ -8,12 +8,12 @@ import Foundation struct PageAndCEWorkspaceSettings: Identifiable, Equatable { - let id: UUID = UUID() - let page: CEWorkspaceSettingsPage - let settings: [CEWorkspaceSettingsPage] - - init(_ page: CEWorkspaceSettingsPage) { - self.page = page - self.settings = CEWorkspaceSettingsData().propertiesOf(page.name) - } + let id: UUID = UUID() + let page: CEWorkspaceSettingsPage + let settings: [CEWorkspaceSettingsPage] + + init(_ page: CEWorkspaceSettingsPage) { + self.page = page + self.settings = CEWorkspaceSettingsData().propertiesOf(page.name) + } } diff --git a/CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift deleted file mode 100644 index d4551c7529..0000000000 --- a/CodeEdit/Features/CEWorkspace/Models/WorkspaceSettings.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// WorkspaceSettings.swift -// CodeEdit -// -// Created by Axel Martinez on 29/3/24. -// - -import Foundation -import SwiftUI - -@propertyWrapper -struct WorkspaceSettings: DynamicProperty where T: Equatable { - - var workspaceSettings: Environment - - let keyPath: WritableKeyPath - - init(_ keyPath: WritableKeyPath) { - self.keyPath = keyPath - let settingsKeyPath = (\EnvironmentValues.workspaceSettings).appending(path: keyPath) - self.workspaceSettings = Environment(settingsKeyPath) - } - - var wrappedValue: T { - get { - CEWorkspaceSettings.shared.preferences[keyPath: keyPath] - } - nonmutating set { - CEWorkspaceSettings.shared.preferences[keyPath: keyPath] = newValue - } - } - - var projectedValue: Binding { - Binding { - CEWorkspaceSettings.shared.preferences[keyPath: keyPath] - } set: { - CEWorkspaceSettings.shared.preferences[keyPath: keyPath] = $0 - } - } -} - -struct CEWorkspaceeSettingsDataEnvironmentKey: EnvironmentKey { - static var defaultValue: SettingsData = .init() -} - -extension EnvironmentValues { - var workspaceSettings: CEWorkspaceeSettingsDataEnvironmentKey.Value { - get { self[CEWorkspaceeSettingsDataEnvironmentKey.self] } - set { self[CEWorkspaceeSettingsDataEnvironmentKey.self] = newValue } - } -} - diff --git a/CodeEdit/Features/CEWorkspace/Views/AddCETaskView.swift b/CodeEdit/Features/CEWorkspace/Views/AddCETaskView.swift new file mode 100644 index 0000000000..08f459c091 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/AddCETaskView.swift @@ -0,0 +1,48 @@ +// +// AddCETaskView.swift +// CodeEdit +// +// Created by Axel Martinez on 3/4/24. +// + +import SwiftUI +import Collections + +struct AddCETaskView: View { + @Environment(\.dismiss) + var dismiss + + @Binding private var settings: CEWorkspaceSettingsData.TasksSettings + + @State private var task: CETask + + init(workingDirectory: String, settings: Binding) { + self._settings = settings + self._task = State(initialValue: CETask( + target: "My Mac", + workingDirectory: workingDirectory + )) + } + + var body: some View { + VStack(spacing: 0) { + CETaskFormView( + task: $task + ) + Spacer() + Divider() + HStack { + Button("Remove...") { + self.dismiss() + } + Spacer() + Button("Done") { + self.settings.items.append(task) + self.dismiss() + } + .disabled(task.isInvalid) + } + .padding() + } + } +} diff --git a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift new file mode 100644 index 0000000000..8f742d246d --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift @@ -0,0 +1,79 @@ +// +// CETaskFormView.swift +// CodeEdit +// +// Created by Axel Martinez on 12/4/24. +// + +import SwiftUI + +struct CETaskFormView: View { + @Binding var task: CETask + + @State private var selectedItemId: UUID? + + var body: some View { + Form { + Section { + TextField(text: $task.name) { + Text("Name") + } + Picker("Target", selection: $task.target) { + Text("My Mac") + .tag("My Mac") + } + } + Section { + TextField(text: $task.command) { + Text("Task") + } + TextField(text: $task.workingDirectory) { + Text("Working Directory") + } + } + Section(content: { + List(selection: $selectedItemId) { + ForEach($task.env) { env in + EnvironmentVariableListItem( + item: env, + selectedItemId: $selectedItemId, + deleteHandler: removeEnv + ) + } + } + .frame(minHeight: 56) + .overlay { + if task.env.isEmpty { + Text("No environment variables") + .foregroundStyle(Color(.secondaryLabelColor)) + } + } + .actionBar { + Button { + self.task.env.append(EnvironmentVariable()) + } label: { + Image(systemName: "plus") + } + Divider() + Button { + if let selectedItemId = selectedItemId { + removeEnv(id: selectedItemId) + } + } label: { + Image(systemName: "minus") + } + .disabled(selectedItemId == nil) + } + }, header: { + Text("Environment Variables") + }) + } + .formStyle(.grouped) + } + + func removeEnv(id: UUID) { + self.task.env.removeAll(where: { + $0.id == id + }) + } +} diff --git a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift index b63a02af1c..5e7be1f71a 100644 --- a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift @@ -9,15 +9,16 @@ import SwiftUI import CodeEditSymbols /// A struct for settings -struct CEWorkspaceSettingsView { - @StateObject var model = SettingsViewModel() +struct CEWorkspaceSettingsView: View { + let workspace: WorkspaceDocument - /// Variables for the selected Page and the current search text + @ObservedObject var settings: CEWorkspaceSettings + + @StateObject var viewModel = SettingsViewModel() @State private var selectedPage: CEWorkspaceSettingsPage = Self.pages[0].page @State private var searchText: String = "" - @Environment(\.presentationMode) - var presentationMode + let window: NSWindow? static var pages: [PageAndCEWorkspaceSettings] = [ .init( @@ -33,17 +34,26 @@ struct CEWorkspaceSettingsView { baseColor: .blue, icon: .system("at") ) - ), + ) ] - @ObservedObject private var settings: CEWorkspaceSettings = .shared - var body: some View { - ProjectCEWorkspaceSettingsView() - TasksCEWorkspaceSettingsView() - .environmentObject(model) - .onAppear { - selectedPage = Self.pages[0].page + VStack(spacing: 0) { + TasksCEWorkspaceSettingsView( + workspace: workspace, + projectSettings: $settings.preferences.project, + settings: $settings.preferences.tasks + ) + Spacer() + Divider() + HStack { + Spacer() + Button("Done") { + window?.close() + } } + .padding() + } + .environmentObject(viewModel) } } diff --git a/CodeEdit/Features/CEWorkspace/Views/EditCETaskView.swift b/CodeEdit/Features/CEWorkspace/Views/EditCETaskView.swift new file mode 100644 index 0000000000..f7ea2b99f4 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/EditCETaskView.swift @@ -0,0 +1,41 @@ +// +// EditCETaskView.swift +// CodeEdit +// +// Created by Axel Martinez on 12/4/24. +// + +import SwiftUI +import Collections + +struct EditCETaskView: View { + @Environment(\.dismiss) + var dismiss + + @Binding var task: CETask + @Binding var settings: CEWorkspaceSettingsData.TasksSettings + + var body: some View { + VStack(spacing: 0) { + CETaskFormView( + task: $task + ) + Spacer() + Divider() + HStack { + Button("Remove...") { + self.settings.items.removeAll(where: { + $0.id == self.task.id + }) + self.dismiss() + } + Spacer() + Button("Done") { + self.dismiss() + } + .disabled(task.isInvalid) + } + .padding() + } + } +} diff --git a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift new file mode 100644 index 0000000000..43a0600523 --- /dev/null +++ b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift @@ -0,0 +1,69 @@ +// +// EnvironmentVariableListItem.swift +// CodeEdit +// +// Created by Axel Martinez on 9/4/24. +// + +import SwiftUI + +struct EnvironmentVariableListItem: View { + @Binding var item: EnvironmentVariable + @Binding var selectedItemId: UUID? + + var delete: (UUID) -> Void + + @State var name: String + @State var value: String + + @FocusState private var isKeyFocused: Bool + + init( + item: Binding, + selectedItemId: Binding, + deleteHandler: @escaping (UUID) -> Void + ) { + self.delete = deleteHandler + + self._name = State(wrappedValue: item.name.wrappedValue) + self._value = State(wrappedValue: item.value.wrappedValue) + self._item = item + self._selectedItemId = selectedItemId + } + + var body: some View { + HStack { + TextField("", text: $name) + .focused($isKeyFocused) + .disableAutocorrection(true) + .autocorrectionDisabled() + .labelsHidden() + .frame(width: 120) + .onAppear { + if item.name.isEmpty { + isKeyFocused = true + } + } + Divider() + TextField("", text: $value) + .disableAutocorrection(true) + .autocorrectionDisabled() + .labelsHidden() + } + .onChange(of: isKeyFocused) { isFocused in + if isFocused { + if selectedItemId != item.id { + selectedItemId = item.id + } + } else { + if name.isEmpty { + selectedItemId = nil + delete(item.id) + } else { + item.name = name + item.value = value + } + } + } + } +} diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift index b1bf7aa705..fdaf18c9ab 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift @@ -9,8 +9,7 @@ import SwiftUI /// A view that implements the `Project` worksppace settings page struct ProjectCEWorkspaceSettingsView: View { - @WorkspaceSettings(\.project) - var settings + @State var settings: CEWorkspaceSettingsData.ProjectSettings var body: some View { SettingsForm { @@ -29,4 +28,3 @@ private extension ProjectCEWorkspaceSettingsView { } } } - diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift index d62c8641fa..5a724fac12 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift @@ -8,22 +8,75 @@ import SwiftUI struct TasksCEWorkspaceSettingsView: View { - @WorkspaceSettings(\.tasks) - var settings - - @WorkspaceSettings(\.accounts.sourceControlAccounts.gitAccounts) - var tasks + let workspace: WorkspaceDocument + + // TODO: Separate Project Settings from Task Settings + @Binding var projectSettings: CEWorkspaceSettingsData.ProjectSettings + @Binding var settings: CEWorkspaceSettingsData.TasksSettings + + @State private var selectedTaskId: UUID? + @State private var addTaskSheetPresented: Bool = false var body: some View { SettingsForm { Section { - Toggle("Tasks", isOn: $settings.tasksEnabled) - } - Section { - ForEach(tasks) { - + TextField(text: $projectSettings.projectName) { + Text("Name") } + Toggle("Tasks", isOn: $settings.enabled) } + Section( + content: { + if settings.items.isEmpty { + Text("No tasks") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .center) + } else { + ForEach(settings.items) { task in + HStack { + Text(task.name) + Spacer() + Group { + Text(task.command) + Image(systemName: "chevron.right") + } + .font(.system(.body, design: .monospaced)) + } + .contentShape(Rectangle()) + .onTapGesture { + self.selectedTaskId = task.id + self.addTaskSheetPresented.toggle() + } + } + } + }, header: { + Text("Tasks") + }, footer: { + HStack { + Spacer() + Button("Add Task...") { + self.selectedTaskId = nil + self.addTaskSheetPresented.toggle() + } + } + } + ) } + .scrollDisabled(true) + .sheet(isPresented: $addTaskSheetPresented, content: { + if let selectedIndex = settings.items.firstIndex(where: { + $0.id == selectedTaskId + }) { + EditCETaskView( + task: $settings.items[selectedIndex], + settings: $settings + ) + } else { + AddCETaskView( + workingDirectory: workspace.fileURL?.relativePath ?? "", + settings: $settings + ) + } + }) } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 210d130dc9..5b36ddfbad 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -18,6 +18,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs var observers: [NSKeyValueObservation] = [] var workspace: WorkspaceDocument? + var workspaceSettings: CEWorkspaceSettings? var quickOpenPanel: OverlayPanel? var commandPalettePanel: OverlayPanel? var navigatorSidebarViewModel: NavigatorSidebarViewModel? @@ -29,6 +30,9 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs init(window: NSWindow, workspace: WorkspaceDocument) { super.init(window: window) self.workspace = workspace + self.workspaceSettings = CEWorkspaceSettings( + workspaceDocument: workspace + ) setupSplitView(with: workspace) let view = CodeEditSplitView(controller: splitViewController).ignoresSafeArea() diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index 3a367e6601..075d59839a 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -111,6 +111,23 @@ extension CodeEditWindowController { .isEmpty self.setDocumentEdited(hasEditedDocuments) } + + @IBAction func openWorkspaceSettings(_ sender: Any) { + if let workspaceSettings, let window = window, let workspace = workspace { + let workspaceSettingsWindow = NSWindow() + let contentView = CEWorkspaceSettingsView( + workspace: workspace, + settings: workspaceSettings, + window: workspaceSettingsWindow + ) + + workspaceSettingsWindow.contentView = NSHostingView(rootView: contentView) + workspaceSettingsWindow.titlebarAppearsTransparent = true + workspaceSettingsWindow.setContentSize(NSSize(width: 515, height: 515)) + + window.addCenteredChildWindow(workspaceSettingsWindow, over: window) + } + } } extension NSToolbarItem.Identifier { diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index ebf8ecd845..b23f46ef37 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -84,7 +84,7 @@ struct FileCommands: Commands { Divider() Button("Workspace Settings") { - openWindow(sceneID: SceneID.workspaceSettings) + NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } //.keyboardShortcut("w", modifiers: [.control, .option, .command]) .disabled(windowController?.workspace == nil) diff --git a/CodeEdit/SceneID.swift b/CodeEdit/SceneID.swift index 2a0b29b575..3e213ab628 100644 --- a/CodeEdit/SceneID.swift +++ b/CodeEdit/SceneID.swift @@ -12,5 +12,4 @@ enum SceneID: String, CaseIterable { case about case extensions case settings - case workspaceSettings } From 03d877ce5ef993f297339754cf9caf26f6eb4720 Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Sat, 13 Apr 2024 13:08:03 +0200 Subject: [PATCH 04/10] Updated comments --- CodeEdit.xcodeproj/project.pbxproj | 10 +++- ...EWorkspaceSettingsData+TasksSettings.swift | 2 +- .../Features/CEWorkspace/Models/CETask.swift | 56 ++++++++++--------- .../Models/CEWorkspaceSettings.swift | 1 + .../Models/CEWorkspaceSettingsData.swift | 9 +-- .../Models/CEWorkspaceSettingsPage.swift | 2 +- .../Models/PageAndCEWorkspaceSettings.swift | 2 +- .../CEWorkspace/Views/CETaskFormView.swift | 2 +- .../Views/EnvironmentVariableListItem.swift | 6 +- .../ProjectCEWorkspaceSettingsView.swift | 2 +- .../Extensions/NSWindow}/NSWindow+Child.swift | 0 11 files changed, 51 insertions(+), 41 deletions(-) rename CodeEdit/{Features/CEWorkspace/Extensions => Utils/Extensions/NSWindow}/NSWindow+Child.swift (100%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 8f0dd2a9ff..cb3cd7632b 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -2178,6 +2178,7 @@ children = ( 85CD0C5D2A10CC2500E531FD /* URL */, 6C82D6C429C0129E00495C54 /* NSApplication */, + 77A01E922BCA9C0400F0EA38 /* NSWindow */, 588847672992AAB800996D95 /* Array */, 6CBD1BC42978DE3E006639D5 /* Text */, 5831E3D02934036D00D5A6D2 /* NSTableView */, @@ -2485,7 +2486,6 @@ children = ( 77A01E292BB424EA00F0EA38 /* CEWorkspaceSettingsData+ProjectSettings.swift */, 77A01E2B2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift */, - 77A01E6C2BC3EA2A00F0EA38 /* NSWindow+Child.swift */, ); path = Extensions; sourceTree = ""; @@ -2499,6 +2499,14 @@ path = Pages; sourceTree = ""; }; + 77A01E922BCA9C0400F0EA38 /* NSWindow */ = { + isa = PBXGroup; + children = ( + 77A01E6C2BC3EA2A00F0EA38 /* NSWindow+Child.swift */, + ); + path = NSWindow; + sourceTree = ""; + }; 85CD0C5D2A10CC2500E531FD /* URL */ = { isa = PBXGroup; children = ( diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift index b68cf87508..317bfdc9e9 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift @@ -20,7 +20,7 @@ extension CEWorkspaceSettingsData { .map { NSLocalizedString($0, comment: "") } } - /// The show live issues behavior of the app + /// The tasks behavior of the app var enabled: Bool = true /// Default initializer diff --git a/CodeEdit/Features/CEWorkspace/Models/CETask.swift b/CodeEdit/Features/CEWorkspace/Models/CETask.swift index b4c76a02cf..1780c6a005 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CETask.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CETask.swift @@ -7,6 +7,8 @@ import SwiftUI +/// Represents a CodeEdit task that will be executed by the a +/// task manager. struct CETask: Identifiable, Hashable, Codable { var id = UUID() var name: String = "" @@ -29,41 +31,41 @@ struct CETask: Identifiable, Hashable, Codable { case command case env } -} -struct EnvironmentVariable: Identifiable, Hashable, Codable { - var id = UUID() - var name: String = "" - var value: String = "" + struct EnvironmentVariable: Identifiable, Hashable, Codable { + var id = UUID() + var name: String = "" + var value: String = "" - private struct CodingKeys: CodingKey { - var stringValue: String - var intValue: Int? + private struct CodingKeys: CodingKey { + var stringValue: String + var intValue: Int? - init?(stringValue: String) { - self.stringValue = stringValue - self.intValue = nil - } + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } - init?(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } } - } - init() { - } + init() { + } - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - for key in container.allKeys { - name = key.stringValue - value = try container.decode(String.self, forKey: key) + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + for key in container.allKeys { + name = key.stringValue + value = try container.decode(String.self, forKey: key) + } } - } - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(value, forKey: CodingKeys(stringValue: name)!) + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: CodingKeys(stringValue: name)!) + } } } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift index c32d42f433..be604457db 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift @@ -9,6 +9,7 @@ import Foundation import SwiftUI import Combine +/// The CodeEdit workspace settings model. final class CEWorkspaceSettings: ObservableObject { @ObservedObject private var workspace: WorkspaceDocument @Published public var preferences: CEWorkspaceSettingsData = .init() diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift index 049e27e7fd..c0ef499bd0 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift @@ -10,14 +10,11 @@ import Foundation /// # Workspace Settings /// -/// The model structure of settings for `CodeEdit` +/// The model structure of the workspace 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. +/// A `JSON` representation is persisted in the workspace's `./codeedit/settings.json`. file /// -/// - Note: Also make sure to implement the ``init(from:)`` initializer, decoding +/// - Note: 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. diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift index cf4bdb3e40..7e2567bb07 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift @@ -8,7 +8,7 @@ import Foundation import SwiftUI -/// A struct for a settings page +/// A struct for a workspace settings page struct CEWorkspaceSettingsPage: Hashable, Equatable, Identifiable { /// A struct for a sidebar icon, with a base color and SF Symbol enum IconResource: Equatable, Hashable { diff --git a/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift index deaf35c715..6830baa7b9 100644 --- a/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/PageAndCEWorkspaceSettings.swift @@ -11,7 +11,7 @@ struct PageAndCEWorkspaceSettings: Identifiable, Equatable { let id: UUID = UUID() let page: CEWorkspaceSettingsPage let settings: [CEWorkspaceSettingsPage] - + init(_ page: CEWorkspaceSettingsPage) { self.page = page self.settings = CEWorkspaceSettingsData().propertiesOf(page.name) diff --git a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift index 8f742d246d..a48954fec2 100644 --- a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift @@ -50,7 +50,7 @@ struct CETaskFormView: View { } .actionBar { Button { - self.task.env.append(EnvironmentVariable()) + self.task.env.append(CETask.EnvironmentVariable()) } label: { Image(systemName: "plus") } diff --git a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift index 43a0600523..780d2305aa 100644 --- a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift +++ b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift @@ -8,18 +8,20 @@ import SwiftUI struct EnvironmentVariableListItem: View { - @Binding var item: EnvironmentVariable + @Binding var item: CETask.EnvironmentVariable @Binding var selectedItemId: UUID? var delete: (UUID) -> Void + /// State variables added to prevent an exception when deleting + /// the item in the onChange event @State var name: String @State var value: String @FocusState private var isKeyFocused: Bool init( - item: Binding, + item: Binding, selectedItemId: Binding, deleteHandler: @escaping (UUID) -> Void ) { diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift index fdaf18c9ab..d2b9bdb838 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift @@ -7,7 +7,7 @@ import SwiftUI -/// A view that implements the `Project` worksppace settings page +/// A view that implements the `Project` workspace settings page struct ProjectCEWorkspaceSettingsView: View { @State var settings: CEWorkspaceSettingsData.ProjectSettings diff --git a/CodeEdit/Features/CEWorkspace/Extensions/NSWindow+Child.swift b/CodeEdit/Utils/Extensions/NSWindow/NSWindow+Child.swift similarity index 100% rename from CodeEdit/Features/CEWorkspace/Extensions/NSWindow+Child.swift rename to CodeEdit/Utils/Extensions/NSWindow/NSWindow+Child.swift From 1ec85aa32e0b8d886398db0027a395249d94ff58 Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Sat, 13 Apr 2024 13:15:28 +0200 Subject: [PATCH 05/10] Update CodeEditWindowController.swift --- .../Documents/Controllers/CodeEditWindowController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 6449e61df0..b985433842 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -19,8 +19,8 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs var workspace: WorkspaceDocument? var workspaceSettings: CEWorkspaceSettings? - var quickOpenPanel: OverlayPanel? - var commandPalettePanel: OverlayPanel? + var quickOpenPanel: SearchPanel? + var commandPalettePanel: SearchPanel? var navigatorSidebarViewModel: NavigatorSidebarViewModel? var splitViewController: NSSplitViewController! From 597abe256a3b1bb1b58c478661e7f8a19c344b0c Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Sat, 13 Apr 2024 13:33:51 +0200 Subject: [PATCH 06/10] Fixed environment variable list issue --- .../Features/CEWorkspace/Views/EnvironmentVariableListItem.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift index 780d2305aa..ef7e73f271 100644 --- a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift +++ b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift @@ -48,6 +48,7 @@ struct EnvironmentVariableListItem: View { } Divider() TextField("", text: $value) + .focused($isKeyFocused) .disableAutocorrection(true) .autocorrectionDisabled() .labelsHidden() From 11a83efd7ff32dd326c38685b819e8bf06d2dfe7 Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Sun, 14 Apr 2024 07:12:51 +0200 Subject: [PATCH 07/10] SwiftLint fixes --- CodeEdit/Features/WindowCommands/FileCommands.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index b23f46ef37..03b0c199e6 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -86,12 +86,11 @@ struct FileCommands: Commands { Button("Workspace Settings") { NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } - //.keyboardShortcut("w", modifiers: [.control, .option, .command]) .disabled(windowController?.workspace == nil) .onReceive(NSApp.publisher(for: \.keyWindow)) { window in windowController = window?.windowController as? CodeEditWindowController } - + Divider() Button("Save") { From 6778939def5b43265e684a1c4b10c4f109ff1d1e Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Sun, 14 Apr 2024 12:11:49 +0200 Subject: [PATCH 08/10] Revert changes to Package.resolved --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b0f3a974ca..14106a2645 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "3f6921a5ec30d1ecb6d6b205cf27a816c318246bb00f0ea367b997cc66527d32", "pins" : [ { "identity" : "anycodable", @@ -200,5 +199,5 @@ } } ], - "version" : 3 + "version" : 2 } From a050dc5c58f41cb093acd653cfc28f553d4d185b Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Wed, 17 Apr 2024 08:36:23 +0200 Subject: [PATCH 09/10] Updated documentation and some coding style changes --- ...orkspaceSettingsData+ProjectSettings.swift | 9 ++------- ...EWorkspaceSettingsData+TasksSettings.swift | 15 ++++----------- .../Features/CEWorkspace/Models/CETask.swift | 12 ++++++------ .../Models/CEWorkspaceSettings.swift | 19 +++++++------------ .../Models/CEWorkspaceSettingsData.swift | 12 +++--------- .../Models/CEWorkspaceSettingsPage.swift | 7 +++---- .../CEWorkspace/Views/CETaskFormView.swift | 8 ++++---- .../Views/CEWorkspaceSettingsView.swift | 3 +-- .../Views/EnvironmentVariableListItem.swift | 9 ++++----- .../ProjectCEWorkspaceSettingsView.swift | 4 +--- .../CodeEditWindowControllerExtensions.swift | 4 ++-- 11 files changed, 37 insertions(+), 65 deletions(-) diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift index 8b2f0a6668..ac31f56bd4 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift @@ -8,7 +8,7 @@ import SwiftUI extension CEWorkspaceSettingsData { - /// The project setting + /// Workspace settings for the project tab. struct ProjectSettings: Codable, Hashable, SearchableSettingsPage { var searchKeys: [String] { [ @@ -17,19 +17,14 @@ extension CEWorkspaceSettingsData { .map { NSLocalizedString($0, comment: "") } } - /// The project name var projectName: 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.projectName = try container.decodeIfPresent( - String.self, - forKey: .projectName - ) ?? "" + self.projectName = try container.decodeIfPresent(String.self,forKey: .projectName) ?? "" } } } diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift index 317bfdc9e9..5dc1b04851 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift @@ -9,7 +9,7 @@ import Foundation import Collections extension CEWorkspaceSettingsData { - /// The tasks setting + /// Workspace settings for the tasks tab. struct TasksSettings: Codable, Hashable, SearchableSettingsPage { var items: [CETask] = [] @@ -20,23 +20,16 @@ extension CEWorkspaceSettingsData { .map { NSLocalizedString($0, comment: "") } } - /// The tasks behavior of the app + /// The tasks functionality behavior of the app var enabled: 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.items = try container.decodeIfPresent( - [CETask].self, - forKey: .items - ) ?? [] - self.enabled = try container.decodeIfPresent( - Bool.self, - forKey: .enabled - ) ?? true + self.items = try container.decodeIfPresent([CETask].self, forKey: .items) ?? [] + self.enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? true } } } diff --git a/CodeEdit/Features/CEWorkspace/Models/CETask.swift b/CodeEdit/Features/CEWorkspace/Models/CETask.swift index 1780c6a005..46d51016a9 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CETask.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CETask.swift @@ -7,15 +7,14 @@ import SwiftUI -/// Represents a CodeEdit task that will be executed by the a -/// task manager. +/// CodeEdit task that will be executed by the task manager. struct CETask: Identifiable, Hashable, Codable { var id = UUID() var name: String = "" var target: String = "" var workingDirectory: String = "" var command: String = "" - var env: [EnvironmentVariable] = [] + var environmentVariables: [EnvironmentVariable] = [] var isInvalid: Bool { name.isEmpty || @@ -29,7 +28,7 @@ struct CETask: Identifiable, Hashable, Codable { case target case workingDirectory case command - case env + case environmentVariables } struct EnvironmentVariable: Identifiable, Hashable, Codable { @@ -37,6 +36,7 @@ struct CETask: Identifiable, Hashable, Codable { var name: String = "" var value: String = "" + /// Enables encoding the environment variables as a `name`:`value`pair. private struct CodingKeys: CodingKey { var stringValue: String var intValue: Int? @@ -46,14 +46,14 @@ struct CETask: Identifiable, Hashable, Codable { self.intValue = nil } + /// Required by the CodingKey protocol but not being currently used. init?(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } } - init() { - } + init() {} init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift index be604457db..a5a3f1f674 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift @@ -5,7 +5,6 @@ // Created by Axel Martinez on 27/3/24. // -import Foundation import SwiftUI import Combine @@ -16,7 +15,7 @@ final class CEWorkspaceSettings: ObservableObject { private var savedSettings = false private var storeTask: AnyCancellable! - private let filemanager = FileManager.default + private let fileManager = FileManager.default private var folderURL: URL? { guard let workspaceURL = workspace.fileURL else { @@ -40,7 +39,7 @@ final class CEWorkspaceSettings: ObservableObject { self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink { if !self.savedSettings, let folderURL = self.folderURL { - try? self.filemanager.createDirectory(at: folderURL, withIntermediateDirectories: false) + try? self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: false) self.savedSettings = true } @@ -48,25 +47,21 @@ final class CEWorkspaceSettings: ObservableObject { } } - /// Load and construct ``Settings`` model from - /// `.codeedit/settings.json` + /// Load and construct ``CEWorkspaceSettings`` model from `.codeedit/settings.json` private func loadSettings() { if let settingsURL = settingsURL { - if filemanager.fileExists(atPath: settingsURL.path) { + if fileManager.fileExists(atPath: settingsURL.path) { guard let json = try? Data(contentsOf: settingsURL), let prefs = try? JSONDecoder().decode(CEWorkspaceSettingsData.self, from: json) - else { - return - } + else { return } + self.savedSettings = true self.preferences = prefs } } - return } - /// Save``Settings`` model to - /// `.codeedit/settings.json` + /// Save``CEWorkspaceSettings`` model to `.codeedit/settings.json` private func savePreferences(_ data: CEWorkspaceSettingsData) throws { guard let settingsURL = settingsURL else { return } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift index c0ef499bd0..67c61e74a1 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift @@ -10,14 +10,9 @@ import Foundation /// # Workspace Settings /// -/// The model structure of the workspace settings for `CodeEdit` -/// -/// A `JSON` representation is persisted in the workspace's `./codeedit/settings.json`. file -/// -/// - Note: 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. +/// The model of the workspace settings for `CodeEdit` that control the behavior of some functionality at the workspace level +/// like the workspace name or defining tasks. A `JSON` representation is persisted in the workspace's +/// `./codeedit/settings.json`. file struct CEWorkspaceSettingsData: Codable, Hashable { /// The project global settings var project: ProjectSettings = .init() @@ -25,7 +20,6 @@ struct CEWorkspaceSettingsData: Codable, Hashable { /// The tasks settings var tasks: TasksSettings = .init() - /// Default initializer init() {} /// Explicit decoder init for setting default values when key is not present in `JSON` diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift index 7e2567bb07..c762629a24 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift @@ -8,16 +8,16 @@ import Foundation import SwiftUI -/// A struct for a workspace settings page +/// Represents a workspace settings tab. struct CEWorkspaceSettingsPage: Hashable, Equatable, Identifiable { - /// A struct for a sidebar icon, with a base color and SF Symbol + /// 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 + /// All the workspace settings pages enum Name: String { case project = "Project" case tasks = "Tasks" @@ -34,7 +34,6 @@ struct CEWorkspaceSettingsPage: Hashable, Equatable, Identifiable { } let icon: IconResource? - /// Default initializer init( _ name: Name, baseColor: Color? = nil, diff --git a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift index a48954fec2..ff9e41e4a2 100644 --- a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift @@ -33,7 +33,7 @@ struct CETaskFormView: View { } Section(content: { List(selection: $selectedItemId) { - ForEach($task.env) { env in + ForEach($task.environmentVariables) { env in EnvironmentVariableListItem( item: env, selectedItemId: $selectedItemId, @@ -43,14 +43,14 @@ struct CETaskFormView: View { } .frame(minHeight: 56) .overlay { - if task.env.isEmpty { + if task.environmentVariables.isEmpty { Text("No environment variables") .foregroundStyle(Color(.secondaryLabelColor)) } } .actionBar { Button { - self.task.env.append(CETask.EnvironmentVariable()) + self.task.environmentVariables.append(CETask.EnvironmentVariable()) } label: { Image(systemName: "plus") } @@ -72,7 +72,7 @@ struct CETaskFormView: View { } func removeEnv(id: UUID) { - self.task.env.removeAll(where: { + self.task.environmentVariables.removeAll(where: { $0.id == id }) } diff --git a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift index 5e7be1f71a..14ef0e6963 100644 --- a/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/CEWorkspaceSettingsView.swift @@ -10,8 +10,6 @@ import CodeEditSymbols /// A struct for settings struct CEWorkspaceSettingsView: View { - let workspace: WorkspaceDocument - @ObservedObject var settings: CEWorkspaceSettings @StateObject var viewModel = SettingsViewModel() @@ -19,6 +17,7 @@ struct CEWorkspaceSettingsView: View { @State private var searchText: String = "" let window: NSWindow? + let workspace: WorkspaceDocument static var pages: [PageAndCEWorkspaceSettings] = [ .init( diff --git a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift index ef7e73f271..d8a8a9bf3c 100644 --- a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift +++ b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift @@ -8,17 +8,16 @@ import SwiftUI struct EnvironmentVariableListItem: View { + @FocusState private var isKeyFocused: Bool + @Binding var item: CETask.EnvironmentVariable @Binding var selectedItemId: UUID? - var delete: (UUID) -> Void - - /// State variables added to prevent an exception when deleting - /// the item in the onChange event + /// State variables added to prevent an exception when deleting the item in the onChange event. @State var name: String @State var value: String - @FocusState private var isKeyFocused: Bool + var delete: (UUID) -> Void init( item: Binding, diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift index d2b9bdb838..47902d53fd 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/ProjectCEWorkspaceSettingsView.swift @@ -23,8 +23,6 @@ struct ProjectCEWorkspaceSettingsView: View { /// The extension of the view with all the preferences private extension ProjectCEWorkspaceSettingsView { private var projectName: some View { - TextField(text: $settings.projectName) { - Text("Name") - } + TextField("Name", text: $settings.projectName) } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index 075d59839a..369d28d847 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -116,9 +116,9 @@ extension CodeEditWindowController { if let workspaceSettings, let window = window, let workspace = workspace { let workspaceSettingsWindow = NSWindow() let contentView = CEWorkspaceSettingsView( - workspace: workspace, settings: workspaceSettings, - window: workspaceSettingsWindow + window: workspaceSettingsWindow, + workspace: workspace ) workspaceSettingsWindow.contentView = NSHostingView(rootView: contentView) From eaf5d2bfb5a8b6fc8e238cb890eabd270479cf78 Mon Sep 17 00:00:00 2001 From: Axel Martinez Date: Wed, 17 Apr 2024 16:59:27 +0200 Subject: [PATCH 10/10] SwiftLint fixes and bug fixes --- ...orkspaceSettingsData+ProjectSettings.swift | 2 +- ...EWorkspaceSettingsData+TasksSettings.swift | 2 +- .../Models/CEWorkspaceSettingsData.swift | 4 ++-- .../CEWorkspaceSettingsSearchResult.swift | 12 +++++----- .../CEWorkspace/Views/CETaskFormView.swift | 13 ++++++++--- .../Views/EnvironmentVariableListItem.swift | 1 - .../Pages/TasksCEWorkspaceSettingsView.swift | 4 +--- .../CodeEditWindowController.swift | 1 + .../CodeEditWindowControllerExtensions.swift | 22 +++++++++++++------ .../Models/SettingsSearchResult.swift | 12 +++++----- 10 files changed, 41 insertions(+), 32 deletions(-) diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift index ac31f56bd4..d3328c1603 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+ProjectSettings.swift @@ -24,7 +24,7 @@ extension CEWorkspaceSettingsData { /// 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.projectName = try container.decodeIfPresent(String.self,forKey: .projectName) ?? "" + self.projectName = try container.decodeIfPresent(String.self, forKey: .projectName) ?? "" } } } diff --git a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift index 5dc1b04851..70e90b37c8 100644 --- a/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Extensions/CEWorkspaceSettingsData+TasksSettings.swift @@ -28,7 +28,7 @@ extension CEWorkspaceSettingsData { /// 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.items = try container.decodeIfPresent([CETask].self, forKey: .items) ?? [] + self.items = try container.decodeIfPresent([CETask].self, forKey: .items) ?? [] self.enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? true } } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift index 67c61e74a1..c195c25409 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift @@ -10,8 +10,8 @@ import Foundation /// # Workspace Settings /// -/// The model of the workspace settings for `CodeEdit` that control the behavior of some functionality at the workspace level -/// like the workspace name or defining tasks. A `JSON` representation is persisted in the workspace's +/// The model of the workspace settings for `CodeEdit` that control the behavior of some functionality at the workspace +/// level like the workspace name or defining tasks. A `JSON` representation is persisted in the workspace's /// `./codeedit/settings.json`. file struct CEWorkspaceSettingsData: Codable, Hashable { /// The project global settings diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift index bad360acdd..f3bd83eca0 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsSearchResult.swift @@ -5,11 +5,14 @@ // Created by Axel Martinez on 27/3/24. // -import Foundation import SwiftUI // TODO: Extend this struct further to support setting "flashing" -class CEWorkspaceSettingsSearchResult: Identifiable { +final class CEWorkspaceSettingsSearchResult: Identifiable { + let id: UUID = UUID() + let pageFound: Bool + let pages: [CEWorkspaceSettingsPage] + init( pageFound: Bool, pages: [CEWorkspaceSettingsPage] @@ -17,9 +20,4 @@ class CEWorkspaceSettingsSearchResult: Identifiable { self.pageFound = pageFound self.pages = pages } - - let id: UUID = UUID() - - let pageFound: Bool - let pages: [CEWorkspaceSettingsPage] } diff --git a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift index ff9e41e4a2..75b6ab588c 100644 --- a/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/CETaskFormView.swift @@ -56,14 +56,15 @@ struct CETaskFormView: View { } Divider() Button { - if let selectedItemId = selectedItemId { - removeEnv(id: selectedItemId) - } + removeSelectedEnv() } label: { Image(systemName: "minus") } .disabled(selectedItemId == nil) } + .onDeleteCommand { + removeSelectedEnv() + } }, header: { Text("Environment Variables") }) @@ -71,6 +72,12 @@ struct CETaskFormView: View { .formStyle(.grouped) } + func removeSelectedEnv() { + if let selectedItemId = selectedItemId { + removeEnv(id: selectedItemId) + } + } + func removeEnv(id: UUID) { self.task.environmentVariables.removeAll(where: { $0.id == id diff --git a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift index d8a8a9bf3c..2ec51aa93d 100644 --- a/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift +++ b/CodeEdit/Features/CEWorkspace/Views/EnvironmentVariableListItem.swift @@ -47,7 +47,6 @@ struct EnvironmentVariableListItem: View { } Divider() TextField("", text: $value) - .focused($isKeyFocused) .disableAutocorrection(true) .autocorrectionDisabled() .labelsHidden() diff --git a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift index 5a724fac12..cb2758f289 100644 --- a/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkspace/Views/Pages/TasksCEWorkspaceSettingsView.swift @@ -20,9 +20,7 @@ struct TasksCEWorkspaceSettingsView: View { var body: some View { SettingsForm { Section { - TextField(text: $projectSettings.projectName) { - Text("Name") - } + TextField("Name", text: $projectSettings.projectName) Toggle("Tasks", isOn: $settings.enabled) } Section( diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index b985433842..73f6550f2c 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -19,6 +19,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs var workspace: WorkspaceDocument? var workspaceSettings: CEWorkspaceSettings? + var workspaceSettingsWindow: NSWindow? var quickOpenPanel: SearchPanel? var commandPalettePanel: SearchPanel? var navigatorSidebarViewModel: NavigatorSidebarViewModel? diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index 369d28d847..4cf51eb3da 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -113,19 +113,27 @@ extension CodeEditWindowController { } @IBAction func openWorkspaceSettings(_ sender: Any) { - if let workspaceSettings, let window = window, let workspace = workspace { - let workspaceSettingsWindow = NSWindow() + guard let workspaceSettings, let window = window, let workspace = workspace else { + return + } + + if let workspaceSettingsWindow, workspaceSettingsWindow.isVisible { + workspaceSettingsWindow.makeKeyAndOrderFront(self) + } else { + let settingsWindow = NSWindow() + self.workspaceSettingsWindow = settingsWindow let contentView = CEWorkspaceSettingsView( settings: workspaceSettings, - window: workspaceSettingsWindow, + window: settingsWindow, workspace: workspace ) - workspaceSettingsWindow.contentView = NSHostingView(rootView: contentView) - workspaceSettingsWindow.titlebarAppearsTransparent = true - workspaceSettingsWindow.setContentSize(NSSize(width: 515, height: 515)) + settingsWindow.contentView = NSHostingView(rootView: contentView) + settingsWindow.titlebarAppearsTransparent = true + settingsWindow.setContentSize(NSSize(width: 515, height: 515)) + settingsWindow.makeKeyAndOrderFront(self) - window.addCenteredChildWindow(workspaceSettingsWindow, over: window) + window.addCenteredChildWindow(settingsWindow, over: window) } } } diff --git a/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift b/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift index 22f2ab2564..1911ba3e4d 100644 --- a/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift +++ b/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift @@ -5,11 +5,14 @@ // Created by Raymond Vleeshouwer on 17/06/23. // -import Foundation import SwiftUI // TODO: Extend this struct further to support setting "flashing" -class SettingsSearchResult: Identifiable { +final class SettingsSearchResult: Identifiable { + let id: UUID = UUID() + let pageFound: Bool + let pages: [SettingsPage] + init( pageFound: Bool, pages: [SettingsPage] @@ -17,9 +20,4 @@ class SettingsSearchResult: Identifiable { self.pageFound = pageFound self.pages = pages } - - let id: UUID = UUID() - - let pageFound: Bool - let pages: [SettingsPage] }