diff --git a/Edit.xcodeproj/project.pbxproj b/Edit.xcodeproj/project.pbxproj index 9e5ee45..cc41cac 100644 --- a/Edit.xcodeproj/project.pbxproj +++ b/Edit.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ C97640F42C403777002C94BF /* RustExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97640F32C403777002C94BF /* RustExtension.swift */; }; C97640F52C40377D002C94BF /* RustExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97640F32C403777002C94BF /* RustExtension.swift */; }; C97640F82C403791002C94BF /* ChimeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9FE52F42A7539B100CACA1A /* ChimeKit.framework */; }; + C976D7E62C4A86F900CC6EA6 /* ThemePark in Frameworks */ = {isa = PBXBuildFile; productRef = C976D7E52C4A86F900CC6EA6 /* ThemePark */; }; C979187B2A9CC1110046EAF1 /* SearchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97918752A9CBEF20046EAF1 /* SearchItem.swift */; }; C979187C2A9CC1190046EAF1 /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97918792A9CBF8C0046EAF1 /* RoundedCorners.swift */; }; C979187D2A9CC1200046EAF1 /* ItemBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97918772A9CBF330046EAF1 /* ItemBackground.swift */; }; @@ -197,7 +198,6 @@ C9E0BA312BD2C1FA007AF034 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E0BA2F2BD2C1EF007AF034 /* SettingsView.swift */; }; C9E0BA352BD2C7F2007AF034 /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E0BA342BD2C7F2007AF034 /* ThemeView.swift */; }; C9E0BA372BD2C96D007AF034 /* ThemeTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E0BA362BD2C96D007AF034 /* ThemeTile.swift */; }; - C9E0BA3E2BD2EB54007AF034 /* ThemePark in Frameworks */ = {isa = PBXBuildFile; productRef = C9E0BA3D2BD2EB54007AF034 /* ThemePark */; }; C9E0BA422BD52732007AF034 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E0BA412BD52732007AF034 /* Theme.swift */; }; C9E877FC2A9F4B820018340C /* TrailingSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E877FB2A9F4B820018340C /* TrailingSidebar.swift */; }; C9E878042A9F53530018340C /* UIPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E878032A9F53530018340C /* UIPlaceholder.swift */; }; @@ -1255,6 +1255,7 @@ files = ( C9439A732C3EB5260020DDF5 /* libSyntaxService.a in Frameworks */, C9439A722C3EB5140020DDF5 /* libHighlighting.a in Frameworks */, + C976D7E62C4A86F900CC6EA6 /* ThemePark in Frameworks */, C96B62002BDA71E400561DE8 /* libStatus.a in Frameworks */, C96B61FF2BDA71B200561DE8 /* libUIUtility.a in Frameworks */, C96B61FE2BDA719100561DE8 /* libGutter.a in Frameworks */, @@ -1273,7 +1274,6 @@ C9BFA1B72BC162EF00E86487 /* NSUI in Frameworks */, C9BFA1B02BC0927A00E86487 /* Glyph in Frameworks */, C95B91362BD98CBD006FDD00 /* RelativeCollections in Frameworks */, - C9E0BA3E2BD2EB54007AF034 /* ThemePark in Frameworks */, C929316A2B822BA700C64DDE /* libTheme.a in Frameworks */, C95B913E2BD98D01006FDD00 /* SwiftTreeSitterLayer in Frameworks */, C92931692B822B7B00C64DDE /* ColorToolbox in Frameworks */, @@ -2379,6 +2379,7 @@ buildRules = ( ); dependencies = ( + C976D7E82C4A871400CC6EA6 /* PBXTargetDependency */, C96B62022BDA71FE00561DE8 /* PBXTargetDependency */, C96B61FB2BDA714000561DE8 /* PBXTargetDependency */, C96B61F52BDA70E000561DE8 /* PBXTargetDependency */, @@ -2396,7 +2397,6 @@ C92931682B822B7B00C64DDE /* ColorToolbox */, C9BFA1AF2BC0927A00E86487 /* Glyph */, C9BFA1B62BC162EF00E86487 /* NSUI */, - C9E0BA3D2BD2EB54007AF034 /* ThemePark */, C95B91282BD98C3B006FDD00 /* Rearrange */, C95B912C2BD98C50006FDD00 /* Neon */, C95B91332BD98CA1006FDD00 /* SwiftTreeSitter */, @@ -2407,6 +2407,7 @@ C96B61FC2BDA714700561DE8 /* ScrollViewPlus */, C96B62052BDA721100561DE8 /* SourceView */, C9439A702C3EB4410020DDF5 /* TreeSitterParsers */, + C976D7E52C4A86F900CC6EA6 /* ThemePark */, ); productName = EditKit; productReference = C92931422B80CC6700C64DDE /* EditKit.framework */; @@ -3247,8 +3248,8 @@ C9C568562B7418B60093C068 /* XCRemoteSwiftPackageReference "Lowlight" */, C9BFA1A12BBF4FA200E86487 /* XCRemoteSwiftPackageReference "nsui" */, C9BFA1AE2BC0927A00E86487 /* XCRemoteSwiftPackageReference "Glyph" */, - C9E0BA3A2BD2EB05007AF034 /* XCRemoteSwiftPackageReference "ThemePark" */, C93F72252BF0F9290021ACF3 /* XCRemoteSwiftPackageReference "Sparkle" */, + C976D7E42C4A86F800CC6EA6 /* XCRemoteSwiftPackageReference "ThemePark" */, ); productRefGroup = C9FE529A2A7525D000CACA1A /* Products */; projectDirPath = ""; @@ -4049,6 +4050,10 @@ target = C9FE52F32A7539B100CACA1A /* ChimeKit */; targetProxy = C97640F62C40378C002C94BF /* PBXContainerItemProxy */; }; + C976D7E82C4A871400CC6EA6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = C976D7E72C4A871400CC6EA6 /* ThemePark */; + }; C97918722A9CBD6B0046EAF1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C9FE539A2A76709D00CACA1A /* Status */; @@ -5353,6 +5358,14 @@ revision = d563ef0ba6c1c91aa6bcee406577b8826baa1f69; }; }; + C976D7E42C4A86F800CC6EA6 /* XCRemoteSwiftPackageReference "ThemePark" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ChimeHQ/ThemePark"; + requirement = { + kind = revision; + revision = 2ccbf37a0c7e52c7b20aadabd8e8cbbb2b25b9a8; + }; + }; C97918C32A9D4A510046EAF1 /* XCRemoteSwiftPackageReference "FuzzyFind" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/truizlop/FuzzyFind"; @@ -5630,6 +5643,16 @@ package = C926738F2B078D3200B3CE2F /* XCRemoteSwiftPackageReference "Neon" */; productName = Neon; }; + C976D7E52C4A86F900CC6EA6 /* ThemePark */ = { + isa = XCSwiftPackageProductDependency; + package = C976D7E42C4A86F800CC6EA6 /* XCRemoteSwiftPackageReference "ThemePark" */; + productName = ThemePark; + }; + C976D7E72C4A871400CC6EA6 /* ThemePark */ = { + isa = XCSwiftPackageProductDependency; + package = C976D7E42C4A86F800CC6EA6 /* XCRemoteSwiftPackageReference "ThemePark" */; + productName = ThemePark; + }; C97918C62A9D4A6A0046EAF1 /* FuzzyFind */ = { isa = XCSwiftPackageProductDependency; package = C97918C32A9D4A510046EAF1 /* XCRemoteSwiftPackageReference "FuzzyFind" */; @@ -5744,11 +5767,6 @@ package = C9FE53EB2A7A5D9700CACA1A /* XCRemoteSwiftPackageReference "Extendable" */; productName = ExtendableHost; }; - C9E0BA3D2BD2EB54007AF034 /* ThemePark */ = { - isa = XCSwiftPackageProductDependency; - package = C9E0BA3A2BD2EB05007AF034 /* XCRemoteSwiftPackageReference "ThemePark" */; - productName = ThemePark; - }; C9F79DDD2A87E656005ED8E9 /* Outline */ = { isa = XCSwiftPackageProductDependency; productName = Outline; diff --git a/Edit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Edit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 692c41c..b39476b 100644 --- a/Edit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Edit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "7ec6ccb9bc276a295422e18b1a495ba45c60ca70b5912320b7f399276466ef72", + "originHash" : "149adbfbae957f76ac903a7309649365bc7a1ca5a075d5070b19f145bb016507", "pins" : [ { "identity" : "asyncxpcconnection", @@ -237,7 +237,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ChimeHQ/ThemePark", "state" : { - "revision" : "fff8ae0521d76e81acd5c72ad39144ad10f1d489" + "revision" : "2ccbf37a0c7e52c7b20aadabd8e8cbbb2b25b9a8" } }, { diff --git a/Edit/Modules/PreferencesWindow/ThemeView.swift b/Edit/Modules/PreferencesWindow/ThemeView.swift index 02e5589..c3c3e56 100644 --- a/Edit/Modules/PreferencesWindow/ThemeView.swift +++ b/Edit/Modules/PreferencesWindow/ThemeView.swift @@ -30,7 +30,7 @@ final class ThemeModel { @MainActor struct ThemeView: View { @State private var model: ThemeModel - @AppStorage("theme-identifier") private var themeId: String = "" + @AppStorage("CurrentTheme", store: UserDefaults.sharedSuite) private var themeId: String = "" private let adaptiveColumn = [ GridItem(.adaptive(minimum: 150)) diff --git a/Edit/Modules/ProjectWindow/ProjectWindowRoot.swift b/Edit/Modules/ProjectWindow/ProjectWindowRoot.swift index 451f434..13d5727 100644 --- a/Edit/Modules/ProjectWindow/ProjectWindowRoot.swift +++ b/Edit/Modules/ProjectWindow/ProjectWindowRoot.swift @@ -11,7 +11,7 @@ import Utility struct ProjectWindowRoot<Content: View>: View { @Environment(WindowStateModel.self) private var model @Environment(\.windowState) private var windowState - @AppStorage("theme-identifier") private var themeId: String = "" + @AppStorage("CurrentTheme", store: UserDefaults.sharedSuite) private var themeId: String = "" let content: Content diff --git a/Edit/Modules/ProjectWindow/WindowStateModel.swift b/Edit/Modules/ProjectWindow/WindowStateModel.swift index 563b1d0..ddde134 100644 --- a/Edit/Modules/ProjectWindow/WindowStateModel.swift +++ b/Edit/Modules/ProjectWindow/WindowStateModel.swift @@ -50,11 +50,7 @@ public final class WindowStateModel { self.documentContext = context self.themeStore = themeStore - let theme = UserDefaults.standard - .string(forKey: "theme-identifier") - .map { themeStore.theme(with: $0) } - - self.currentTheme = theme ?? Theme.fallback + self.currentTheme = ThemeStore.currentTheme ?? Theme.fallback } func windowStateChanged(_ old: WindowStateObserver.State, _ new: WindowStateObserver.State) { diff --git a/Edit/Modules/Theme/ThemeStore.swift b/Edit/Modules/Theme/ThemeStore.swift index 05cf494..b6449f4 100644 --- a/Edit/Modules/Theme/ThemeStore.swift +++ b/Edit/Modules/Theme/ThemeStore.swift @@ -5,6 +5,7 @@ import ThemePark @MainActor public final class ThemeStore { private var themeCache = [Theme.Identity: Theme]() + private var themeURLs = [Theme.Identity: URL]() public init() { } @@ -39,20 +40,41 @@ public final class ThemeStore { private func loadXcodeThemes() { #if os(macOS) - for (name, theme) in XcodeTheme.all { + for url in XcodeTheme.all { + guard let theme = try? XcodeTheme(contentsOf: url) else { continue } + + let name = url.deletingPathExtension().lastPathComponent let identity = Theme.Identity(source: .xcode, name: name) cacheStyler(theme, with: identity) + themeURLs[identity] = url } #endif } private func loadTextMateThemes() { #if os(macOS) - for theme in TextMateTheme.all { + for url in TextMateTheme.all { + guard let theme = try? TextMateTheme(contentsOf: url) else { continue } + let identity = Theme.Identity(source: .textmate, name: theme.name) cacheStyler(theme, with: identity) + themeURLs[identity] = url + } +#endif + } + + private func loadBBEditThemes() { +#if os(macOS) + for url in BBEditTheme.all { + guard let theme = try? BBEditTheme(contentsOf: url) else { continue } + + let name = url.deletingPathExtension().lastPathComponent + let identity = Theme.Identity(source: .bbedit, name: name) + + cacheStyler(theme, with: identity) + themeURLs[identity] = url } #endif } @@ -66,30 +88,7 @@ public final class ThemeStore { private func loadThemes() { loadXcodeThemes() loadTextMateThemes() - - guard let containerURL = FileManager.default.appGroupContainerURL else { return } - - let themeCacheDir = containerURL.appending(path: "Library/Caches/Themes", directoryHint: .isDirectory) - - try? FileManager.default.createDirectory(at: themeCacheDir, withIntermediateDirectories: true) - - for (identity, theme) in themeCache { - let codableTheme = CodableTheme(styler: CodableStyler(theme), identity: identity) - let url = themeCacheDir.appending(path: identity.storageString, directoryHint: .notDirectory) - - do { - let data = try JSONEncoder().encode(codableTheme) - - try data.write(to: url) - } catch { - print("failed to write out current theme: ", error) - } - } - - let names = themeCache.keys.map { $0.storageString } - UserDefaults.sharedSuite?.setValue(names, forKey: "ThemeIdentities") - - print(Self.availableIdentities) + loadBBEditThemes() } public var all: [Theme.Identity: Theme] { @@ -104,25 +103,51 @@ extension ThemeStore { FileManager.default.appGroupContainerURL?.appending(path: "CurrentTheme.json") } - public static var availableIdentities: Set<Theme.Identity> { - guard let names = UserDefaults.sharedSuite?.array(forKey: "ThemeIdentities") as? [String] else { - return [] - } - - return Set(names.compactMap { Theme.Identity(storageString: $0) }) + public static var copiedThemesURL: URL? { + FileManager.default.appGroupContainerURL?.appending(path: "Themes") } +// public static var availableIdentities: Set<Theme.Identity> { +// guard let names = UserDefaults.sharedSuite?.array(forKey: "ThemeIdentities") as? [String] else { +// return [] +// } +// +// return Set(names.compactMap { Theme.Identity(storageString: $0) }) +// } + public static var currentTheme: Theme? { - guard let url = ThemeStore.currentThemeURL else { + guard let dirURL = Self.copiedThemesURL else { return nil } - do { - let data = try Data(contentsOf: url) + guard let storageString = UserDefaults.sharedSuite?.string(forKey: "CurrentTheme") else { + return nil + } + + guard let identity = Theme.Identity(storageString: storageString) else { + return nil + } - let codableTheme = try JSONDecoder().decode(CodableTheme.self, from: data) + let themeURL = dirURL.appending(path: identity.storageString, directoryHint: .notDirectory) - return Theme(identity: codableTheme.identity, styler: codableTheme.styler) + do { + switch identity.source { + case .xcode: + let theme = try XcodeTheme(contentsOf: themeURL) + + return Theme(identity: identity, styler: theme) + case .textmate: + let theme = try TextMateTheme(contentsOf: themeURL) + + return Theme(identity: identity, styler: theme) + case .bbedit: + let theme = try BBEditTheme(contentsOf: themeURL) + + return Theme(identity: identity, styler: theme) + case .chime: + assertionFailure("What are we supposed to do here exactly?") + return Theme.fallback + } } catch { print("failed to load current theme: ", error) @@ -131,18 +156,24 @@ extension ThemeStore { } public func updateCurrentTheme(with identity: Theme.Identity) { - guard let url = ThemeStore.currentThemeURL else { + UserDefaults.sharedSuite?.setValue(identity.storageString, forKey: "CurrentTheme") + + guard let url = Self.copiedThemesURL else { return } - let theme = theme(with: identity) - - let codableTheme = CodableTheme(styler: CodableStyler(theme), identity: identity) + guard let source = themeURLs[identity] else { + return + } do { - let data = try JSONEncoder().encode(codableTheme) - try data.write(to: url) + let destination = url.appending(path: identity.storageString, directoryHint: .notDirectory) + + try? FileManager.default.removeItem(at: destination) + try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) + try FileManager.default.copyItem(at: source, to: destination) + } catch { print("failed to write out current theme: ", error) }