import AppKit import Configs import Foundation public protocol UserDefaultsType { func value(forKey: String) -> Any? func set(_ value: Any?, forKey: String) } public extension UserDefaults { static var shared = UserDefaults(suiteName: userDefaultSuiteName)! /// Workspace-level auto-approval storage. /// /// Backed by the `group..autoApproval.prefs` suite so it persists /// across app restarts and is isolated from general app preferences. static var autoApproval = UserDefaults(suiteName: autoApprovalUserDefaultSuiteName)! static func setupDefaultSettings() { shared.setupDefaultValue(for: \.quitXPCServiceOnXcodeAndAppQuit) shared.setupDefaultValue(for: \.realtimeSuggestionToggle) shared.setupDefaultValue(for: \.realtimeNESToggle) shared.setupDefaultValue(for: \.realtimeSuggestionDebounce) shared.setupDefaultValue(for: \.suggestionPresentationMode) shared.setupDefaultValue(for: \.autoAttachChatToXcode) shared.setupDefaultValue(for: \.enableFixError) shared.setupDefaultValue(for: \.enableSubagent) shared.setupDefaultValue(for: \.enableAutoApproval) shared.setupDefaultValue(for: \.trustToolAnnotations) shared.setupDefaultValue(for: \.widgetColorScheme) shared.setupDefaultValue(for: \.customCommands) shared.setupDefaultValue( for: \.suggestionFeatureProvider, defaultValue: .builtIn(shared.deprecatedValue(for: \.oldSuggestionFeatureProvider)) ) shared.setupDefaultValue( for: \.promptToCodeCodeFontSize, defaultValue: shared.value(for: \.suggestionCodeFontSize) ) shared.setupDefaultValue( for: \.suggestionCodeFont, defaultValue: .init(.init(nsFont: .monospacedSystemFont( ofSize: shared.value(for: \.suggestionCodeFontSize), weight: .regular ))) ) shared.setupDefaultValue( for: \.codeFontLight, defaultValue: .init(.init(nsFont: .monospacedSystemFont( ofSize: 12, weight: .regular ))) ) shared.setupDefaultValue( for: \.codeFontDark, defaultValue: .init(.init(nsFont: .monospacedSystemFont( ofSize: 12, weight: .regular ))) ) shared.setupDefaultValue( for: \.promptToCodeCodeFont, defaultValue: .init(.init(nsFont: .monospacedSystemFont( ofSize: shared.value(for: \.promptToCodeCodeFontSize), weight: .regular ))) ) shared.setupDefaultValue( for: \.chatCodeFont, defaultValue: .init(.init(nsFont: .monospacedSystemFont( ofSize: shared.value(for: \.chatCodeFontSize), weight: .regular ))) ) shared.setupDefaultValue( for: \.fontScale, defaultValue: shared.value(for: \.fontScale) ) } } extension UserDefaults: UserDefaultsType {} public protocol UserDefaultsStorable {} extension Int: UserDefaultsStorable {} extension Double: UserDefaultsStorable {} extension Bool: UserDefaultsStorable {} extension String: UserDefaultsStorable {} extension Data: UserDefaultsStorable {} extension URL: UserDefaultsStorable {} extension Dictionary: UserDefaultsStorable {} extension Array: @retroactive RawRepresentable where Element: Codable { public init?(rawValue: String) { guard let data = rawValue.data(using: .utf8), let result = try? JSONDecoder().decode([Element].self, from: data) else { return nil } self = result } public var rawValue: String { guard let data = try? JSONEncoder().encode(self), let result = String(data: data, encoding: .utf8) else { return "[]" } return result } } public struct UserDefaultsStorageBox: RawRepresentable { public let value: Element public init(_ value: Element) { self.value = value } public init?(rawValue: String) { guard let data = rawValue.data(using: .utf8), let result = try? JSONDecoder().decode(Element.self, from: data) else { return nil } value = result } public var rawValue: String { guard let data = try? JSONEncoder().encode(value), let result = String(data: data, encoding: .utf8) else { return "" } return result } } extension UserDefaultsStorageBox: Equatable where Element: Equatable {} public extension UserDefaultsType { // MARK: Normal Types func value( for keyPath: KeyPath ) -> K.Value where K.Value: UserDefaultsStorable { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] return (value(forKey: key.key) as? K.Value) ?? key.defaultValue } func set( _ value: K.Value, for keyPath: KeyPath ) where K.Value: UserDefaultsStorable { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] set(value, forKey: key.key) } func setupDefaultValue( for keyPath: KeyPath ) where K.Value: UserDefaultsStorable { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] if value(forKey: key.key) == nil { set(key.defaultValue, forKey: key.key) } } func setupDefaultValue( for keyPath: KeyPath, defaultValue: K.Value ) where K.Value: UserDefaultsStorable { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] if value(forKey: key.key) == nil { set(defaultValue, forKey: key.key) } } // MARK: Raw Representable func value( for keyPath: KeyPath ) -> K.Value where K.Value: RawRepresentable, K.Value.RawValue == String { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? String else { return key.defaultValue } return K.Value(rawValue: rawValue) ?? key.defaultValue } func value( for keyPath: KeyPath ) -> K.Value where K.Value: RawRepresentable, K.Value.RawValue == Int { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? Int else { return key.defaultValue } return K.Value(rawValue: rawValue) ?? key.defaultValue } func value( for keyPath: KeyPath ) -> V where K.Value == UserDefaultsStorageBox { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? String else { return key.defaultValue.value } return (K.Value(rawValue: rawValue) ?? key.defaultValue).value } func set( _ value: K.Value, for keyPath: KeyPath ) where K.Value: RawRepresentable, K.Value.RawValue == String { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] set(value.rawValue, forKey: key.key) } func set( _ value: K.Value, for keyPath: KeyPath ) where K.Value: RawRepresentable, K.Value.RawValue == Int { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] set(value.rawValue, forKey: key.key) } func set( _ value: V, for keyPath: KeyPath ) where K.Value == UserDefaultsStorageBox { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] set(UserDefaultsStorageBox(value).rawValue, forKey: key.key) } func setupDefaultValue( for keyPath: KeyPath, defaultValue: K.Value? = nil ) where K.Value: RawRepresentable, K.Value.RawValue == String { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] if value(forKey: key.key) == nil { set(defaultValue?.rawValue ?? key.defaultValue.rawValue, forKey: key.key) } } func setupDefaultValue( for keyPath: KeyPath, defaultValue: K.Value? = nil ) where K.Value: RawRepresentable, K.Value.RawValue == Int { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] if value(forKey: key.key) == nil { set(defaultValue?.rawValue ?? key.defaultValue.rawValue, forKey: key.key) } } } // MARK: - Deprecated Key Accessor public extension UserDefaultsType { // MARK: Normal Types func deprecatedValue( for keyPath: KeyPath> ) -> K where K: UserDefaultsStorable { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] return (value(forKey: key.key) as? K) ?? key.defaultValue } // MARK: Raw Representable func deprecatedValue( for keyPath: KeyPath> ) -> K where K: RawRepresentable, K.RawValue == String { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? String else { return key.defaultValue } return K(rawValue: rawValue) ?? key.defaultValue } func deprecatedValue( for keyPath: KeyPath> ) -> K where K: RawRepresentable, K.RawValue == Int { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? Int else { return key.defaultValue } return K(rawValue: rawValue) ?? key.defaultValue } } public extension UserDefaultsType { @available(*, deprecated, message: "This preference key is deprecated.") func value( for keyPath: KeyPath> ) -> K where K: UserDefaultsStorable { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] return (value(forKey: key.key) as? K) ?? key.defaultValue } @available(*, deprecated, message: "This preference key is deprecated.") func value( for keyPath: KeyPath> ) -> K where K: RawRepresentable, K.RawValue == String { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? String else { return key.defaultValue } return K(rawValue: rawValue) ?? key.defaultValue } @available(*, deprecated, message: "This preference key is deprecated.") func value( for keyPath: KeyPath> ) -> K where K: RawRepresentable, K.RawValue == Int { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? Int else { return key.defaultValue } return K(rawValue: rawValue) ?? key.defaultValue } } public extension UserDefaultsType { // MARK: Dictionary Raw Representable func value( for keyPath: KeyPath ) -> K.Value where K.Value: RawRepresentable, K.Value.RawValue == [String: Any] { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] guard let rawValue = value(forKey: key.key) as? [String: Any] else { return key.defaultValue } return K.Value(rawValue: rawValue) ?? key.defaultValue } func set( _ value: K.Value, for keyPath: KeyPath ) where K.Value: RawRepresentable, K.Value.RawValue == [String: Any] { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] set(value.rawValue, forKey: key.key) } func setupDefaultValue( for keyPath: KeyPath, defaultValue: K.Value? = nil ) where K.Value: RawRepresentable, K.Value.RawValue == [String: Any] { let key = UserDefaultPreferenceKeys()[keyPath: keyPath] if value(forKey: key.key) == nil { set(defaultValue?.rawValue ?? key.defaultValue.rawValue, forKey: key.key) } } }