Use UTType in file setting managers

This commit is contained in:
1024jp 2022-03-15 16:54:22 +09:00
parent 91d9756d1e
commit 7f9195308e
12 changed files with 64 additions and 69 deletions

View File

@ -16,6 +16,7 @@ Change Log
- Detect the line ending in documents more intelligently.
- Optimize syntax parsing.
- Deprecate the feature to replace `$LN` in the outline menu template with the line number of the occurrence.
- Prefer using .yml for syntax definition files over .yaml.
- [trivial] Replace `\n` with `\R` for the new line meta character in the regular expression reference.
- [trivial] Tweak Anura theme.

View File

@ -392,8 +392,8 @@
2A9082F31D32A9B500228F50 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9082F11D32A9B500228F50 /* ThemeManager.swift */; };
2A91C3181D1BE91E007CF8BE /* DefaultSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C3171D1BE91E007CF8BE /* DefaultSettings.swift */; };
2A91C3191D1BE91E007CF8BE /* DefaultSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C3171D1BE91E007CF8BE /* DefaultSettings.swift */; };
2A91C31B1D1BFE47007CF8BE /* DocumentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C31A1D1BFE47007CF8BE /* DocumentType.swift */; };
2A91C31C1D1BFE47007CF8BE /* DocumentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C31A1D1BFE47007CF8BE /* DocumentType.swift */; };
2A91C31B1D1BFE47007CF8BE /* UTType+SettingFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C31A1D1BFE47007CF8BE /* UTType+SettingFile.swift */; };
2A91C31C1D1BFE47007CF8BE /* UTType+SettingFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C31A1D1BFE47007CF8BE /* UTType+SettingFile.swift */; };
2A91C31E1D1C3963007CF8BE /* PrintPaneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C31D1D1C3963007CF8BE /* PrintPaneController.swift */; };
2A91C31F1D1C3963007CF8BE /* PrintPaneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C31D1D1C3963007CF8BE /* PrintPaneController.swift */; };
2A91C3211D1C40E4007CF8BE /* FileDropPaneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A91C3201D1C40E4007CF8BE /* FileDropPaneController.swift */; };
@ -1168,7 +1168,7 @@
2A9082EE1D325ED900228F50 /* GeometryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeometryTests.swift; sourceTree = "<group>"; };
2A9082F11D32A9B500228F50 /* ThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
2A91C3171D1BE91E007CF8BE /* DefaultSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultSettings.swift; sourceTree = "<group>"; };
2A91C31A1D1BFE47007CF8BE /* DocumentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentType.swift; sourceTree = "<group>"; };
2A91C31A1D1BFE47007CF8BE /* UTType+SettingFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UTType+SettingFile.swift"; sourceTree = "<group>"; };
2A91C31D1D1C3963007CF8BE /* PrintPaneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrintPaneController.swift; sourceTree = "<group>"; };
2A91C3201D1C40E4007CF8BE /* FileDropPaneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileDropPaneController.swift; sourceTree = "<group>"; };
2A94FC781BE2256F00B454A8 /* cot */ = {isa = PBXFileReference; explicitFileType = text.script.python; name = cot; path = cot/cot; sourceTree = SOURCE_ROOT; };
@ -1655,6 +1655,7 @@
2AACB1DA1D19B1440073775B /* Key Binding Managers */,
2A8DA9431D286C53003D0C4B /* ScriptManager.swift */,
2AC13A0824F112D800799A93 /* CommandLineToolManager.swift */,
2A91C31A1D1BFE47007CF8BE /* UTType+SettingFile.swift */,
);
name = Managers;
sourceTree = "<group>";
@ -1724,7 +1725,6 @@
isa = PBXGroup;
children = (
2AC2462D1D1BC70C00E46CFA /* AppDelegate.swift */,
2A91C31A1D1BFE47007CF8BE /* DocumentType.swift */,
2A8961911DB76A3400E9E0EC /* MainMenu.swift */,
2A78BFBB1D1B376000A583D2 /* ServicesProvider.swift */,
2A78BFB21D1B240900A583D2 /* UpdaterManager.swift */,
@ -2891,7 +2891,6 @@
2AD616CD1D3E583D0016EFB6 /* DocumentController.swift in Sources */,
2A50AA63204D513500D10A10 /* DocumentFile.swift in Sources */,
2AAB4BFA1D2435AC0049A68B /* DocumentInspectorViewController.swift in Sources */,
2A91C31C1D1BFE47007CF8BE /* DocumentType.swift in Sources */,
2AED70EF1D2E36EF006FFBCE /* DocumentViewController.swift in Sources */,
2A71BC7C1DDC50530085AE1C /* DocumentViewController+TouchBar.swift in Sources */,
2A17A3171D2D4319001DD717 /* DocumentWindow.swift in Sources */,
@ -3111,6 +3110,7 @@
2A1A4EAC24FB7BDE00B50AA0 /* UserDefaults+DefaultKey.swift in Sources */,
2A222C3024FA8E0500251084 /* UserDefaults.Publisher.swift in Sources */,
2AFD218A27E0434100E83E88 /* UTType.swift in Sources */,
2A91C31C1D1BFE47007CF8BE /* UTType+SettingFile.swift in Sources */,
2A78BFB11D1B168E00A583D2 /* WebDocumentViewController.swift in Sources */,
2A17A3141D2D16F1001DD717 /* WindowContentViewController.swift in Sources */,
2A78BFA51D1B02ED00A583D2 /* WindowPaneController.swift in Sources */,
@ -3207,7 +3207,6 @@
2AAB4BF91D2435AC0049A68B /* DocumentInspectorViewController.swift in Sources */,
2AED70EE1D2E36EF006FFBCE /* DocumentViewController.swift in Sources */,
2A71BC7B1DDC50530085AE1C /* DocumentViewController+TouchBar.swift in Sources */,
2A91C31B1D1BFE47007CF8BE /* DocumentType.swift in Sources */,
2A17A3161D2D4319001DD717 /* DocumentWindow.swift in Sources */,
2AA749C31D3C263300850802 /* DocumentWindowController.swift in Sources */,
2ACDC0911D1726BD009B72D6 /* DotView.swift in Sources */,
@ -3426,6 +3425,7 @@
2A1A4EAD24FB7BDE00B50AA0 /* UserDefaults+DefaultKey.swift in Sources */,
2A222C3124FA8E0500251084 /* UserDefaults.Publisher.swift in Sources */,
2AFD218B27E0434100E83E88 /* UTType.swift in Sources */,
2A91C31B1D1BFE47007CF8BE /* UTType+SettingFile.swift in Sources */,
2A78BFB01D1B168E00A583D2 /* WebDocumentViewController.swift in Sources */,
2A17A3131D2D16F1001DD717 /* WindowContentViewController.swift in Sources */,
2A78BFA41D1B02ED00A583D2 /* WindowPaneController.swift in Sources */,

View File

@ -26,6 +26,7 @@
import Combine
import Cocoa
import UniformTypeIdentifiers
private extension NSSound {
@ -228,9 +229,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let documentURLs = filenames.map { URL(fileURLWithPath: $0) }
.filter {
// ask installation if the file is CotEditor theme file
DocumentType.theme.extensions.contains($0.pathExtension)
? !self.askThemeInstallation(fileURL: $0)
: true
$0.conforms(to: .cotTheme) ? !self.askThemeInstallation(fileURL: $0) : true
}
guard !documentURLs.isEmpty else { return NSApp.reply(toOpenOrPrint: .success) }
@ -406,7 +405,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
/// - Returns: Whether the given file was handled as a theme file.
private func askThemeInstallation(fileURL url: URL) -> Bool {
assert(DocumentType.theme.extensions.contains(url.pathExtension))
assert(url.conforms(to: .cotTheme))
// ask whether theme file should be opened as a text file
let alert = NSAlert()

View File

@ -27,6 +27,7 @@
import Combine
import Cocoa
import AudioToolbox
import UniformTypeIdentifiers
final class AppearancePaneController: NSViewController, NSMenuItemValidation, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate, NSMenuDelegate, ThemeViewControllerDelegate {
@ -239,7 +240,7 @@ final class AppearancePaneController: NSViewController, NSMenuItemValidation, NS
let pboard = info.draggingPasteboard
let objects = pboard.readObjects(forClasses: [NSURL.self],
options: [.urlReadingFileURLsOnly: true,
.urlReadingContentsConformToTypes: [DocumentType.theme.utType]])
.urlReadingContentsConformToTypes: [UTType.cotTheme.identifier]])
guard let urls = objects, !urls.isEmpty else { return [] }
@ -258,7 +259,7 @@ final class AppearancePaneController: NSViewController, NSMenuItemValidation, NS
info.enumerateDraggingItems(for: tableView, classes: [NSURL.self],
searchOptions: [.urlReadingFileURLsOnly: true,
.urlReadingContentsConformToTypes: [DocumentType.theme.utType]])
.urlReadingContentsConformToTypes: [UTType.cotTheme.identifier]])
{ [unowned self] (draggingItem, _, _) in
guard let fileURL = draggingItem.item as? URL else { return }
@ -486,7 +487,7 @@ final class AppearancePaneController: NSViewController, NSMenuItemValidation, NS
savePanel.isExtensionHidden = true
savePanel.nameFieldLabel = "Export As:".localized
savePanel.nameFieldStringValue = settingName
savePanel.allowedFileTypes = [ThemeManager.shared.filePathExtension]
savePanel.allowedContentTypes = [ThemeManager.shared.fileType]
Task {
guard await savePanel.beginSheetModal(for: self.view.window!) == .OK else { return }
@ -508,7 +509,7 @@ final class AppearancePaneController: NSViewController, NSMenuItemValidation, NS
openPanel.resolvesAliases = true
openPanel.allowsMultipleSelection = true
openPanel.canChooseDirectories = false
openPanel.allowedFileTypes = [ThemeManager.shared.filePathExtension]
openPanel.allowedContentTypes = [ThemeManager.shared.fileType]
Task {
guard await openPanel.beginSheetModal(for: self.view.window!) == .OK else { return }

View File

@ -219,10 +219,9 @@ final class FormatPaneController: NSViewController, NSMenuItemValidation, NSTabl
// get file URLs from pasteboard
let pboard = info.draggingPasteboard
let urls = pboard.readObjects(forClasses: [NSURL.self],
options: [.urlReadingFileURLsOnly: true])?
let urls = pboard.readObjects(forClasses: [NSURL.self], options: [.urlReadingFileURLsOnly: true])?
.compactMap { $0 as? URL }
.filter { SyntaxManager.shared.filePathExtensions.contains($0.pathExtension) } ?? []
.filter { $0.conforms(to: SyntaxManager.shared.fileType) } ?? []
guard !urls.isEmpty else { return [] }
@ -245,7 +244,7 @@ final class FormatPaneController: NSViewController, NSMenuItemValidation, NSTabl
guard
let fileURL = draggingItem.item as? URL,
SyntaxManager.shared.filePathExtensions.contains(fileURL.pathExtension)
fileURL.conforms(to: SyntaxManager.shared.fileType)
else { return }
self.importSyntaxStyle(fileURL: fileURL)
@ -349,7 +348,7 @@ final class FormatPaneController: NSViewController, NSMenuItemValidation, NSTabl
savePanel.isExtensionHidden = true
savePanel.nameFieldLabel = "Export As:".localized
savePanel.nameFieldStringValue = settingName
savePanel.allowedFileTypes = [SyntaxManager.shared.filePathExtension]
savePanel.allowedContentTypes = [SyntaxManager.shared.fileType]
Task {
guard await savePanel.beginSheetModal(for: self.view.window!) == .OK else { return }
@ -371,7 +370,7 @@ final class FormatPaneController: NSViewController, NSMenuItemValidation, NSTabl
openPanel.resolvesAliases = true
openPanel.allowsMultipleSelection = true
openPanel.canChooseDirectories = false
openPanel.allowedFileTypes = SyntaxManager.shared.filePathExtensions
openPanel.allowedContentTypes = [SyntaxManager.shared.fileType]
Task {
guard await openPanel.beginSheetModal(for: self.view.window!) == .OK else { return }

View File

@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2017-2021 1024jp
// © 2017-2022 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@
import Combine
import Cocoa
import AudioToolbox
import UniformTypeIdentifiers
final class MultipleReplacementListViewController: NSViewController, NSMenuItemValidation, MultipleReplacementPanelViewControlling {
@ -201,7 +202,7 @@ final class MultipleReplacementListViewController: NSViewController, NSMenuItemV
savePanel.isExtensionHidden = true
savePanel.nameFieldLabel = "Export As:".localized
savePanel.nameFieldStringValue = settingName
savePanel.allowedFileTypes = ReplacementManager.shared.filePathExtensions
savePanel.allowedContentTypes = [ReplacementManager.shared.fileType]
Task {
guard await savePanel.beginSheetModal(for: self.view.window!) == .OK else { return }
@ -223,7 +224,7 @@ final class MultipleReplacementListViewController: NSViewController, NSMenuItemV
openPanel.resolvesAliases = true
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.allowedFileTypes = [ReplacementManager.shared.filePathExtension]
openPanel.allowedContentTypes = [ReplacementManager.shared.fileType]
Task {
guard await openPanel.beginSheetModal(for: self.view.window!) == .OK else { return }
@ -409,7 +410,7 @@ extension MultipleReplacementListViewController: NSTableViewDataSource {
let pboard = info.draggingPasteboard
let objects = pboard.readObjects(forClasses: [NSURL.self],
options: [.urlReadingFileURLsOnly: true,
.urlReadingContentsConformToTypes: [DocumentType.replacement.utType]])
.urlReadingContentsConformToTypes: [UTType.cotReplacement.identifier]])
guard let urls = objects, !urls.isEmpty else { return [] }
@ -428,7 +429,7 @@ extension MultipleReplacementListViewController: NSTableViewDataSource {
info.enumerateDraggingItems(for: tableView, classes: [NSURL.self],
searchOptions: [.urlReadingFileURLsOnly: true,
.urlReadingContentsConformToTypes: [DocumentType.replacement.utType]])
.urlReadingContentsConformToTypes: [UTType.cotReplacement.identifier]])
{ [weak self] (draggingItem, _, _) in
guard let fileURL = draggingItem.item as? URL else { return }

View File

@ -25,6 +25,7 @@
import Combine
import Foundation
import UniformTypeIdentifiers
final class ReplacementManager: SettingFileManaging {
@ -41,8 +42,7 @@ final class ReplacementManager: SettingFileManaging {
let didUpdateSetting: PassthroughSubject<SettingChange, Never> = .init()
static let directoryName: String = "Replacements"
let filePathExtensions: [String] = DocumentType.replacement.extensions
let settingFileType: SettingFileType = .replacement
let fileType: UTType = .cotReplacement
@Published var settingNames: [String] = []
let bundledSettingNames: [String] = []

View File

@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2016-2020 1024jp
// © 2016-2022 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@
import Combine
import Foundation
import AppKit.NSApplication
import UniformTypeIdentifiers
enum SettingFileType {
@ -81,11 +82,8 @@ protocol SettingFileManaging: SettingManaging {
/// directory name in both Application Support and bundled Resources
static var directoryName: String { get }
/// path extensions for user setting file
var filePathExtensions: [String] { get }
/// setting file type
var settingFileType: SettingFileType { get }
/// UTType of user setting file
var fileType: UTType { get }
/// list of names of setting file name (without extension)
var settingNames: [String] { get set }
@ -133,20 +131,13 @@ extension SettingFileManaging {
// MARK: Public Methods
/// default path extension for user setting file
var filePathExtension: String {
return self.filePathExtensions.first!
}
/// file urls for user settings
var userSettingFileURLs: [URL] {
return (try? FileManager.default.contentsOfDirectory(at: self.userSettingDirectoryURL,
includingPropertiesForKeys: nil,
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]))?
.filter { self.filePathExtensions.contains($0.pathExtension) } ?? []
.filter { $0.conforms(to: self.fileType) } ?? []
}
@ -167,7 +158,7 @@ extension SettingFileManaging {
/// return a setting file URL in the application's Resources domain or nil if not exists
func urlForBundledSetting(name: String) -> URL? {
return Bundle.main.url(forResource: name, withExtension: self.filePathExtension, subdirectory: Self.directoryName)
return Bundle.main.url(forResource: name, withExtension: self.fileType.preferredFilenameExtension, subdirectory: Self.directoryName)
}
@ -183,7 +174,7 @@ extension SettingFileManaging {
/// return a setting file URL in the user's Application Support domain (don't care if it exists)
func preparedURLForUserSetting(name: String) -> URL {
return self.userSettingDirectoryURL.appendingPathComponent(name).appendingPathExtension(self.filePathExtension)
return self.userSettingDirectoryURL.appendingPathComponent(name, conformingTo: self.fileType)
}
@ -364,7 +355,7 @@ extension SettingFileManaging {
guard name.caseInsensitiveCompare(importName) == .orderedSame else { continue }
guard self.urlForUserSetting(name: name) == nil else { // duplicated
throw ImportDuplicationError(name: name, type: self.settingFileType, replacingClosure: { [unowned self] in
throw ImportDuplicationError(name: name, type: self.fileType, replacingClosure: { [unowned self] in
try self.overwriteSetting(fileURL: fileURL)
})
}
@ -523,19 +514,21 @@ struct SettingFileError: LocalizedError {
struct ImportDuplicationError: LocalizedError, RecoverableError {
var name: String
var type: SettingFileType
var type: UTType
var replacingClosure: (() throws -> Void)
var errorDescription: String? {
switch self.type {
case .syntaxStyle:
case .yaml:
return String(format: "A new style named “%@” will be installed, but a custom style with the same name already exists.".localized, self.name)
case .theme:
case .cotTheme:
return String(format: "A new theme named “%@” will be installed, but a custom theme with the same name already exists.".localized, self.name)
case .replacement:
case .cotReplacement:
return String(format: "A new replacement definition named “%@” will be installed, but a definition with the same name already exists.".localized, self.name)
default:
fatalError()
}
}
@ -543,12 +536,14 @@ struct ImportDuplicationError: LocalizedError, RecoverableError {
var recoverySuggestion: String? {
switch self.type {
case .syntaxStyle:
case .yaml:
return "Do you want to replace it?\nReplaced style cant be restored.".localized
case .theme:
case .cotTheme:
return "Do you want to replace it?\nReplaced theme cant be restored.".localized
case .replacement:
case .cotReplacement:
return "Do you want to replace it?\nReplaced definition cant be restored.".localized
default:
fatalError()
}
}

View File

@ -26,6 +26,7 @@
import Combine
import Foundation
import UniformTypeIdentifiers
import Yams
@objc protocol SyntaxHolder: AnyObject {
@ -64,8 +65,7 @@ final class SyntaxManager: SettingFileManaging {
let didUpdateSetting: PassthroughSubject<SettingChange, Never> = .init()
static let directoryName: String = "Syntaxes"
let filePathExtensions: [String] = ["yml", "yaml"]
let settingFileType: SettingFileType = .syntaxStyle
let fileType: UTType = .yaml
@Published var settingNames: [SettingName] = []
let bundledSettingNames: [SettingName]

View File

@ -26,6 +26,7 @@
import Combine
import Foundation
import AppKit
import UniformTypeIdentifiers
@objc protocol ThemeHolder: AnyObject {
@ -51,8 +52,7 @@ final class ThemeManager: SettingFileManaging {
let didUpdateSetting: PassthroughSubject<SettingChange, Never> = .init()
static let directoryName: String = "Themes"
let filePathExtensions: [String] = DocumentType.theme.extensions
let settingFileType: SettingFileType = .theme
let fileType: UTType = .cotTheme
@Published var settingNames: [String] = []
private(set) var bundledSettingNames: [String] = []
@ -66,7 +66,7 @@ final class ThemeManager: SettingFileManaging {
private init() {
// cache bundled setting names
self.bundledSettingNames = Bundle.main.urls(forResourcesWithExtension: self.filePathExtension, subdirectory: Self.directoryName)!
self.bundledSettingNames = Bundle.main.urls(forResourcesWithExtension: self.fileType.preferredFilenameExtension, subdirectory: Self.directoryName)!
.map { self.settingName(from: $0) }
.sorted(options: [.localized, .caseInsensitive])

View File

@ -1,14 +1,14 @@
//
// DocumentType.swift
// UTType+SettingFile.swift
//
// CotEditor
// https://coteditor.com
//
// Created by 1024jp on 2016-06-23.
// Created by 1024jp on 2022-03-15.
//
// ---------------------------------------------------------------------------
//
// © 2016-2020 1024jp
// © 2022 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -23,12 +23,10 @@
// limitations under the License.
//
struct DocumentType {
import UniformTypeIdentifiers
extension UTType {
var utType: String
var extensions: [String]
static let theme = Self(utType: "com.coteditor.CotEditor.theme", extensions: ["cottheme"])
static let replacement = Self(utType: "com.coteditor.CotEditor.replacement", extensions: ["cotrpl"])
static let cotTheme = UTType("com.coteditor.CotEditor.theme")!
static let cotReplacement = UTType("com.coteditor.CotEditor.replacement")!
}

View File

@ -9,7 +9,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2016-2020 1024jp
// © 2016-2022 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -25,6 +25,7 @@
//
import XCTest
import UniformTypeIdentifiers
@testable import CotEditor
final class ThemeTests: XCTestCase {
@ -72,7 +73,7 @@ final class ThemeTests: XCTestCase {
let enumerator = FileManager.default.enumerator(at: themeDirectoryURL, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])!
for case let url as URL in enumerator {
guard DocumentType.theme.extensions.contains(url.pathExtension) else { continue }
guard UTType.cotTheme.filenameExtensions.contains(url.pathExtension) else { continue }
_ = try Theme.theme(contentsOf: url)
}
@ -86,7 +87,7 @@ private extension ThemeTests {
func loadThemeWithName(_ name: String) throws -> Theme? {
let url = self.bundle.url(forResource: name, withExtension: DocumentType.theme.extensions[0], subdirectory: themeDirectoryName)
let url = self.bundle.url(forResource: name, withExtension: UTType.cotTheme.preferredFilenameExtension, subdirectory: themeDirectoryName)
return try Theme.theme(contentsOf: url!)
}