Repo // Refactor APIs related to LM access and configs.

This commit is contained in:
ShikiSuen 2024-01-27 22:25:49 +08:00
parent 23ef3124d4
commit 586822c981
14 changed files with 159 additions and 186 deletions

View File

@ -30,26 +30,36 @@ public extension vChewingLM {
/// LMI LMI /// LMI LMI
/// ///
class LMInstantiator: LangModelProtocol { 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 // SQLite
static var ptrSQL: OpaquePointer? static var ptrSQL: OpaquePointer?
// SQLite // SQLite
public private(set) static var isSQLDBConnected: Bool = false public private(set) static var isSQLDBConnected: Bool = false
//
public let isCHS: Bool
// //
public var isCassetteEnabled = false public var config = Config()
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
// package // package
public init(isCHS: Bool = false) { public init(isCHS: Bool = false) {
self.isCHS = isCHS self.isCHS = isCHS
} }
public func setOptions(handler: (inout Config) -> Void) {
handler(&config)
}
@discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool { @discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool {
if dropPreviousConnection { disconnectSQLDB() } if dropPreviousConnection { disconnectSQLDB() }
vCLog("Establishing SQLite connection to: \(dbPath)") vCLog("Establishing SQLite connection to: \(dbPath)")
@ -315,10 +325,10 @@ public extension vChewingLM {
/// ///
var rawAllUnigrams: [Megrez.Unigram] = [] 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) } rawAllUnigrams += lmPlainBopomofo.valuesFor(key: keyChain).map { Megrez.Unigram(value: $0, score: 0) }
} }
@ -327,18 +337,18 @@ public extension vChewingLM {
// rawUserUnigrams // rawUserUnigrams
rawAllUnigrams += lmUserPhrases.unigramsFor(key: keyChain).reversed() 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) // LMMisc LMCore score (-10.0, 0.0)
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW) rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW)
rawAllUnigrams += factoryCoreUnigramsFor(key: keyChain) rawAllUnigrams += factoryCoreUnigramsFor(key: keyChain)
if isCNSEnabled { if config.isCNSEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS) rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS)
} }
} }
if isSymbolEnabled { if config.isSymbolEnabled {
rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain) rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain)
if !isCassetteEnabled { if !config.isCassetteEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB) rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB)
} }
} }
@ -363,7 +373,7 @@ public extension vChewingLM {
} }
// //
if isPhraseReplacementEnabled { if config.isPhraseReplacementEnabled {
for i in 0 ..< rawAllUnigrams.count { for i in 0 ..< rawAllUnigrams.count {
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value) let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
guard !newValue.isEmpty else { continue } guard !newValue.isEmpty else { continue }

View File

@ -19,14 +19,14 @@ extension vChewingLM.LMInstantiator {
func processDateWithDayDelta(_ delta: Int) { func processDateWithDayDelta(_ delta: Int) {
tokens = ["MACRO@DATE_DAYDELTA:\(delta)"] 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)_SHORTENED")
tokens.append("MACRO@DATE_DAYDELTA:\(delta)_LUNA") tokens.append("MACRO@DATE_DAYDELTA:\(delta)_LUNA")
} }
func processYearWithYearDelta(_ delta: Int) { func processYearWithYearDelta(_ delta: Int) {
tokens = ["MACRO@YEAR_YEARDELTA:\(delta)"] 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_GANZHI_YEARDELTA:\(delta)")
tokens.append("MACRO@YEAR_ZODIAC_YEARDELTA:\(delta)") tokens.append("MACRO@YEAR_ZODIAC_YEARDELTA:\(delta)")
} }

View File

@ -59,8 +59,10 @@ final class InputTokenTests: XCTestCase {
func testGeneratedResultsFromLMInstantiator() throws { func testGeneratedResultsFromLMInstantiator() throws {
let instance = vChewingLM.LMInstantiator(isCHS: true) let instance = vChewingLM.LMInstantiator(isCHS: true)
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB()) XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
instance.isCNSEnabled = false instance.setOptions { config in
instance.isSymbolEnabled = false config.isCNSEnabled = false
config.isSymbolEnabled = false
}
instance.insertTemporaryData( instance.insertTemporaryData(
keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"], keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"],
unigram: .init(value: "MACRO@DATE_YEARDELTA:-1945", score: -97.5), unigram: .init(value: "MACRO@DATE_YEARDELTA:-1945", score: -97.5),

View File

@ -24,14 +24,18 @@ final class LMInstantiatorSQLTests: XCTestCase {
func testSQL() throws { func testSQL() throws {
let instance = vChewingLM.LMInstantiator(isCHS: true) let instance = vChewingLM.LMInstantiator(isCHS: true)
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB()) XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
instance.isCNSEnabled = false instance.setOptions { config in
instance.isSymbolEnabled = false config.isCNSEnabled = false
config.isSymbolEnabled = false
}
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).description, "[(吹牛逼,-7.375), (吹牛屄,-7.399)]") XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).description, "[(吹牛逼,-7.375), (吹牛屄,-7.399)]")
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)") XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)")
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).description, "[(㨃,-9.544)]") XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).description, "[(㨃,-9.544)]")
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).description, "[(ㄋㄟㄋㄟ,-1.0)]") XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).description, "[(ㄋㄟㄋㄟ,-1.0)]")
instance.isCNSEnabled = true instance.setOptions { config in
instance.isSymbolEnabled = true config.isCNSEnabled = true
config.isSymbolEnabled = true
}
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).last?.description, "(🌳🆕🐝,-13.0)") XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).last?.description, "(🌳🆕🐝,-13.0)")
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)") XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)")
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10) XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10)

