From 586822c981bd4f33139ca65c2fcbb0e4944352eb Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 27 Jan 2024 22:25:49 +0800 Subject: [PATCH] Repo // Refactor APIs related to LM access and configs. --- .../LangModelAssembly/LMInstantiator.swift | 38 +-- .../LMInstantiator_DateTimeExtension.swift | 4 +- .../InputTokenTests.swift | 6 +- .../LMInstantiatorSQLTests.swift | 12 +- .../Sources/MainAssembly/LMMgr_Core.swift | 230 +++++++----------- .../LMMgr_UserPhraseStructure.swift | 2 +- .../Sources/MainAssembly/PrefMgr_Core.swift | 10 +- .../MainAssembly/SessionCtl_Core.swift | 15 +- .../MainAssembly/SessionCtl_Delegates.swift | 10 +- .../SettingsUI/VwrSettingsPaneCassette.swift | 2 +- .../VwrSettingsPaneDictionary.swift | 6 +- .../Sources/Shared/Shared.swift | 4 + Source/Modules/SessionCtl_Menu.swift | 2 +- .../WindowControllers/CtlPrefWindow.swift | 4 +- 14 files changed, 159 insertions(+), 186 deletions(-) diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift index 161cedf7..ad02d0a1 100644 --- a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift +++ b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift @@ -30,26 +30,36 @@ public extension vChewingLM { /// LMI 會根據需要分別載入原廠語言模組和其他個別的子語言模組。LMI 本身不會記錄這些 /// 語言模組的相關資料的存放位置,僅藉由參數來讀取相關訊息。 class LMInstantiator: LangModelProtocol { + public struct Config { + public var isCassetteEnabled = false + public var isPhraseReplacementEnabled = false + public var isCNSEnabled = false + public var isSymbolEnabled = false + public var isSCPCEnabled = false + public var deltaOfCalendarYears: Int = -2000 + } + // SQLite 連線所在的記憶體位置。 static var ptrSQL: OpaquePointer? // SQLite 連線是否已經建立。 public private(set) static var isSQLDBConnected: Bool = false + // 簡體中文模型? + public let isCHS: Bool + // 在函式內部用以記錄狀態的開關。 - public var isCassetteEnabled = false - public var isPhraseReplacementEnabled = false - public var isCNSEnabled = false - public var isSymbolEnabled = false - public var isSCPCEnabled = false - public var isCHS = false - public var deltaOfCalendarYears: Int = -2000 + public var config = Config() // 這句需要留著,不然無法被 package 外界存取。 public init(isCHS: Bool = false) { self.isCHS = isCHS } + public func setOptions(handler: (inout Config) -> Void) { + handler(&config) + } + @discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool { if dropPreviousConnection { disconnectSQLDB() } vCLog("Establishing SQLite connection to: \(dbPath)") @@ -315,10 +325,10 @@ public extension vChewingLM { /// 準備不同的語言模組容器,開始逐漸往容器陣列內塞入資料。 var rawAllUnigrams: [Megrez.Unigram] = [] - if isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) } + if config.isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) } // 如果有檢測到使用者自訂逐字選字語料庫內的相關資料的話,在這裡先插入。 - if isSCPCEnabled { + if config.isSCPCEnabled { rawAllUnigrams += lmPlainBopomofo.valuesFor(key: keyChain).map { Megrez.Unigram(value: $0, score: 0) } } @@ -327,18 +337,18 @@ public extension vChewingLM { // 將兩句差分也是為了讓 rawUserUnigrams 的類型不受可能的影響。 rawAllUnigrams += lmUserPhrases.unigramsFor(key: keyChain).reversed() - if !isCassetteEnabled || isCassetteEnabled && keyChain.map(\.description)[0] == "_" { + if !config.isCassetteEnabled || config.isCassetteEnabled && keyChain.map(\.description)[0] == "_" { // LMMisc 與 LMCore 的 score 在 (-10.0, 0.0) 這個區間內。 rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW) rawAllUnigrams += factoryCoreUnigramsFor(key: keyChain) - if isCNSEnabled { + if config.isCNSEnabled { rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS) } } - if isSymbolEnabled { + if config.isSymbolEnabled { rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain) - if !isCassetteEnabled { + if !config.isCassetteEnabled { rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB) } } @@ -363,7 +373,7 @@ public extension vChewingLM { } // 提前處理語彙置換。 - if isPhraseReplacementEnabled { + if config.isPhraseReplacementEnabled { for i in 0 ..< rawAllUnigrams.count { let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value) guard !newValue.isEmpty else { continue } diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator_DateTimeExtension.swift b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator_DateTimeExtension.swift index 71f6061c..e0d3e945 100644 --- a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator_DateTimeExtension.swift +++ b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator_DateTimeExtension.swift @@ -19,14 +19,14 @@ extension vChewingLM.LMInstantiator { func processDateWithDayDelta(_ delta: Int) { tokens = ["MACRO@DATE_DAYDELTA:\(delta)"] - if deltaOfCalendarYears != 0 { tokens.append("MACRO@DATE_DAYDELTA:\(delta)_YEARDELTA:\(deltaOfCalendarYears)") } + if config.deltaOfCalendarYears != 0 { tokens.append("MACRO@DATE_DAYDELTA:\(delta)_YEARDELTA:\(config.deltaOfCalendarYears)") } tokens.append("MACRO@DATE_DAYDELTA:\(delta)_SHORTENED") tokens.append("MACRO@DATE_DAYDELTA:\(delta)_LUNA") } func processYearWithYearDelta(_ delta: Int) { tokens = ["MACRO@YEAR_YEARDELTA:\(delta)"] - if deltaOfCalendarYears != 0 { tokens.append("MACRO@YEAR_YEARDELTA:\(delta + deltaOfCalendarYears)") } + if config.deltaOfCalendarYears != 0 { tokens.append("MACRO@YEAR_YEARDELTA:\(delta + config.deltaOfCalendarYears)") } tokens.append("MACRO@YEAR_GANZHI_YEARDELTA:\(delta)") tokens.append("MACRO@YEAR_ZODIAC_YEARDELTA:\(delta)") } diff --git a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/InputTokenTests.swift b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/InputTokenTests.swift index 7af06ed6..588a472d 100644 --- a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/InputTokenTests.swift +++ b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/InputTokenTests.swift @@ -59,8 +59,10 @@ final class InputTokenTests: XCTestCase { func testGeneratedResultsFromLMInstantiator() throws { let instance = vChewingLM.LMInstantiator(isCHS: true) XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB()) - instance.isCNSEnabled = false - instance.isSymbolEnabled = false + instance.setOptions { config in + config.isCNSEnabled = false + config.isSymbolEnabled = false + } instance.insertTemporaryData( keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"], unigram: .init(value: "MACRO@DATE_YEARDELTA:-1945", score: -97.5), diff --git a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMInstantiatorSQLTests.swift b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMInstantiatorSQLTests.swift index 4b0ef283..ca8296f0 100644 --- a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMInstantiatorSQLTests.swift +++ b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMInstantiatorSQLTests.swift @@ -24,14 +24,18 @@ final class LMInstantiatorSQLTests: XCTestCase { func testSQL() throws { let instance = vChewingLM.LMInstantiator(isCHS: true) XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB()) - instance.isCNSEnabled = false - instance.isSymbolEnabled = false + instance.setOptions { config in + config.isCNSEnabled = false + config.isSymbolEnabled = false + } XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).description, "[(吹牛逼,-7.375), (吹牛屄,-7.399)]") XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,,-9.9)") XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).description, "[(㨃,-9.544)]") XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).description, "[(ㄋㄟㄋㄟ,-1.0)]") - instance.isCNSEnabled = true - instance.isSymbolEnabled = true + instance.setOptions { config in + config.isCNSEnabled = true + config.isSymbolEnabled = true + } XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).last?.description, "(🌳🆕🐝,-13.0)") XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,,-9.9)") XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10) diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_Core.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_Core.swift index fbc08a54..eeeabfe0 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_Core.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_Core.swift @@ -12,50 +12,42 @@ import NotifierUI import Shared import SwiftExtension +// MARK: - Input Mode Extension for Language Models + +public extension Shared.InputMode { + private static let lmCHS = vChewingLM.LMInstantiator(isCHS: true) + private static let lmCHT = vChewingLM.LMInstantiator(isCHS: false) + private static let uomCHS = vChewingLM.LMUserOverride(dataURL: LMMgr.userOverrideModelDataURL(.imeModeCHS)) + private static let uomCHT = vChewingLM.LMUserOverride(dataURL: LMMgr.userOverrideModelDataURL(.imeModeCHT)) + + var langModel: vChewingLM.LMInstantiator { + switch self { + case .imeModeCHS: return Self.lmCHS + case .imeModeCHT: return Self.lmCHT + case .imeModeNULL: return .init() + } + } + + var uom: vChewingLM.LMUserOverride { + switch self { + case .imeModeCHS: return Self.uomCHS + case .imeModeCHT: return Self.uomCHT + case .imeModeNULL: return .init(dataURL: LMMgr.userOverrideModelDataURL(IMEApp.currentInputMode)) + } + } +} + +// MARK: - Language Model Manager. + public class LMMgr { public static var shared = LMMgr() - public private(set) static var lmCHS = vChewingLM.LMInstantiator(isCHS: true) - public private(set) static var lmCHT = vChewingLM.LMInstantiator(isCHS: false) - public private(set) static var uomCHS = vChewingLM.LMUserOverride( - dataURL: LMMgr.userOverrideModelDataURL(.imeModeCHS)) - public private(set) static var uomCHT = vChewingLM.LMUserOverride( - dataURL: LMMgr.userOverrideModelDataURL(.imeModeCHT)) - - public static var currentLM: vChewingLM.LMInstantiator { - Self.getLM(mode: IMEApp.currentInputMode) - } - - public static var currentUOM: vChewingLM.LMUserOverride { - Self.getUOM(mode: IMEApp.currentInputMode) - } - - public static func getLM(mode: Shared.InputMode) -> vChewingLM.LMInstantiator { - switch mode { - case .imeModeCHS: - return Self.lmCHS - case .imeModeCHT: - return Self.lmCHT - case .imeModeNULL: - return .init() - } - } - - public static func getUOM(mode: Shared.InputMode) -> vChewingLM.LMUserOverride { - switch mode { - case .imeModeCHS: - return Self.uomCHS - case .imeModeCHT: - return Self.uomCHT - case .imeModeNULL: - return .init(dataURL: Self.userOverrideModelDataURL(IMEApp.currentInputMode)) - } - } // MARK: - Functions reacting directly with language models. public static func initUserLangModels() { - Self.chkUserLMFilesExist(.imeModeCHT) - Self.chkUserLMFilesExist(.imeModeCHS) + Shared.InputMode.validCases.forEach { mode in + Self.chkUserLMFilesExist(mode) + } // LMMgr 的 loadUserPhrases 等函式在自動讀取 dataFolderPath 時, // 如果發現自訂目錄不可用,則會自動抹去自訂目錄設定、改採預設目錄。 // 所以這裡不需要特別處理。 @@ -84,83 +76,69 @@ public class LMMgr { public static func loadUserPhrasesData(type: vChewingLM.ReplacableUserDataType? = nil) { guard let type = type else { - Self.lmCHT.loadUserPhrasesData( - path: userDictDataURL(mode: .imeModeCHT, type: .thePhrases).path, - filterPath: userDictDataURL(mode: .imeModeCHT, type: .theFilter).path - ) - Self.lmCHS.loadUserPhrasesData( - path: userDictDataURL(mode: .imeModeCHS, type: .thePhrases).path, - filterPath: userDictDataURL(mode: .imeModeCHS, type: .theFilter).path - ) - Self.lmCHT.loadUserSymbolData(path: userDictDataURL(mode: .imeModeCHT, type: .theSymbols).path) - Self.lmCHS.loadUserSymbolData(path: userDictDataURL(mode: .imeModeCHS, type: .theSymbols).path) + Shared.InputMode.validCases.forEach { mode in + mode.langModel.loadUserPhrasesData( + path: userDictDataURL(mode: mode, type: .thePhrases).path, + filterPath: userDictDataURL(mode: mode, type: .theFilter).path + ) + mode.langModel.loadUserSymbolData(path: userDictDataURL(mode: mode, type: .theSymbols).path) + mode.uom.loadData(fromURL: userOverrideModelDataURL(mode)) + } if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() } if PrefMgr.shared.phraseReplacementEnabled { Self.loadUserPhraseReplacement() } if PrefMgr.shared.useSCPCTypingMode { Self.loadSCPCSequencesData() } - Self.uomCHT.loadData(fromURL: userOverrideModelDataURL(.imeModeCHT)) - Self.uomCHS.loadData(fromURL: userOverrideModelDataURL(.imeModeCHS)) - CandidateNode.load(url: Self.userSymbolMenuDataURL()) return } - switch type { - case .thePhrases: - Self.lmCHT.loadUserPhrasesData( - path: userDictDataURL(mode: .imeModeCHT, type: .thePhrases).path, - filterPath: nil - ) - Self.lmCHS.loadUserPhrasesData( - path: userDictDataURL(mode: .imeModeCHS, type: .thePhrases).path, - filterPath: nil - ) - case .theFilter: - DispatchQueue.main.async { - Self.reloadUserFilterDirectly(mode: IMEApp.currentInputMode) + Shared.InputMode.validCases.forEach { mode in + switch type { + case .thePhrases: + mode.langModel.loadUserPhrasesData( + path: userDictDataURL(mode: mode, type: .thePhrases).path, + filterPath: nil + ) + case .theFilter: + DispatchQueue.main.async { + Self.reloadUserFilterDirectly(mode: mode) + } + case .theReplacements: + if PrefMgr.shared.phraseReplacementEnabled { Self.loadUserPhraseReplacement() } + case .theAssociates: + if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() } + case .theSymbols: + mode.langModel.loadUserSymbolData( + path: Self.userDictDataURL(mode: mode, type: .theSymbols).path + ) } - DispatchQueue.main.async { - Self.reloadUserFilterDirectly(mode: IMEApp.currentInputMode.reversed) - } - case .theReplacements: - if PrefMgr.shared.phraseReplacementEnabled { Self.loadUserPhraseReplacement() } - case .theAssociates: - if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() } - case .theSymbols: - Self.lmCHT.loadUserSymbolData( - path: Self.userDictDataURL(mode: .imeModeCHT, type: .theSymbols).path - ) - Self.lmCHS.loadUserSymbolData( - path: Self.userDictDataURL(mode: .imeModeCHS, type: .theSymbols).path - ) } } public static func loadUserAssociatesData() { - Self.lmCHT.loadUserAssociatesData( - path: Self.userDictDataURL(mode: .imeModeCHT, type: .theAssociates).path - ) - Self.lmCHS.loadUserAssociatesData( - path: Self.userDictDataURL(mode: .imeModeCHS, type: .theAssociates).path - ) + Shared.InputMode.validCases.forEach { mode in + mode.langModel.loadUserAssociatesData( + path: Self.userDictDataURL(mode: mode, type: .theAssociates).path + ) + } } public static func loadUserPhraseReplacement() { - Self.lmCHT.loadReplacementsData( - path: Self.userDictDataURL(mode: .imeModeCHT, type: .theReplacements).path - ) - Self.lmCHS.loadReplacementsData( - path: Self.userDictDataURL(mode: .imeModeCHS, type: .theReplacements).path - ) + Shared.InputMode.validCases.forEach { mode in + mode.langModel.loadReplacementsData( + path: Self.userDictDataURL(mode: mode, type: .theReplacements).path + ) + } } public static func loadSCPCSequencesData() { - Self.lmCHT.loadSCPCSequencesData() - Self.lmCHS.loadSCPCSequencesData() + Shared.InputMode.validCases.forEach { mode in + mode.langModel.loadSCPCSequencesData() + } } public static func reloadUserFilterDirectly(mode: Shared.InputMode) { - Self.getLM(mode: mode).reloadUserFilterDirectly(path: userDictDataURL(mode: mode, type: .theFilter).path) + mode.langModel.reloadUserFilterDirectly(path: userDictDataURL(mode: mode, type: .theFilter).path) } public static func checkIfPhrasePairExists( @@ -169,7 +147,7 @@ public class LMMgr { keyArray: [String], factoryDictionaryOnly: Bool = false ) -> Bool { - Self.getLM(mode: mode).hasKeyValuePairFor( + mode.langModel.hasKeyValuePairFor( keyArray: keyArray, value: userPhrase, factoryDictionaryOnly: factoryDictionaryOnly ) } @@ -179,7 +157,7 @@ public class LMMgr { mode: Shared.InputMode, keyArray: [String] ) -> Bool { - Self.getLM(mode: mode).isPairFiltered(pair: .init(keyArray: keyArray, value: userPhrase)) + mode.langModel.isPairFiltered(pair: .init(keyArray: keyArray, value: userPhrase)) } public static func countPhrasePairs( @@ -187,39 +165,22 @@ public class LMMgr { mode: Shared.InputMode, factoryDictionaryOnly: Bool = false ) -> Int { - Self.getLM(mode: mode).countKeyValuePairs( + mode.langModel.countKeyValuePairs( keyArray: keyArray, factoryDictionaryOnly: factoryDictionaryOnly ) } - public static func setPhraseReplacementEnabled(_ state: Bool) { - Self.lmCHT.isPhraseReplacementEnabled = state - Self.lmCHS.isPhraseReplacementEnabled = state - } - - public static func setCNSEnabled(_ state: Bool) { - Self.lmCHT.isCNSEnabled = state - Self.lmCHS.isCNSEnabled = state - } - - public static func setSymbolEnabled(_ state: Bool) { - Self.lmCHT.isSymbolEnabled = state - Self.lmCHS.isSymbolEnabled = state - } - - public static func setSCPCEnabled(_ state: Bool) { - Self.lmCHT.isSCPCEnabled = state - Self.lmCHS.isSCPCEnabled = state - } - - public static func setCassetteEnabled(_ state: Bool) { - Self.lmCHT.isCassetteEnabled = state - Self.lmCHS.isCassetteEnabled = state - } - - public static func setDeltaOfCalendarYears(_ delta: Int) { - Self.lmCHT.deltaOfCalendarYears = delta - Self.lmCHS.deltaOfCalendarYears = delta + public static func syncLMPrefs() { + Shared.InputMode.validCases.forEach { mode in + mode.langModel.setOptions { config in + config.isPhraseReplacementEnabled = PrefMgr.shared.phraseReplacementEnabled + config.isCNSEnabled = PrefMgr.shared.cns11643Enabled + config.isSymbolEnabled = PrefMgr.shared.symbolInputEnabled + config.isSCPCEnabled = PrefMgr.shared.useSCPCTypingMode + config.isCassetteEnabled = PrefMgr.shared.cassetteEnabled + config.deltaOfCalendarYears = PrefMgr.shared.deltaOfCalendarYears + } + } } // MARK: UOM @@ -227,26 +188,23 @@ public class LMMgr { public static func saveUserOverrideModelData() { let globalQueue = DispatchQueue(label: "vChewingLM_UOM", qos: .unspecified, attributes: .concurrent) let group = DispatchGroup() - group.enter() - globalQueue.async { - Self.uomCHT.saveData(toURL: userOverrideModelDataURL(.imeModeCHT)) - group.leave() - } - group.enter() - globalQueue.async { - Self.uomCHS.saveData(toURL: userOverrideModelDataURL(.imeModeCHS)) - group.leave() + Shared.InputMode.validCases.forEach { mode in + group.enter() + globalQueue.async { + mode.uom.saveData(toURL: userOverrideModelDataURL(mode)) + group.leave() + } } _ = group.wait(timeout: .distantFuture) group.notify(queue: DispatchQueue.main) {} } public static func bleachSpecifiedSuggestions(targets: [String], mode: Shared.InputMode) { - Self.getUOM(mode: mode).bleachSpecifiedSuggestions(targets: targets, saveCallback: { Self.getUOM(mode: mode).saveData() }) + mode.uom.bleachSpecifiedSuggestions(targets: targets, saveCallback: { mode.uom.saveData() }) } public static func removeUnigramsFromUserOverrideModel(_ mode: Shared.InputMode) { - Self.getUOM(mode: mode).bleachUnigrams(saveCallback: { Self.getUOM(mode: mode).saveData() }) + mode.uom.bleachUnigrams(saveCallback: { mode.uom.saveData() }) } public static func relocateWreckedUOMData() { @@ -268,6 +226,6 @@ public class LMMgr { } public static func clearUserOverrideModelData(_ mode: Shared.InputMode = .imeModeNULL) { - Self.getUOM(mode: mode).clearData(withURL: userOverrideModelDataURL(mode)) + mode.uom.clearData(withURL: userOverrideModelDataURL(mode)) } } diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_UserPhraseStructure.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_UserPhraseStructure.swift index 78e75fd0..3c57dea4 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_UserPhraseStructure.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/LMMgr_UserPhraseStructure.swift @@ -62,7 +62,7 @@ public extension LMMgr { } public var isAlreadyFiltered: Bool { - LMMgr.getLM(mode: inputMode).isPairFiltered(pair: .init(keyArray: keyArray, value: value)) + inputMode.langModel.isPairFiltered(pair: .init(keyArray: keyArray, value: value)) } public func write(toFilter: Bool) -> Bool { diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift index e76ca22a..b7cb76b5 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift @@ -230,21 +230,21 @@ import SwiftExtension @AppProperty(key: UserDef.kCNS11643Enabled.rawValue, defaultValue: false) public dynamic var cns11643Enabled: Bool { didSet { - LMMgr.setCNSEnabled(cns11643Enabled) // 很重要 + LMMgr.syncLMPrefs() } } @AppProperty(key: UserDef.kSymbolInputEnabled.rawValue, defaultValue: true) public dynamic var symbolInputEnabled: Bool { didSet { - LMMgr.setSymbolEnabled(symbolInputEnabled) // 很重要 + LMMgr.syncLMPrefs() } } @AppProperty(key: UserDef.kCassetteEnabled.rawValue, defaultValue: false) public dynamic var cassetteEnabled: Bool { didSet { - LMMgr.setCassetteEnabled(cassetteEnabled) // 很重要 + LMMgr.syncLMPrefs() } } @@ -325,7 +325,7 @@ import SwiftExtension willSet { if newValue { LMMgr.loadSCPCSequencesData() - LMMgr.setSCPCEnabled(true) + LMMgr.syncLMPrefs() } } } @@ -333,7 +333,7 @@ import SwiftExtension @AppProperty(key: UserDef.kPhraseReplacementEnabled.rawValue, defaultValue: false) public dynamic var phraseReplacementEnabled: Bool { willSet { - LMMgr.setPhraseReplacementEnabled(newValue) + LMMgr.syncLMPrefs() if newValue { LMMgr.loadUserPhraseReplacement() } diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Core.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Core.swift index e7a97646..afcf8853 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Core.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Core.swift @@ -177,8 +177,8 @@ public class SessionCtl: IMKInputController { resetInputHandler() // ---------------------------- /// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。 - inputHandler?.currentLM = LMMgr.currentLM // 會自動更新組字引擎內的模組。 - inputHandler?.currentUOM = LMMgr.currentUOM + inputHandler?.currentLM = inputMode.langModel // 會自動更新組字引擎內的模組。 + inputHandler?.currentUOM = inputMode.uom /// 清空注拼槽+同步最新的注拼槽排列設定。 inputHandler?.ensureKeyboardParser() /// 將輸入法偏好設定同步至語言模組內。 @@ -215,7 +215,7 @@ public class SessionCtl: IMKInputController { Self.current?.hidePalettes() Self.current = self self.inputHandler = InputHandler( - lm: LMMgr.currentLM, uom: LMMgr.currentUOM, pref: PrefMgr.shared + lm: self.inputMode.langModel, uom: self.inputMode.uom, pref: PrefMgr.shared ) self.inputHandler?.delegate = self self.syncBaseLMPrefs() @@ -314,7 +314,7 @@ public extension SessionCtl { // 這裡不需要 setValue(),因為 IMK 會在自動呼叫 activateServer() 之後自動執行 setValue()。 self.inputHandler = InputHandler( - lm: LMMgr.currentLM, uom: LMMgr.currentUOM, pref: PrefMgr.shared + lm: self.inputMode.langModel, uom: self.inputMode.uom, pref: PrefMgr.shared ) self.inputHandler?.delegate = self self.syncBaseLMPrefs() @@ -408,12 +408,7 @@ public extension SessionCtl { /// 將輸入法偏好設定同步至語言模組內。 func syncBaseLMPrefs() { - LMMgr.currentLM.isPhraseReplacementEnabled = PrefMgr.shared.phraseReplacementEnabled - LMMgr.currentLM.isCNSEnabled = PrefMgr.shared.cns11643Enabled - LMMgr.currentLM.isSymbolEnabled = PrefMgr.shared.symbolInputEnabled - LMMgr.currentLM.isSCPCEnabled = PrefMgr.shared.useSCPCTypingMode - LMMgr.currentLM.isCassetteEnabled = PrefMgr.shared.cassetteEnabled - LMMgr.currentLM.deltaOfCalendarYears = PrefMgr.shared.deltaOfCalendarYears + LMMgr.syncLMPrefs() } } diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Delegates.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Delegates.swift index ca9fa1df..21e9ed20 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Delegates.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_Delegates.swift @@ -63,7 +63,7 @@ extension SessionCtl: InputHandlerDelegate { // 因為上述操作不會立即生效(除非遞交組字區),所以暫時塞入臨時資料記錄。 // 該臨時資料記錄會在接下來的語言模組資料重載過程中被自動清除。 - LMMgr.currentLM.insertTemporaryData( + inputMode.langModel.insertTemporaryData( keyArray: userPhrase.keyArray, unigram: .init(value: userPhrase.value, score: userPhrase.weight ?? 0), isFiltering: addToFilter @@ -119,7 +119,7 @@ extension SessionCtl: CtlCandidateDelegate { if state.type == .ofAssociates { return shortened ? "⇧" : NSLocalizedString("Hold ⇧ to choose associates.", comment: "") } else if state.type == .ofInputting, state.isCandidateContainer { - let useShift = LMMgr.currentLM.areCassetteCandidateKeysShiftHeld + let useShift = inputMode.langModel.areCassetteCandidateKeysShiftHeld let theEmoji = useShift ? "⬆️" : "⚡️" return shortened ? theEmoji : "\(theEmoji) " + NSLocalizedString("Quick Candidates", comment: "") } else if PrefMgr.shared.cassetteEnabled { @@ -141,14 +141,14 @@ extension SessionCtl: CtlCandidateDelegate { if value.isEmpty { return blankResult } // 空字串沒有需要反查的東西。 if value.contains("_") { return blankResult } // 因為威注音輸入法的反查結果僅由磁帶模組負責,所以相關運算挪至 LMInstantiator 內處理。 - return LMMgr.currentLM.cassetteReverseLookup(for: value) + return inputMode.langModel.cassetteReverseLookup(for: value) } public var selectionKeys: String { // 磁帶模式的 `%quick` 有單獨的選字鍵判定,會在資料不合規時使用 1234567890 選字鍵。 cassetteQuick: if state.type == .ofInputting, state.isCandidateContainer { guard PrefMgr.shared.cassetteEnabled else { break cassetteQuick } - guard let cinCandidateKey = LMMgr.currentLM.cassetteSelectionKey, + guard let cinCandidateKey = inputMode.langModel.cassetteSelectionKey, CandidateKey.validate(keys: cinCandidateKey) == nil else { return "1234567890" @@ -267,7 +267,7 @@ extension SessionCtl: CtlCandidateDelegate { // 因為上述操作不會立即生效(除非遞交組字區),所以暫時塞入臨時資料記錄。 // 該臨時資料記錄會在接下來的語言模組資料重載過程中被自動清除。 - LMMgr.currentLM.insertTemporaryData( + inputMode.langModel.insertTemporaryData( keyArray: userPhrase.keyArray, unigram: .init(value: userPhrase.value, score: userPhrase.weight ?? 0), isFiltering: action == .toFilter diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneCassette.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneCassette.swift index 5b3de497..9f5f29e6 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneCassette.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneCassette.swift @@ -140,7 +140,7 @@ public struct VwrSettingsPaneCassette: View { } else { LMMgr.loadCassetteData() } - LMMgr.setCassetteEnabled(cassetteEnabled) + LMMgr.syncLMPrefs() } ) } diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneDictionary.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneDictionary.swift index bbcfea07..29c20bb5 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneDictionary.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SettingsUI/VwrSettingsPaneDictionary.swift @@ -177,13 +177,13 @@ public struct VwrSettingsPaneDictionary: View { Toggle( LocalizedStringKey("Enable CNS11643 Support (2023-11-06)"), isOn: $cns11643Enabled.onChange { - LMMgr.setCNSEnabled(cns11643Enabled) + LMMgr.syncLMPrefs() } ) Toggle( LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"), isOn: $symbolInputEnabled.onChange { - LMMgr.setSymbolEnabled(symbolInputEnabled) + LMMgr.syncLMPrefs() } ) VStack(alignment: .leading) { @@ -200,7 +200,7 @@ public struct VwrSettingsPaneDictionary: View { Toggle( LocalizedStringKey("Enable phrase replacement table"), isOn: $phraseReplacementEnabled.onChange { - LMMgr.setPhraseReplacementEnabled(phraseReplacementEnabled) + LMMgr.syncLMPrefs() if phraseReplacementEnabled { LMMgr.loadUserPhraseReplacement() } diff --git a/Packages/vChewing_Shared/Sources/Shared/Shared.swift b/Packages/vChewing_Shared/Sources/Shared/Shared.swift index 9297ef11..9cf07e67 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Shared.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Shared.swift @@ -226,6 +226,10 @@ public enum Shared { } } + public static var validCases: [InputMode] { + [.imeModeCHS, .imeModeCHT] + } + public var localizedDescription: String { NSLocalizedString(description, comment: "") } public var description: String { switch self { diff --git a/Source/Modules/SessionCtl_Menu.swift b/Source/Modules/SessionCtl_Menu.swift index 4da7fed2..58096841 100644 --- a/Source/Modules/SessionCtl_Menu.swift +++ b/Source/Modules/SessionCtl_Menu.swift @@ -272,7 +272,7 @@ public extension SessionCtl { ? "NotificationSwitchON".localized : "NotificationSwitchOFF".localized) ) - if !LMMgr.currentLM.isCassetteDataLoaded { + if !inputMode.langModel.isCassetteDataLoaded { LMMgr.loadCassetteData() } } diff --git a/Source/Modules/WindowControllers/CtlPrefWindow.swift b/Source/Modules/WindowControllers/CtlPrefWindow.swift index a0ffd5bb..48a70cf1 100644 --- a/Source/Modules/WindowControllers/CtlPrefWindow.swift +++ b/Source/Modules/WindowControllers/CtlPrefWindow.swift @@ -233,11 +233,11 @@ class CtlPrefWindow: NSWindowController, NSWindowDelegate { // 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。 // 所有涉及到語言模型開關的內容均需要這樣處理。 @IBAction func toggleCNSSupport(_: Any) { - LMMgr.setCNSEnabled(PrefMgr.shared.cns11643Enabled) + LMMgr.syncLMPrefs() } @IBAction func toggleSymbolInputEnabled(_: Any) { - LMMgr.setSymbolEnabled(PrefMgr.shared.symbolInputEnabled) + LMMgr.syncLMPrefs() } @IBAction func toggleTrad2KangXiAction(_: Any) {