From 512222721427bac896db4abf8ae5ae318c3774f0 Mon Sep 17 00:00:00 2001 From: luah5 <128280019+luah5@users.noreply.github.com> Date: Sat, 30 Sep 2023 16:46:11 +0100 Subject: [PATCH] Settings Search (#1312) * add support for searching settings * code cleanup and fixes * respond to some review comments from @austincondiff, * highlight the containing letters/word * partially working "non grouping" settings search * fully working non grouping settings. * fix swiftlint and remaining bugs * final fixes and documentation * bug fixes and remove swiftlint:disable * remove uneeded code * rename file and make text primary when there is no search text * partially respond to @Wouter's comments by adding a dictionary for settings translation and add locationsSettings for better search results * changes * fix swiftlint * final fix for swiftlint * refactoring * edit docs for adding new setting/settings page * refactoring * fix swiftlint * restructure SettingsPage.swift * partially fix review * fully fix review * fix minor UI bugs * remove uneeded font parameter * fix weird looking text * remove swiftlint disable * fix remaining swiftlint errors * Update CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift Co-authored-by: Austin Condiff * remove unneeded `internal` * apply suggestions from 0xWDG's review Co-authored-by: Wesley de Groot * Fix swiftlint errors --------- Co-authored-by: Austin Condiff Co-authored-by: Wesley de Groot --- CodeEdit.xcodeproj/project.pbxproj | 40 +++- .../xcshareddata/swiftpm/Package.resolved | 6 +- .../Git/Accounts/Networking/GitRouter.swift | 4 - .../Features/Keybindings/CommandManager.swift | 5 - .../Keybindings/ModifierKeysObserver.swift | 5 - .../Settings/Models/PageAndSettings.swift | 19 ++ .../Settings/Models/SettingsData.swift | 52 ++++- .../Settings/Models/SettingsPage.swift | 56 +++--- .../Models/SettingsSearchResult.swift | 25 +++ .../Models/AccountsSettings.swift | 12 +- .../Models/SourceControlAccount.swift | 19 -- .../FeatureFlags/FeatureFlagsSettings.swift | 15 -- .../Models/FeatureFlagsSettings.swift | 24 +++ .../Models/GeneralSettings.swift | 24 ++- .../Models/KeybindingsSettings.swift | 1 + .../LocationsSettingsView.swift | 4 +- .../Models/LocationsSettings.swift | 24 +++ .../Models/SourceControlSettings.swift | 28 ++- .../Models/TerminalSettings.swift | 16 +- .../Models/TextEditingSettings.swift | 19 +- .../ThemeSettings/Models/ThemeSettings.swift | 19 +- CodeEdit/Features/Settings/SettingsView.swift | 137 ++++++++++--- .../Settings/Views/SettingsPageView.swift | 63 +++--- .../String/String+HighlightOccurrences.swift | 56 ++++++ .../Protocols/SearchableSettingsPage.swift | 12 ++ .../AppPreferences/AppPreferences.md | 32 +-- .../AppPreferences/Create a View.md | 190 +++++++++--------- .../AppPreferences/Getting Started.md | 19 +- .../Sections/AccountPreferencesView.md | 4 +- .../Sections/GeneralPreferencesView.md | 4 +- .../Sections/KeybindingsPreferencesView.md | 4 +- .../Sections/SourceControlPreferencesView.md | 4 +- .../Sections/TerminalPreferencesView.md | 4 +- .../Sections/TextEditingPreferencesView.md | 4 +- .../Sections/ThemePreferencesView.md | 4 +- 35 files changed, 662 insertions(+), 292 deletions(-) create mode 100644 CodeEdit/Features/Settings/Models/PageAndSettings.swift create mode 100644 CodeEdit/Features/Settings/Models/SettingsSearchResult.swift delete mode 100644 CodeEdit/Features/Settings/Pages/FeatureFlags/FeatureFlagsSettings.swift create mode 100644 CodeEdit/Features/Settings/Pages/FeatureFlags/Models/FeatureFlagsSettings.swift create mode 100644 CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift create mode 100644 CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift create mode 100644 CodeEdit/Utils/Protocols/SearchableSettingsPage.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index aececcf40..3e449540a 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -333,7 +333,12 @@ 6CFF967C29BEBD5200182D6F /* WindowCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */; }; 850C631029D6B01D00E1444C /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C630F29D6B01D00E1444C /* SettingsView.swift */; }; 850C631229D6B03400E1444C /* SettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C631129D6B03400E1444C /* SettingsPage.swift */; }; + 852C7E332A587279006BA599 /* SearchableSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852C7E322A587279006BA599 /* SearchableSettingsPage.swift */; }; + 852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852E62002A5C17E500447138 /* PageAndSettings.swift */; }; + 85745D632A38F8D900089AAB /* String+HighlightOccurrences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */; }; + 85773E1E2A3E0A1F00C5D926 /* SettingsSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */; }; 85CD0C5F2A10CC3200E531FD /* URL+isImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */; }; + 85E4122A2A46C8CA00183F2B /* LocationsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E412292A46C8CA00183F2B /* LocationsSettings.swift */; }; B6041F4D29D7A4E9000F3454 /* SettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */; }; B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */; }; B60BE8BD297A167600841125 /* AcknowledgementRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */; }; @@ -782,7 +787,12 @@ 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCommands.swift; sourceTree = ""; }; 850C630F29D6B01D00E1444C /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 850C631129D6B03400E1444C /* SettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPage.swift; sourceTree = ""; }; + 852C7E322A587279006BA599 /* SearchableSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchableSettingsPage.swift; sourceTree = ""; }; + 852E62002A5C17E500447138 /* PageAndSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageAndSettings.swift; sourceTree = ""; }; + 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+HighlightOccurrences.swift"; sourceTree = ""; }; + 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchResult.swift; sourceTree = ""; }; 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+isImage.swift"; sourceTree = ""; }; + 85E412292A46C8CA00183F2B /* LocationsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsSettings.swift; sourceTree = ""; }; B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPageView.swift; sourceTree = ""; }; B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindow.swift; sourceTree = ""; }; B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementRowView.swift; sourceTree = ""; }; @@ -1248,6 +1258,7 @@ isa = PBXGroup; children = ( 58F2EACD292FB2B0004A9BDE /* Loopable.swift */, + 852C7E322A587279006BA599 /* SearchableSettingsPage.swift */, ); path = Protocols; sourceTree = ""; @@ -1975,6 +1986,7 @@ 6CED16E32A3E660D000EC962 /* String+Lines.swift */, 58D01C8D293167DC00C5B6B4 /* String+RemoveOccurrences.swift */, 58D01C8C293167DC00C5B6B4 /* String+SHA256.swift */, + 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */, ); path = String; sourceTree = ""; @@ -2108,8 +2120,8 @@ 6C5BE51A2A3D5419002DA0FC /* FeatureFlags */ = { isa = PBXGroup; children = ( + 85AEE8D42A474EEC009507BC /* Models */, 6C5BE51B2A3D542B002DA0FC /* FeatureFlagsSettingsView.swift */, - 6C5BE51D2A3D545F002DA0FC /* FeatureFlagsSettings.swift */, ); path = FeatureFlags; sourceTree = ""; @@ -2173,6 +2185,14 @@ path = Text; sourceTree = ""; }; + 85AEE8D42A474EEC009507BC /* Models */ = { + isa = PBXGroup; + children = ( + 6C5BE51D2A3D545F002DA0FC /* FeatureFlagsSettings.swift */, + ); + path = Models; + sourceTree = ""; + }; 85CD0C5D2A10CC2500E531FD /* URL */ = { isa = PBXGroup; children = ( @@ -2181,6 +2201,14 @@ path = URL; sourceTree = ""; }; + 85E412282A46C8B900183F2B /* Models */ = { + isa = PBXGroup; + children = ( + 85E412292A46C8CA00183F2B /* LocationsSettings.swift */, + ); + path = Models; + sourceTree = ""; + }; B61DA9DD29D929BF00BF4A43 /* Pages */ = { isa = PBXGroup; children = ( @@ -2228,6 +2256,8 @@ 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */, 6C0D0C6729E861B000AE4D3F /* SettingsSidebarFix.swift */, 850C631129D6B03400E1444C /* SettingsPage.swift */, + 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */, + 852E62002A5C17E500447138 /* PageAndSettings.swift */, ); path = Models; sourceTree = ""; @@ -2547,6 +2577,7 @@ B6F0516E29D9E35300D72287 /* LocationsSettings */ = { isa = PBXGroup; children = ( + 85E412282A46C8B900183F2B /* Models */, B6F0516F29D9E36800D72287 /* LocationsSettingsView.swift */, ); path = LocationsSettings; @@ -2556,8 +2587,8 @@ isa = PBXGroup; children = ( 58F2EAE0292FB2B0004A9BDE /* ThemeSettings.swift */, - B6EA1FE629DA341D001BF195 /* Theme.swift */, B6EA1FE429DA33DB001BF195 /* ThemeModel.swift */, + B6EA1FE629DA341D001BF195 /* Theme.swift */, ); path = Models; sourceTree = ""; @@ -2866,6 +2897,7 @@ 6C0D0C6829E861B000AE4D3F /* SettingsSidebarFix.swift in Sources */, 587B9E8429301D8F00AC7927 /* GitHubUser.swift in Sources */, 2072FA13280D74ED00C7F8D4 /* HistoryInspectorModel.swift in Sources */, + 852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */, 587B9DA029300ABD00AC7927 /* PanelDivider.swift in Sources */, 58822534292C280D00E83CDE /* CursorLocation.swift in Sources */, 201169E52837B40300F92B46 /* SourceControlNavigatorRepositoriesView.swift in Sources */, @@ -2933,6 +2965,7 @@ 5878DAB1291D627C00DD95A3 /* EditorPathBarComponent.swift in Sources */, 587B9E6E29301D8F00AC7927 /* GitLabProject.swift in Sources */, 58798234292E30B90085B254 /* FeedbackIssueArea.swift in Sources */, + 852C7E332A587279006BA599 /* SearchableSettingsPage.swift in Sources */, 587B9E5F29301D8F00AC7927 /* GitLabProjectRouter.swift in Sources */, 587B9E7329301D8F00AC7927 /* GitRouter.swift in Sources */, 6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */, @@ -2970,6 +3003,7 @@ B61DA9DF29D929E100BF4A43 /* GeneralSettingsView.swift in Sources */, B6D7EA5C297107DD00301FAC /* InspectorField.swift in Sources */, 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */, + 85E4122A2A46C8CA00183F2B /* LocationsSettings.swift in Sources */, 581550D129FBD30400684881 /* TextTableViewCell.swift in Sources */, 587B9E6629301D8F00AC7927 /* GitLabProjectHook.swift in Sources */, 587B9E9329301D8F00AC7927 /* BitBucketOAuthConfiguration.swift in Sources */, @@ -2982,6 +3016,7 @@ 58798219292D92370085B254 /* SearchModeModel.swift in Sources */, 6C5C891B2A3F736500A94FE1 /* FocusedValues.swift in Sources */, B62AEDD72A27B3D0009A9F52 /* UtilityAreaTabViewModel.swift in Sources */, + 85773E1E2A3E0A1F00C5D926 /* SettingsSearchResult.swift in Sources */, B66A4E4F29C917B8004573B4 /* WelcomeWindow.swift in Sources */, 58D01C9D293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift in Sources */, B6E41C8B29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift in Sources */, @@ -3146,6 +3181,7 @@ B6D7EA592971078500301FAC /* InspectorSection.swift in Sources */, B6AB09A32AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift in Sources */, 58F2EAEF292FB2B0004A9BDE /* ThemeSettingsView.swift in Sources */, + 85745D632A38F8D900089AAB /* String+HighlightOccurrences.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorAreaView.swift in Sources */, 587B9DA229300ABD00AC7927 /* EffectView.swift in Sources */, 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */, diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f3018a654..969fb9a88 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -39,7 +39,7 @@ { "identity" : "codeedittextview", "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditTextView", + "location" : "https://github.com/CodeEditApp/CodeEditTextView.git", "state" : { "revision" : "7f130bd50bb9eb6bacf6a42700cce571ec82bd64", "version" : "0.6.7" @@ -230,8 +230,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { - "revision" : "84196bab1c7f05ad8c3c2a5bfb3058b1211e189f", - "version" : "0.6.1" + "revision" : "6dce3c8f5bfa8bc20120c7497da27e984a8813aa", + "version" : "0.7.0" } }, { diff --git a/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift b/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift index a6da02982..c1d16e44d 100644 --- a/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift +++ b/CodeEdit/Features/Git/Accounts/Networking/GitRouter.swift @@ -21,10 +21,6 @@ enum GitHTTPEncoding: Int { struct GitHTTPHeader { var headerField: String var value: String - init(headerField: String, value: String) { - self.headerField = headerField - self.value = value - } } protocol GitRouterConfiguration { diff --git a/CodeEdit/Features/Keybindings/CommandManager.swift b/CodeEdit/Features/Keybindings/CommandManager.swift index 4ac98e9b2..394f58494 100644 --- a/CodeEdit/Features/Keybindings/CommandManager.swift +++ b/CodeEdit/Features/Keybindings/CommandManager.swift @@ -63,11 +63,6 @@ struct Command: Identifiable, Hashable { let id: String let title: String let closureWrapper: CommandClosureWrapper - init(id: String, title: String, closureWrapper: CommandClosureWrapper) { - self.id = id - self.title = title - self.closureWrapper = closureWrapper - } } /// A simple wrapper for command closure diff --git a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift index 8bce8159b..c0a1329c1 100644 --- a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift +++ b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift @@ -36,11 +36,6 @@ extension NSEvent { let scope: Scope let matching: EventTypeMask - init(scope: Scope, matching: EventTypeMask) { - self.scope = scope - self.matching = matching - } - public func receive(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input { let subscription = Subscription(scope: scope, matching: matching, subscriber: subscriber) subscriber.receive(subscription: subscription) diff --git a/CodeEdit/Features/Settings/Models/PageAndSettings.swift b/CodeEdit/Features/Settings/Models/PageAndSettings.swift new file mode 100644 index 000000000..3297fcb06 --- /dev/null +++ b/CodeEdit/Features/Settings/Models/PageAndSettings.swift @@ -0,0 +1,19 @@ +// +// PageAndSettings.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 10/07/23. +// + +import Foundation + +struct PageAndSettings: Identifiable, Equatable { + let id: UUID = UUID() + let page: SettingsPage + let settings: [SettingsPage] + + init(_ page: SettingsPage) { + self.page = page + self.settings = SettingsData().propertiesOf(page.name) + } +} diff --git a/CodeEdit/Features/Settings/Models/SettingsData.swift b/CodeEdit/Features/Settings/Models/SettingsData.swift index 96560de55..011b48ed4 100644 --- a/CodeEdit/Features/Settings/Models/SettingsData.swift +++ b/CodeEdit/Features/Settings/Models/SettingsData.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Foundation /// # Settings /// @@ -22,28 +23,28 @@ import SwiftUI /// and providing a default value. Otherwise all settings get overridden. struct SettingsData: Codable, Hashable { - /// The general global setting + /// The general global settings var general: GeneralSettings = .init() - /// The global settings for text editing + /// The global settings for accounts var accounts: AccountsSettings = .init() /// The global settings for themes var theme: ThemeSettings = .init() - /// The global settings for the terminal emulator - var terminal: TerminalSettings = .init() - /// The global settings for text editing var textEditing: TextEditingSettings = .init() - /// The global settings for text editing + /// The global settings for the terminal emulator + var terminal: TerminalSettings = .init() + + /// The global settings for source control var sourceControl: SourceControlSettings = .init() /// The global settings for keybindings var keybindings: KeybindingsSettings = .init() - /// Featureflags settings + /// Feature Flags settings var featureFlags: FeatureFlagsSettings = .init() /// Default initializer @@ -61,7 +62,42 @@ struct SettingsData: Codable, Hashable { SourceControlSettings.self, forKey: .sourceControl ) ?? .init() - self.keybindings = try container.decodeIfPresent(KeybindingsSettings.self, forKey: .keybindings) ?? .init() + self.keybindings = try container.decodeIfPresent( + KeybindingsSettings.self, + forKey: .keybindings + ) ?? .init() self.featureFlags = try container.decodeIfPresent(FeatureFlagsSettings.self, forKey: .featureFlags) ?? .init() } + + // swiftlint:disable cyclomatic_complexity + func propertiesOf(_ name: SettingsPage.Name) -> [SettingsPage] { + var settings: [SettingsPage] = [] + + switch name { + case .general: + general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .accounts: + accounts.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .theme: + theme.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .textEditing: + textEditing.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .terminal: + terminal.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .sourceControl: + sourceControl.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .location: + LocationsSettings().searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .featureFlags: + featureFlags.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .behavior: return [.init(name, settingName: "Error")] + case .navigation: return [.init(name, settingName: "Error")] + case .components: return [.init(name, settingName: "Error")] + case .keybindings: return [.init(name, settingName: "Error")] + case .advanced: return [.init(name, settingName: "Error")] + } + + return settings + } + // swiftlint:enable cyclomatic_complexity } diff --git a/CodeEdit/Features/Settings/Models/SettingsPage.swift b/CodeEdit/Features/Settings/Models/SettingsPage.swift index 41788f2a6..e8903fb18 100644 --- a/CodeEdit/Features/Settings/Models/SettingsPage.swift +++ b/CodeEdit/Features/Settings/Models/SettingsPage.swift @@ -8,32 +8,8 @@ import Foundation import SwiftUI -/// A struct for a preferences tab -struct SettingsPage: Hashable, Identifiable { - /// Default intializer - internal init( - _ name: Name, - baseColor: Color? = nil, - icon: IconResource? = nil, - hideName: Bool? = false, - children: [SettingsPage] = [] - ) { - self.children = children - self.name = name - self.baseColor = baseColor ?? .red - self.icon = icon ?? .system("questionmark.app") - self.hideName = hideName - } - - var id: String { name.rawValue } - - let name: Name - let baseColor: Color - let children: [SettingsPage] - let hideName: Bool? - var nameString: LocalizedStringKey { LocalizedStringKey(name.rawValue) } - let icon: IconResource? - +/// A struct for a settings page +struct SettingsPage: Hashable, Equatable, Identifiable { /// A struct for a sidebar icon, with a base color and SF Symbol enum IconResource: Equatable, Hashable { case system(_ name: String) @@ -41,9 +17,8 @@ struct SettingsPage: Hashable, Identifiable { case asset(_ name: String) } - /// An enum of all the preferences tabs + /// An enum of all the settings pages enum Name: String { - // MARK: - App Preferences case general = "General" case accounts = "Accounts" case behavior = "Behaviors" @@ -59,4 +34,29 @@ struct SettingsPage: Hashable, Identifiable { case advanced = "Advanced" } + let id: UUID = UUID() + + let name: Name + let baseColor: Color? + let isSetting: Bool + let settingName: String + var nameString: LocalizedStringKey { + LocalizedStringKey(name.rawValue) + } + let icon: IconResource? + + /// Default initializer + init( + _ name: Name, + baseColor: Color? = nil, + icon: IconResource? = nil, + isSetting: Bool = false, + settingName: String = "" + ) { + self.name = name + self.baseColor = baseColor + self.icon = icon + self.isSetting = isSetting + self.settingName = settingName + } } diff --git a/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift b/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift new file mode 100644 index 000000000..22f2ab256 --- /dev/null +++ b/CodeEdit/Features/Settings/Models/SettingsSearchResult.swift @@ -0,0 +1,25 @@ +// +// SettingsSearchResult.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 17/06/23. +// + +import Foundation +import SwiftUI + +// TODO: Extend this struct further to support setting "flashing" +class SettingsSearchResult: Identifiable { + init( + pageFound: Bool, + pages: [SettingsPage] + ) { + self.pageFound = pageFound + self.pages = pages + } + + let id: UUID = UUID() + + let pageFound: Bool + let pages: [SettingsPage] +} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift index 10e5b5f68..b0220a2fc 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift @@ -10,10 +10,20 @@ import Foundation extension SettingsData { /// The global settings for text editing - struct AccountsSettings: Codable, Hashable { + struct AccountsSettings: Codable, Hashable, SearchableSettingsPage { /// An integer indicating how many spaces a `tab` will generate var sourceControlAccounts: GitAccounts = .init() + /// The search keys + var searchKeys: [String] { + [ + "Accounts", + "Delete Account...", + "Add Account..." + ] + .map { NSLocalizedString($0, comment: "") } + } + /// Default initializer init() {} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift index 41c9b9e99..fdc2b4fd6 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift @@ -8,25 +8,6 @@ import SwiftUI struct SourceControlAccount: Codable, Identifiable, Hashable { - internal init( - id: String, - name: String, - description: String, - provider: Provider, - serverURL: String, - urlProtocol: URLProtocol, - sshKey: String, - isTokenValid: Bool - ) { - self.id = id - self.name = name - self.description = description - self.provider = provider - self.serverURL = serverURL - self.urlProtocol = urlProtocol - self.sshKey = sshKey - self.isTokenValid = isTokenValid - } var id: String var name: String diff --git a/CodeEdit/Features/Settings/Pages/FeatureFlags/FeatureFlagsSettings.swift b/CodeEdit/Features/Settings/Pages/FeatureFlags/FeatureFlagsSettings.swift deleted file mode 100644 index 5f91ef789..000000000 --- a/CodeEdit/Features/Settings/Pages/FeatureFlags/FeatureFlagsSettings.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// FeatureFlagsSettings.swift -// CodeEdit -// -// Created by Wouter Hennen on 17/06/2023. -// - -import Foundation - -extension SettingsData { - - struct FeatureFlagsSettings: Codable, Hashable { - var useNewWindowingSystem = false - } -} diff --git a/CodeEdit/Features/Settings/Pages/FeatureFlags/Models/FeatureFlagsSettings.swift b/CodeEdit/Features/Settings/Pages/FeatureFlags/Models/FeatureFlagsSettings.swift new file mode 100644 index 000000000..f612c8d78 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/FeatureFlags/Models/FeatureFlagsSettings.swift @@ -0,0 +1,24 @@ +// +// FeatureFlagsSettings.swift +// CodeEdit +// +// Created by Wouter Hennen on 17/06/2023. +// + +import Foundation + +extension SettingsData { + + struct FeatureFlagsSettings: Codable, Hashable, SearchableSettingsPage { + + /// The search keys + var searchKeys: [String] { + [ + "New Windowing System" + ] + .map { NSLocalizedString($0, comment: "") } + } + + var useNewWindowingSystem = false + } +} diff --git a/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift b/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift index 0ef39fd49..2293ae2ca 100644 --- a/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift +++ b/CodeEdit/Features/Settings/Pages/GeneralSettings/Models/GeneralSettings.swift @@ -10,7 +10,7 @@ import SwiftUI extension SettingsData { /// The general global setting - struct GeneralSettings: Codable, Hashable { + struct GeneralSettings: Codable, Hashable, SearchableSettingsPage { /// The appearance of the app var appAppearance: Appearances = .system @@ -21,6 +21,28 @@ extension SettingsData { /// 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", + "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" + ] + .map { NSLocalizedString($0, comment: "") } + } + /// Show editor path bar var showEditorPathBar: Bool = true diff --git a/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift b/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift index 7c0a961d7..429d45552 100644 --- a/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift +++ b/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift @@ -11,6 +11,7 @@ extension SettingsData { /// The global settings for text editing struct KeybindingsSettings: Codable, Hashable { + /// An integer indicating how many spaces a `tab` will generate var keybindings: [String: KeyboardShortcutWrapper] = .init() diff --git a/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift b/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift index d6a287e10..d797cc1f6 100644 --- a/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift +++ b/CodeEdit/Features/Settings/Pages/LocationsSettings/LocationsSettingsView.swift @@ -7,7 +7,7 @@ import SwiftUI -/// A view that implements the `Locations` preference section +/// A view that implements the `Locations` settings section struct LocationsSettingsView: View { var body: some View { SettingsForm { @@ -52,7 +52,7 @@ private extension LocationsSettingsView { private var extensionsLocation: some View { ExternalLink(destination: ThemeModel.shared.extensionsURL) { Text("Extensions") - Text(ThemeModel.shared.extensionsURL.path) + Text(ThemeModel.shared.extensionsURL.path()) .font(.footnote) .foregroundColor(.secondary) } diff --git a/CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift b/CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift new file mode 100644 index 000000000..9481b1f01 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/LocationsSettings/Models/LocationsSettings.swift @@ -0,0 +1,24 @@ +// +// LocationsSettings.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 24/06/23. +// + +import Foundation + +extension SettingsData { + + struct LocationsSettings: SearchableSettingsPage { + + /// The search keys + var searchKeys: [String] { + [ + "Settings Location", + "Themes Location", + "Extensions Location" + ] + .map { NSLocalizedString($0, comment: "") } + } + } +} diff --git a/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift b/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift index 2e67c6149..097825782 100644 --- a/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift +++ b/CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/SourceControlSettings.swift @@ -9,13 +9,39 @@ import Foundation extension SettingsData { /// The global settings for source control - struct SourceControlSettings: Codable, Hashable { + struct SourceControlSettings: Codable, Hashable, SearchableSettingsPage { + + var searchKeys: [String] { + [ + "General", + "Enable source control", + "Refresh local status automatically", + "Fetch and refresh server status automatically", + "Add and remove files automatically", + "Select files to commit automatically", + "Show source control changes", + "Include upstream changes", + "Comparison view", + "Source control navigator", + "Default branch name", + "Git", + "Author Name", + "Author Email", + "Prefer to rebase when pulling", + "Show merge commits in per-file log" + ] + .map { NSLocalizedString($0, comment: "") } + } + /// The general source control settings var general: SourceControlGeneral = .init() + /// The source control git settings var git: SourceControlGit = .init() + /// Default initializer init() {} + /// Explicit decoder init for setting default values when key is not present in `JSON` init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) diff --git a/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift b/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift index fa7e4dc07..ef5ec0cac 100644 --- a/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift +++ b/CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift @@ -10,7 +10,21 @@ import Foundation extension SettingsData { /// The global settings for the terminal emulator - struct TerminalSettings: Codable, Hashable { + struct TerminalSettings: Codable, Hashable, SearchableSettingsPage { + + /// The search keys + var searchKeys: [String] { + [ + "Shell", + "Use \"Option\" key as \"Meta\"", + "Use text editor font", + "Font", + "Font Size", + "Terminal Cursor Style", + "Blink Cursor" + ] + .map { NSLocalizedString($0, comment: "") } + } /// If true terminal will use editor theme. var useEditorTheme: Bool = true diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift index 200a02585..508d6ce9e 100644 --- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift +++ b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift @@ -11,7 +11,24 @@ import Foundation extension SettingsData { /// The global settings for text editing - struct TextEditingSettings: Codable, Hashable { + struct TextEditingSettings: Codable, Hashable, SearchableSettingsPage { + + var searchKeys: [String] { + [ + "Prefer Indent Using", + "Tab Width", + "Wrap lines to editor width", + "Font", + "Font Size", + "Line Height", + "Letter Spacing", + "Autocomplete braces", + "Enable type-over completion", + "Bracket Pair Highlight" + ] + .map { NSLocalizedString($0, comment: "") } + } + /// An integer indicating how many spaces a `tab` will appear as visually. var defaultTabWidth: Int = 4 diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift index 4ca3a18cb..a4285b6f9 100644 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift +++ b/CodeEdit/Features/Settings/Pages/ThemeSettings/Models/ThemeSettings.swift @@ -30,7 +30,24 @@ extension SettingsData { typealias ThemeOverrides = [String: [String: Theme.Attributes]] /// The global settings for themes - struct ThemeSettings: Codable, Hashable { + struct ThemeSettings: Codable, Hashable, SearchableSettingsPage { + + var searchKeys: [String] { + [ + "Automatically Change theme based on system appearance", + "Always use dark terminal appearance", + "Use theme background", + "Light Appearance", + "GitHub Light", + "Xcode Light", + "Solarized Light", + "Solarized Dark", + "Midnight", + "Xcode Dark", + "GitHub Dark" + ] + .map { NSLocalizedString($0, comment: "") } + } /// The name of the currently selected dark theme var selectedDarkTheme: String = "codeedit-xcode-dark" diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift index 708039f00..e5107abbf 100644 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ b/CodeEdit/Features/Settings/SettingsView.swift @@ -7,8 +7,6 @@ import SwiftUI import CodeEditSymbols -import AppKit -import Introspect /// A struct for settings struct SettingsView: View { @@ -16,42 +14,129 @@ struct SettingsView: View { @Environment(\.colorScheme) private var colorScheme - /// An array of navigationItem(s) - private static let pages: [SettingsPage] = [ - .init(.general, baseColor: .gray, icon: .system("gear")), - .init(.accounts, baseColor: .blue, icon: .system("at")), -// .init(.behaviors, baseColor: .red, icon: .system("flowchart.fill")), -// .init(.navigation, baseColor: .green, icon: .system("arrow.triangle.turn.up.right.diamond.fill")), - .init(.theme, baseColor: .pink, icon: .system("paintbrush.fill")), - .init(.textEditing, baseColor: .blue, icon: .system("pencil.line")), - .init(.terminal, baseColor: .blue, icon: .system("terminal.fill")), -// .init(.keybindings, baseColor: .gray, icon: .system("keyboard.fill")), - .init(.sourceControl, baseColor: .blue, icon: .symbol("vault")), -// .init(.components, baseColor: .blue, icon: .system("puzzlepiece.fill")), - .init(.location, baseColor: .green, icon: .system("externaldrive.fill")), - .init(.featureFlags, baseColor: .cyan, icon: .system("flag.2.crossed.fill")) -// .init(.advanced, baseColor: .gray, icon: .system("gearshape.2.fill")) - ] - /// Variables for the selected Page, the current search text and software updater - @State private var selectedPage = pages.first! + @State private var selectedPage: SettingsPage = .init( + .general, + baseColor: .gray, + icon: .system("gear") + ) @State private var searchText: String = "" - @ObservedObject private var settings: Settings = .shared - @Environment(\.presentationMode) var presentationMode + static var pages: [PageAndSettings] = [ + .init( + SettingsPage( + .general, + baseColor: .gray, + icon: .system("gear") + ) + ), + .init( + SettingsPage( + .accounts, + baseColor: .blue, + icon: .system("at") + ) + ), + .init( + SettingsPage( + .theme, + baseColor: .pink, + icon: .system("paintbrush.fill") + ) + ), + .init( + SettingsPage( + .textEditing, + baseColor: .blue, + icon: .system("pencil.line") + ) + ), + .init( + SettingsPage( + .terminal, + baseColor: .blue, + icon: .system("terminal.fill") + ) + ), + .init( + SettingsPage( + .sourceControl, + baseColor: .blue, + icon: .symbol("vault") + ) + ), + .init( + SettingsPage( + .location, + baseColor: .green, + icon: .system("externaldrive.fill") + ) + ), + .init( + SettingsPage( + .featureFlags, + baseColor: .cyan, + icon: .system("flag.2.crossed.fill") + ) + ) + ] + + @ObservedObject private var settings: Settings = .shared + let updater: SoftwareUpdater + /// Searches through an array of pages to check if a page name exists in the array + private func resultFound(_ page: SettingsPage, pages: [SettingsPage]) -> SettingsSearchResult { + let lowercasedSearchText = searchText.lowercased() + var returnedPages: [SettingsPage] = [] + var foundPage = false + + for item in pages where item.name == page.name { + if item.isSetting && item.settingName.lowercased().contains(lowercasedSearchText) { + returnedPages.append(item) + } else if item.name.rawValue.contains(lowercasedSearchText) && !item.isSetting { + foundPage = true + } + } + + return SettingsSearchResult(pageFound: foundPage, pages: returnedPages) + } + + /// Gets search results from a settings page and an array of settings + @ViewBuilder + private func results(_ page: SettingsPage, _ settings: [SettingsPage]) -> some View { + if !searchText.isEmpty { + let results: SettingsSearchResult = resultFound(page, pages: settings) + + if !results.pages.isEmpty && !page.isSetting { + SettingsPageView(page, searchText: searchText) + + ForEach(results.pages, id: \.settingName) { setting in + NavigationLink(value: setting) { + setting.settingName.capitalized.highlightOccurrences(searchText) + .padding(.leading, 22) + } + } + } else if + page.name.rawValue.lowercased().contains(searchText.lowercased()) && + !page.isSetting + { + SettingsPageView(page, searchText: searchText) + } + } else if !page.isSetting { + SettingsPageView(page, searchText: searchText) + } + } + var body: some View { NavigationSplitView { List(selection: $selectedPage) { Section { - ForEach(Self.pages) { item in - if searchText.isEmpty || item.name.rawValue.lowercased().contains(searchText.lowercased()) { - SettingsPageView(item) - } + ForEach(Self.pages) { pageAndSettings in + results(pageAndSettings.page, pageAndSettings.settings) } } } diff --git a/CodeEdit/Features/Settings/Views/SettingsPageView.swift b/CodeEdit/Features/Settings/Views/SettingsPageView.swift index 846b22739..a8f2fa011 100644 --- a/CodeEdit/Features/Settings/Views/SettingsPageView.swift +++ b/CodeEdit/Features/Settings/Views/SettingsPageView.swift @@ -9,49 +9,48 @@ import SwiftUI struct SettingsPageView: View { var page: SettingsPage + var searchText: String - init(_ page: SettingsPage) { + init(_ page: SettingsPage, searchText: String) { self.page = page + self.searchText = searchText } var body: some View { NavigationLink(value: page) { Label { - Text(page.nameString) + page.name.rawValue.highlightOccurrences(self.searchText) .padding(.leading, 2) } icon: { - if let icon = page.icon { - Group { - switch 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) - } + 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.gradient) - .shadow(color: Color(NSColor.black).opacity(0.25), radius: 0.5, y: 0.5) - ) - } else { - 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/Utils/Extensions/String/String+HighlightOccurrences.swift b/CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift new file mode 100644 index 000000000..67155ef85 --- /dev/null +++ b/CodeEdit/Utils/Extensions/String/String+HighlightOccurrences.swift @@ -0,0 +1,56 @@ +// +// String+HighlightOccurrences.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 13/06/23. +// + +import Foundation +import SwiftUI + +extension String { + /// Highlights occurences of a substring in a string and returns text highlighted as such + func highlightOccurrences(_ ofSearch: String) -> some View { + if ofSearch.isEmpty { + return Text(self) + } + + let ranges = self.rangesOfSubstring(ofSearch.lowercased()) + + var currentIndex = self.startIndex + var highlightedText = Text("") + + for range in ranges { + let nonHighlightedText = self[currentIndex.. [Range] { + var ranges = [Range]() + var currentIndex = self.startIndex + + while let range = self.range( + of: substring, + options: .caseInsensitive, + range: currentIndex.. - -### Preferences Model +### Settings Model - ``SettingsModel`` - ``SoftwareUpdater`` -### Preferences Section Views +### Settings Section Views -- ``GeneralPreferencesView`` -- ``ThemePreferencesView`` -- ``TextEditingPreferencesView`` -- ``TerminalPreferencesView`` -- ``LocationsPreferencesView`` -- ``KeybindingsPreferencesView`` -- ``AccountPreferencesView`` -- ``SourceControlPreferencesView`` -- ``PreferencesPlaceholderView`` +- ``GeneralSettingsView`` +- ``ThemeSettingsView`` +- ``TextEditingSettingsView`` +- ``TerminalSettingsView`` +- ``LocationsSettingsView`` +- ``KeybindingsSettingsView`` +- ``AccountSettingsView`` +- ``SourceControlSettingsView`` +- ``SettingsPlaceholderView`` ### Helper Views -- ``PreferencesContent`` -- ``PreferencesSection`` -- ``PreferencesColorPicker`` -- ``PreferencesToolbar`` +- ``SettingsContent`` +- ``SettingsSection`` +- ``SettingsColorPicker`` +- ``SettingsToolbar`` -### Theme Preferences Model +### Theme Settings Model - ``Theme`` - ``ThemeModel`` diff --git a/Documentation.docc/AppPreferences/Create a View.md b/Documentation.docc/AppPreferences/Create a View.md index a7ef61890..1d97b5b1b 100644 --- a/Documentation.docc/AppPreferences/Create a View.md +++ b/Documentation.docc/AppPreferences/Create a View.md @@ -2,30 +2,31 @@ Now that you followed the guide it's time to create a view. -## Add Option to existing Section +## Add setting to existing Section -In our example we added `ourNewOption` in ``Settings/GeneralPreferences``. +In our example we added `ourNewOption` in ``Settings/GeneralSettings``. -Now let's take a look at the ``GeneralPreferencesView``. +Now let's take a look at the ``GeneralSettingsView``. ```swift import SwiftUI -struct GeneralPreferencesView: View { - - // MARK: - View - - init() {} +struct GeneralSettingsView: View { + @AppSettings(\.general) + var settings var body: some View { - PreferencesContent { - appearanceSection - showIssuesSection - fileExtensionsSection + SettingsForm { + Section { + appearance + fileIconStyle + tabBarStyle + navigatorTabBarPosition + inspectorTabBarPosition + ... + } } } - - @AppSettings var settings } ``` @@ -34,20 +35,11 @@ As you can see ``SettingsModel`` is already setup and ready to use. To add your option toggle below the other options just add something like this: ```swift -private extension GeneralPreferencesView { - - // MARK: - Sections - - private var yourOptionSection: some View { - PreferencesSection("Your Option") { - yourOption - } - } - - // MARK: - Preferences View +private extension GeneralSettingsView { + // MARK: - Settings View private var yourOption: some View { - Toggle("Your text", isOn: $settings.general.yourNewOption) + Toggle("Your text", isOn: $general.yourNewOption) } } ``` @@ -55,19 +47,16 @@ private extension GeneralPreferencesView { Then add it to `var body: some View` ```swift -struct GeneralPreferencesView: View { - - // MARK: - View - - init() {} - +struct GeneralSettingsView: View { var body: some View { - PreferencesContent { - appearanceSection - showIssuesSection - fileExtensionsSection - // REMOVEME: et cetera - yourOptionSection + SettingsForm { + Section { + appearanceSection + showIssuesSection + fileExtensionsSection + // REMOVEME: et cetera + yourOptionSection + } } } } @@ -75,50 +64,27 @@ struct GeneralPreferencesView: View { And now you're done! -## Implement new Section +## Implement a new section > Tip: Rename YourSection to the section name that you want -To implement a new section first create a new folder inside the `Sections` folder and name it accordingly. +To implement a new section first create a new folder inside the `Pages` folder and name it accordingly. -Inside the folder create a new SwiftUI view and name it "PreferencesYourSectionView.swift". +Inside the folder create a new SwiftUI view and name it "YourSectionSettingsView.swift". -Then find `VenturaPreferences.swift` by searching in the filter field at the bottom of the file explorer, then create a new page for your view like so: +Then create a new folder inside called `Models` and inside of it create a file named "YourSectionSettings.swift" -> Tip: The order that pages are arranged in the array is the same as in the settings window, the first array member will be the top item -```swift -private static let pages: [Page] = [ - .init(.appPreferencesSection, children: [ - .init( - .generalPreferences, - icon: .init( - baseColor: Colors().gray, - systemName: "gear", - icon: .system("gear") - ) - ), - .init( - .yourSection, - icon: .init( - baseColor: Colors().yourColor - systemName: "// REMOVEME: Find an SF Symbol that is similar to the icon you imagined" - icon: .system("// REMOVEME: Find an SF Symbol that is similar to the icon you imagined") - ) - ) - ] -] +> Tip: The order that pages are arranged in the array is the same as in the settings window, the first array member will be the top item ``` -Then find the file `Page.swift` and add `YourSection` to the `enum Name` like this: +Then find the file `SettingsPage.swift` and add `YourSection` to the `enum Name` like this: ```swift enum Name: String { - case appPreferencesSection = "App Preferences" - - case generalPreferences = "General" - case advancedPreferences = "Advanced" - // REMOVEME: et cetera + case general = "General" + case advanced = "Advanced" + // et cetera case yourSection = "YourSection" } ``` @@ -128,45 +94,77 @@ Back in `YourSectionView.swift` implement your option like this: ```swift import SwiftUI -struct YourSectionPreferencesView: View { +struct YourSectionSettingsView: View { + @AppSettings(\.yourSection) + var yourSection + var body: some View { - yourToggleSection + SettingsForm { + Section { + yourToggleSection + } + } } +} - @StateObject - private var prefs: SettingsModel = .shared +private extension YourSectionSettingsView { + // MARK: - Settings Views - public init() {} + private var yourToggle: some View { + Toggle("Your option", isOn: $yourSection.yourNewOption) + } } +``` -private extension YourSectionPreferencesView { - - // MARK: - Sections +There are 3 more steps, almost done. - private var yourToggleSection: some View { - yourToggle - } +Open `ModelNameToSettingName.swift` and add your translated search result: - // MARK: - Preferences Views +```swift +let translator: [String: String] = [ + // MARK: - General Settings + "appAppearance": NSLocalizedString("Appearance", comment: ""), + "fileIconStyle": NSLocalizedString("File Icon Style", comment: ""), + // etc + // MARK: - Your Section + "yourOption": NSLocalizedString("Your Option", comment: "Your translation comment") +] +``` - private var yourToggle: some View { - Toggle("Your option", isOn: $settings.general.yourNewOption) - } +Now, open `SettingsView.swift` and add your section to the `populatePages()` method: + +```swift +/// Creates all the neccessary pages +private func populatePages() -> [SettingsPage] { + var pages = [SettingsPage]() + let settingsData = SettingsData() + + let generalSettings = SettingsPage(.general, baseColor: .gray, icon: .system("gear")) + pages = createPageAndSettings(settingsData.general, parent: generalSettings, prePages: pages) + + let accountsSettings = SettingsPage(.accounts, baseColor: .blue, icon: .system("at")) + pages = createPageAndSettings(settingsData.accounts, parent: accountsSettings, prePages: pages) + + // etc + let yourSectionSettings = SettingsPage(.yourSection, baseColor: /* add color here */, icon: /* add icon */) + pages = createPageAndSettings(settingsData.yourSection, parent: yourSectionSettings, prePages: pages) + + return pages } ``` -When you are done, add `YourSectionView` to `VenturaPreferences.swift`: + +When you are done, add `YourSectionSettingsView` to `SettingsView.swift`: ```swift -if selectedPage?.name != nil { - // Can force un-wrap because we just checked if it was nil - switch selectedPage!.name { - case .generalPreferences: - GeneralPreferencesView() - .environmentObject(updater) - case .themePreferences: - ThemePreferencesView() - // REMOVEME: et cetera +Group { + switch selectedPage { + case .general: + GeneralSettingsView().environmentObject(updater) case .yourSection: - YourSectionPreferencesView() + YourSectionSettingsView() + default: + Text("Implementation Needed").frame(alignment: .center) + } +} ``` diff --git a/Documentation.docc/AppPreferences/Getting Started.md b/Documentation.docc/AppPreferences/Getting Started.md index 5ec56e8dc..c6f4f0227 100644 --- a/Documentation.docc/AppPreferences/Getting Started.md +++ b/Documentation.docc/AppPreferences/Getting Started.md @@ -4,32 +4,29 @@ There are a few things to consider when using the ``Settings``. ## Reading/Writing Values -The Preferences can be accessed from everywhere in the app like this: +The Settings can be accessed from everywhere in the app like this: ```swift -import Settings - -@AppSettings var settings +@AppSettings(\.settingName) +var setting ``` -Since it is a `@StateObject` we can be sure to always get up-to-date information and we can easily bind to the individual properties like this: - ```swift -Toggle("Enable some Feature", value: $settings.someFeature.isEnabled) +Toggle("Enable some Feature", value: $setting) ``` ## Creating a New Preference -When implementing a new feature, we might have some options in regards to this new feature we want to show the user in the apps Preferences Window. +When implementing a new feature, we might have some options in regards to this new feature we want to show the user in the apps Settings Window. ### Find a Section -The preferences window is structured in different sections. Figure out in which section your new option should appear in. +The settings window is structured in different sections. Figure out in which section your new option should appear in. -If the section is already populated with other options (e.g. ``Settings/GeneralPreferences``), just add your new option like this: +If the section is already populated with other options (e.g. ``Settings/GeneralSettings``), just add your new option like this: ```swift -struct GeneralPreferences: Codable { +struct GeneralSettings: Codable, Hashable { // ... diff --git a/Documentation.docc/AppPreferences/Sections/AccountPreferencesView.md b/Documentation.docc/AppPreferences/Sections/AccountPreferencesView.md index 492ea7b89..adf62c7a6 100644 --- a/Documentation.docc/AppPreferences/Sections/AccountPreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/AccountPreferencesView.md @@ -1,10 +1,10 @@ -# ``CodeEdit/AccountPreferencesView`` +# ``CodeEdit/AccountSettingsView`` ## Topics ### Model -- ``Settings/AccountsPreferences`` +- ``Settings/AccountsSettings`` - ``SourceControlAccounts`` - ``SourceControlProvider`` diff --git a/Documentation.docc/AppPreferences/Sections/GeneralPreferencesView.md b/Documentation.docc/AppPreferences/Sections/GeneralPreferencesView.md index e0e6e56d9..a59d4502b 100644 --- a/Documentation.docc/AppPreferences/Sections/GeneralPreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/GeneralPreferencesView.md @@ -1,7 +1,7 @@ -# ``CodeEdit/GeneralPreferencesView`` +# ``CodeEdit/GeneralSettingsView`` ## Topics ### Model -- ``Settings/GeneralPreferences`` +- ``Settings/GeneralSettings`` diff --git a/Documentation.docc/AppPreferences/Sections/KeybindingsPreferencesView.md b/Documentation.docc/AppPreferences/Sections/KeybindingsPreferencesView.md index e50a4dac0..376c8416a 100644 --- a/Documentation.docc/AppPreferences/Sections/KeybindingsPreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/KeybindingsPreferencesView.md @@ -1,7 +1,7 @@ -# ``CodeEdit/KeybindingsPreferencesView`` +# ``CodeEdit/KeybindingsSettingsView`` ## Topics ### Model -- ``Settings/KeybindingsPreferences`` +- ``Settings/KeybindingsSettings`` diff --git a/Documentation.docc/AppPreferences/Sections/SourceControlPreferencesView.md b/Documentation.docc/AppPreferences/Sections/SourceControlPreferencesView.md index 1b4264881..2ab028ebe 100644 --- a/Documentation.docc/AppPreferences/Sections/SourceControlPreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/SourceControlPreferencesView.md @@ -1,10 +1,10 @@ -# ``CodeEdit/SourceControlPreferencesView`` +# ``CodeEdit/SourceControlSettingsView`` ## Topics ### Model -- ``Settings/SourceControlPreferences`` +- ``Settings/SourceControlSettings`` - ``IgnoredFiles`` ### Views diff --git a/Documentation.docc/AppPreferences/Sections/TerminalPreferencesView.md b/Documentation.docc/AppPreferences/Sections/TerminalPreferencesView.md index 2a10ec3d5..63e727bea 100644 --- a/Documentation.docc/AppPreferences/Sections/TerminalPreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/TerminalPreferencesView.md @@ -1,7 +1,7 @@ -# ``CodeEdit/TerminalPreferencesView`` +# ``CodeEdit/TerminalSettingsView`` ## Topics ### Model -- ``Settings/TerminalPreferences`` +- ``Settings/TerminalSettings`` diff --git a/Documentation.docc/AppPreferences/Sections/TextEditingPreferencesView.md b/Documentation.docc/AppPreferences/Sections/TextEditingPreferencesView.md index f44302e50..3af61fbc4 100644 --- a/Documentation.docc/AppPreferences/Sections/TextEditingPreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/TextEditingPreferencesView.md @@ -1,7 +1,7 @@ -# ``CodeEdit/TextEditingPreferencesView`` +# ``CodeEdit/TextEditingSettingsView`` ## Topics ### Model -- ``Settings/TextEditingPreferences`` +- ``Settings/TextEditingSettings`` diff --git a/Documentation.docc/AppPreferences/Sections/ThemePreferencesView.md b/Documentation.docc/AppPreferences/Sections/ThemePreferencesView.md index 6587e91b3..928164a8b 100644 --- a/Documentation.docc/AppPreferences/Sections/ThemePreferencesView.md +++ b/Documentation.docc/AppPreferences/Sections/ThemePreferencesView.md @@ -1,10 +1,10 @@ -# ``CodeEdit/ThemePreferencesView`` +# ``CodeEdit/ThemeSettingsView`` ## Topics ### Model -- ``Settings/ThemePreferences`` +- ``Settings/ThemeSettings`` - ``ThemeModel`` ### Views