View File

@ -12,50 +12,42 @@ import NotifierUI
import Shared import Shared
import SwiftExtension 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 class LMMgr {
public static var shared = 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. // MARK: - Functions reacting directly with language models.
public static func initUserLangModels() { public static func initUserLangModels() {
Self.chkUserLMFilesExist(.imeModeCHT) Shared.InputMode.validCases.forEach { mode in
Self.chkUserLMFilesExist(.imeModeCHS) Self.chkUserLMFilesExist(mode)
}
// LMMgr loadUserPhrases dataFolderPath // LMMgr loadUserPhrases dataFolderPath
// //
// //
@ -84,83 +76,69 @@ public class LMMgr {
public static func loadUserPhrasesData(type: vChewingLM.ReplacableUserDataType? = nil) { public static func loadUserPhrasesData(type: vChewingLM.ReplacableUserDataType? = nil) {
guard let type = type else { guard let type = type else {
Self.lmCHT.loadUserPhrasesData( Shared.InputMode.validCases.forEach { mode in
path: userDictDataURL(mode: .imeModeCHT, type: .thePhrases).path, mode.langModel.loadUserPhrasesData(
filterPath: userDictDataURL(mode: .imeModeCHT, type: .theFilter).path path: userDictDataURL(mode: mode, type: .thePhrases).path,
) filterPath: userDictDataURL(mode: mode, type: .theFilter).path
Self.lmCHS.loadUserPhrasesData( )
path: userDictDataURL(mode: .imeModeCHS, type: .thePhrases).path, mode.langModel.loadUserSymbolData(path: userDictDataURL(mode: mode, type: .theSymbols).path)
filterPath: userDictDataURL(mode: .imeModeCHS, type: .theFilter).path mode.uom.loadData(fromURL: userOverrideModelDataURL(mode))
) }
Self.lmCHT.loadUserSymbolData(path: userDictDataURL(mode: .imeModeCHT, type: .theSymbols).path)
Self.lmCHS.loadUserSymbolData(path: userDictDataURL(mode: .imeModeCHS, type: .theSymbols).path)
if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() } if PrefMgr.shared.associatedPhrasesEnabled { Self.loadUserAssociatesData() }
if PrefMgr.shared.phraseReplacementEnabled { Self.loadUserPhraseReplacement() } if PrefMgr.shared.phraseReplacementEnabled { Self.loadUserPhraseReplacement() }
if PrefMgr.shared.useSCPCTypingMode { Self.loadSCPCSequencesData() } if PrefMgr.shared.useSCPCTypingMode { Self.loadSCPCSequencesData() }
Self.uomCHT.loadData(fromURL: userOverrideModelDataURL(.imeModeCHT))
Self.uomCHS.loadData(fromURL: userOverrideModelDataURL(.imeModeCHS))
CandidateNode.load(url: Self.userSymbolMenuDataURL()) CandidateNode.load(url: Self.userSymbolMenuDataURL())
return return
} }
switch type { Shared.InputMode.validCases.forEach { mode in
case .thePhrases: switch type {
Self.lmCHT.loadUserPhrasesData( case .thePhrases:
path: userDictDataURL(mode: .imeModeCHT, type: .thePhrases).path, mode.langModel.loadUserPhrasesData(
filterPath: nil path: userDictDataURL(mode: mode, type: .thePhrases).path,
) filterPath: nil
Self.lmCHS.loadUserPhrasesData( )
path: userDictDataURL(mode: .imeModeCHS, type: .thePhrases).path, case .theFilter:
filterPath: nil DispatchQueue.main.async {
) Self.reloadUserFilterDirectly(mode: mode)
case .theFilter: }
DispatchQueue.main.async { case .theReplacements:
Self.reloadUserFilterDirectly(mode: IMEApp.currentInputMode) 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() { public static func loadUserAssociatesData() {
Self.lmCHT.loadUserAssociatesData( Shared.InputMode.validCases.forEach { mode in
path: Self.userDictDataURL(mode: .imeModeCHT, type: .theAssociates).path mode.langModel.loadUserAssociatesData(
) path: Self.userDictDataURL(mode: mode, type: .theAssociates).path
Self.lmCHS.loadUserAssociatesData( )
path: Self.userDictDataURL(mode: .imeModeCHS, type: .theAssociates).path }
)
} }
public static func loadUserPhraseReplacement() { public static func loadUserPhraseReplacement() {
Self.lmCHT.loadReplacementsData( Shared.InputMode.validCases.forEach { mode in
path: Self.userDictDataURL(mode: .imeModeCHT, type: .theReplacements).path mode.langModel.loadReplacementsData(
) path: Self.userDictDataURL(mode: mode, type: .theReplacements).path
Self.lmCHS.loadReplacementsData( )
path: Self.userDictDataURL(mode: .imeModeCHS, type: .theReplacements).path }
)
} }
public static func loadSCPCSequencesData() { public static func loadSCPCSequencesData() {
Self.lmCHT.loadSCPCSequencesData() Shared.InputMode.validCases.forEach { mode in
Self.lmCHS.loadSCPCSequencesData() mode.langModel.loadSCPCSequencesData()
}
} }
public static func reloadUserFilterDirectly(mode: Shared.InputMode) { 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( public static func checkIfPhrasePairExists(
@ -169,7 +147,7 @@ public class LMMgr {
keyArray: [String], keyArray: [String],
factoryDictionaryOnly: Bool = false factoryDictionaryOnly: Bool = false
) -> Bool { ) -> Bool {
Self.getLM(mode: mode).hasKeyValuePairFor( mode.langModel.hasKeyValuePairFor(
keyArray: keyArray, value: userPhrase, factoryDictionaryOnly: factoryDictionaryOnly keyArray: keyArray, value: userPhrase, factoryDictionaryOnly: factoryDictionaryOnly
) )
} }
@ -179,7 +157,7 @@ public class LMMgr {
mode: Shared.InputMode, mode: Shared.InputMode,
keyArray: [String] keyArray: [String]
) -> Bool { ) -> 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( public static func countPhrasePairs(
@ -187,39 +165,22 @@ public class LMMgr {
mode: Shared.InputMode, mode: Shared.InputMode,
factoryDictionaryOnly: Bool = false factoryDictionaryOnly: Bool = false
) -> Int { ) -> Int {
Self.getLM(mode: mode).countKeyValuePairs( mode.langModel.countKeyValuePairs(
keyArray: keyArray, factoryDictionaryOnly: factoryDictionaryOnly keyArray: keyArray, factoryDictionaryOnly: factoryDictionaryOnly
) )
} }
public static func setPhraseReplacementEnabled(_ state: Bool) { public static func syncLMPrefs() {
Self.lmCHT.isPhraseReplacementEnabled = state Shared.InputMode.validCases.forEach { mode in
Self.lmCHS.isPhraseReplacementEnabled = state mode.langModel.setOptions { config in
} config.isPhraseReplacementEnabled = PrefMgr.shared.phraseReplacementEnabled
config.isCNSEnabled = PrefMgr.shared.cns11643Enabled
public static func setCNSEnabled(_ state: Bool) { config.isSymbolEnabled = PrefMgr.shared.symbolInputEnabled
Self.lmCHT.isCNSEnabled = state config.isSCPCEnabled = PrefMgr.shared.useSCPCTypingMode
Self.lmCHS.isCNSEnabled = state config.isCassetteEnabled = PrefMgr.shared.cassetteEnabled
} config.deltaOfCalendarYears = PrefMgr.shared.deltaOfCalendarYears
}
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
} }
// MARK: UOM // MARK: UOM
@ -227,26 +188,23 @@ public class LMMgr {
public static func saveUserOverrideModelData() { public static func saveUserOverrideModelData() {
let globalQueue = DispatchQueue(label: "vChewingLM_UOM", qos: .unspecified, attributes: .concurrent) let globalQueue = DispatchQueue(label: "vChewingLM_UOM", qos: .unspecified, attributes: .concurrent)
let group = DispatchGroup() let group = DispatchGroup()
group.enter() Shared.InputMode.validCases.forEach { mode in
globalQueue.async { group.enter()
Self.uomCHT.saveData(toURL: userOverrideModelDataURL(.imeModeCHT)) globalQueue.async {
group.leave() mode.uom.saveData(toURL: userOverrideModelDataURL(mode))
} group.leave()
group.enter() }
globalQueue.async {
Self.uomCHS.saveData(toURL: userOverrideModelDataURL(.imeModeCHS))
group.leave()
} }
_ = group.wait(timeout: .distantFuture) _ = group.wait(timeout: .distantFuture)
group.notify(queue: DispatchQueue.main) {} group.notify(queue: DispatchQueue.main) {}
} }
public static func bleachSpecifiedSuggestions(targets: [String], mode: Shared.InputMode) { 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) { 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() { public static func relocateWreckedUOMData() {
@ -268,6 +226,6 @@ public class LMMgr {
} }
public static func clearUserOverrideModelData(_ mode: Shared.InputMode = .imeModeNULL) { public static func clearUserOverrideModelData(_ mode: Shared.InputMode = .imeModeNULL) {
Self.getUOM(mode: mode).clearData(withURL: userOverrideModelDataURL(mode)) mode.uom.clearData(withURL: userOverrideModelDataURL(mode))
} }
} }

View File

@ -62,7 +62,7 @@ public extension LMMgr {
} }
public var isAlreadyFiltered: Bool { 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 { public func write(toFilter: Bool) -> Bool {

View File

@ -230,21 +230,21 @@ import SwiftExtension
@AppProperty(key: UserDef.kCNS11643Enabled.rawValue, defaultValue: false) @AppProperty(key: UserDef.kCNS11643Enabled.rawValue, defaultValue: false)
public dynamic var cns11643Enabled: Bool { public dynamic var cns11643Enabled: Bool {
didSet { didSet {
LMMgr.setCNSEnabled(cns11643Enabled) // LMMgr.syncLMPrefs()
} }
} }
@AppProperty(key: UserDef.kSymbolInputEnabled.rawValue, defaultValue: true) @AppProperty(key: UserDef.kSymbolInputEnabled.rawValue, defaultValue: true)
public dynamic var symbolInputEnabled: Bool { public dynamic var symbolInputEnabled: Bool {
didSet { didSet {
LMMgr.setSymbolEnabled(symbolInputEnabled) // LMMgr.syncLMPrefs()
} }
} }
@AppProperty(key: UserDef.kCassetteEnabled.rawValue, defaultValue: false) @AppProperty(key: UserDef.kCassetteEnabled.rawValue, defaultValue: false)
public dynamic var cassetteEnabled: Bool { public dynamic var cassetteEnabled: Bool {
didSet { didSet {
LMMgr.setCassetteEnabled(cassetteEnabled) // LMMgr.syncLMPrefs()
} }
} }
@ -325,7 +325,7 @@ import SwiftExtension
willSet { willSet {
if newValue { if newValue {
LMMgr.loadSCPCSequencesData() LMMgr.loadSCPCSequencesData()
LMMgr.setSCPCEnabled(true) LMMgr.syncLMPrefs()
} }
} }
} }
@ -333,7 +333,7 @@ import SwiftExtension
@AppProperty(key: UserDef.kPhraseReplacementEnabled.rawValue, defaultValue: false) @AppProperty(key: UserDef.kPhraseReplacementEnabled.rawValue, defaultValue: false)
public dynamic var phraseReplacementEnabled: Bool { public dynamic var phraseReplacementEnabled: Bool {
willSet { willSet {
LMMgr.setPhraseReplacementEnabled(newValue) LMMgr.syncLMPrefs()
if newValue { if newValue {
LMMgr.loadUserPhraseReplacement() LMMgr.loadUserPhraseReplacement()
} }

View File

@ -177,8 +177,8 @@ public class SessionCtl: IMKInputController {
resetInputHandler() resetInputHandler()
// ---------------------------- // ----------------------------
/// ///
inputHandler?.currentLM = LMMgr.currentLM // inputHandler?.currentLM = inputMode.langModel //
inputHandler?.currentUOM = LMMgr.currentUOM inputHandler?.currentUOM = inputMode.uom
/// ///
inputHandler?.ensureKeyboardParser() inputHandler?.ensureKeyboardParser()
/// ///
@ -215,7 +215,7 @@ public class SessionCtl: IMKInputController {
Self.current?.hidePalettes() Self.current?.hidePalettes()
Self.current = self Self.current = self
self.inputHandler = InputHandler( 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.inputHandler?.delegate = self
self.syncBaseLMPrefs() self.syncBaseLMPrefs()
@ -314,7 +314,7 @@ public extension SessionCtl {
// setValue() IMK activateServer() setValue() // setValue() IMK activateServer() setValue()
self.inputHandler = InputHandler( 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.inputHandler?.delegate = self
self.syncBaseLMPrefs() self.syncBaseLMPrefs()
@ -408,12 +408,7 @@ public extension SessionCtl {
/// ///
func syncBaseLMPrefs() { func syncBaseLMPrefs() {
LMMgr.currentLM.isPhraseReplacementEnabled = PrefMgr.shared.phraseReplacementEnabled LMMgr.syncLMPrefs()
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
} }
} }

View File

@ -63,7 +63,7 @@ extension SessionCtl: InputHandlerDelegate {
// //
// //
LMMgr.currentLM.insertTemporaryData( inputMode.langModel.insertTemporaryData(
keyArray: userPhrase.keyArray, keyArray: userPhrase.keyArray,
unigram: .init(value: userPhrase.value, score: userPhrase.weight ?? 0), unigram: .init(value: userPhrase.value, score: userPhrase.weight ?? 0),
isFiltering: addToFilter isFiltering: addToFilter
@ -119,7 +119,7 @@ extension SessionCtl: CtlCandidateDelegate {
if state.type == .ofAssociates { if state.type == .ofAssociates {
return shortened ? "" : NSLocalizedString("Hold ⇧ to choose associates.", comment: "") return shortened ? "" : NSLocalizedString("Hold ⇧ to choose associates.", comment: "")
} else if state.type == .ofInputting, state.isCandidateContainer { } else if state.type == .ofInputting, state.isCandidateContainer {
let useShift = LMMgr.currentLM.areCassetteCandidateKeysShiftHeld let useShift = inputMode.langModel.areCassetteCandidateKeysShiftHeld
let theEmoji = useShift ? "⬆️" : "⚡️" let theEmoji = useShift ? "⬆️" : "⚡️"
return shortened ? theEmoji : "\(theEmoji) " + NSLocalizedString("Quick Candidates", comment: "") return shortened ? theEmoji : "\(theEmoji) " + NSLocalizedString("Quick Candidates", comment: "")
} else if PrefMgr.shared.cassetteEnabled { } else if PrefMgr.shared.cassetteEnabled {
@ -141,14 +141,14 @@ extension SessionCtl: CtlCandidateDelegate {
if value.isEmpty { return blankResult } // 西 if value.isEmpty { return blankResult } // 西
if value.contains("_") { return blankResult } if value.contains("_") { return blankResult }
// LMInstantiator // LMInstantiator
return LMMgr.currentLM.cassetteReverseLookup(for: value) return inputMode.langModel.cassetteReverseLookup(for: value)
} }
public var selectionKeys: String { public var selectionKeys: String {
// `%quick` 使 1234567890 // `%quick` 使 1234567890
cassetteQuick: if state.type == .ofInputting, state.isCandidateContainer { cassetteQuick: if state.type == .ofInputting, state.isCandidateContainer {
guard PrefMgr.shared.cassetteEnabled else { break cassetteQuick } guard PrefMgr.shared.cassetteEnabled else { break cassetteQuick }
guard let cinCandidateKey = LMMgr.currentLM.cassetteSelectionKey, guard let cinCandidateKey = inputMode.langModel.cassetteSelectionKey,
CandidateKey.validate(keys: cinCandidateKey) == nil CandidateKey.validate(keys: cinCandidateKey) == nil
else { else {
return "1234567890" return "1234567890"
@ -267,7 +267,7 @@ extension SessionCtl: CtlCandidateDelegate {
// //
// //
LMMgr.currentLM.insertTemporaryData( inputMode.langModel.insertTemporaryData(
keyArray: userPhrase.keyArray, keyArray: userPhrase.keyArray,
unigram: .init(value: userPhrase.value, score: userPhrase.weight ?? 0), unigram: .init(value: userPhrase.value, score: userPhrase.weight ?? 0),
isFiltering: action == .toFilter isFiltering: action == .toFilter

View File

@ -140,7 +140,7 @@ public struct VwrSettingsPaneCassette: View {
} else { } else {
LMMgr.loadCassetteData() LMMgr.loadCassetteData()
} }
LMMgr.setCassetteEnabled(cassetteEnabled) LMMgr.syncLMPrefs()
} }
) )
} }

View File

@ -177,13 +177,13 @@ public struct VwrSettingsPaneDictionary: View {
Toggle( Toggle(
LocalizedStringKey("Enable CNS11643 Support (2023-11-06)"), LocalizedStringKey("Enable CNS11643 Support (2023-11-06)"),
isOn: $cns11643Enabled.onChange { isOn: $cns11643Enabled.onChange {
LMMgr.setCNSEnabled(cns11643Enabled) LMMgr.syncLMPrefs()
} }
) )
Toggle( Toggle(
LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"), LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"),
isOn: $symbolInputEnabled.onChange { isOn: $symbolInputEnabled.onChange {
LMMgr.setSymbolEnabled(symbolInputEnabled) LMMgr.syncLMPrefs()
} }
) )
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -200,7 +200,7 @@ public struct VwrSettingsPaneDictionary: View {
Toggle( Toggle(
LocalizedStringKey("Enable phrase replacement table"), LocalizedStringKey("Enable phrase replacement table"),
isOn: $phraseReplacementEnabled.onChange { isOn: $phraseReplacementEnabled.onChange {
LMMgr.setPhraseReplacementEnabled(phraseReplacementEnabled) LMMgr.syncLMPrefs()
if phraseReplacementEnabled { if phraseReplacementEnabled {
LMMgr.loadUserPhraseReplacement() LMMgr.loadUserPhraseReplacement()
} }

View File

@ -226,6 +226,10 @@ public enum Shared {
} }
} }
public static var validCases: [InputMode] {
[.imeModeCHS, .imeModeCHT]
}
public var localizedDescription: String { NSLocalizedString(description, comment: "") } public var localizedDescription: String { NSLocalizedString(description, comment: "") }
public var description: String { public var description: String {
switch self { switch self {

View File

@ -272,7 +272,7 @@ public extension SessionCtl {
? "NotificationSwitchON".localized ? "NotificationSwitchON".localized
: "NotificationSwitchOFF".localized) : "NotificationSwitchOFF".localized)
) )
if !LMMgr.currentLM.isCassetteDataLoaded { if !inputMode.langModel.isCassetteDataLoaded {
LMMgr.loadCassetteData() LMMgr.loadCassetteData()
} }
} }

View File

@ -233,11 +233,11 @@ class CtlPrefWindow: NSWindowController, NSWindowDelegate {
// CNS // CNS
// //
@IBAction func toggleCNSSupport(_: Any) { @IBAction func toggleCNSSupport(_: Any) {
LMMgr.setCNSEnabled(PrefMgr.shared.cns11643Enabled) LMMgr.syncLMPrefs()
} }
@IBAction func toggleSymbolInputEnabled(_: Any) { @IBAction func toggleSymbolInputEnabled(_: Any) {
LMMgr.setSymbolEnabled(PrefMgr.shared.symbolInputEnabled) LMMgr.syncLMPrefs()
} }
@IBAction func toggleTrad2KangXiAction(_: Any) { @IBAction func toggleTrad2KangXiAction(_: Any) {