diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift index 6dfcebb0..b0b4e281 100644 --- a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift +++ b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift @@ -229,14 +229,15 @@ extension vChewingLM { /// 插入臨時資料。 /// - Parameters: - /// - key: 索引鍵。 + /// - key: 索引鍵陣列。 /// - unigram: 要插入的單元圖。 /// - isFiltering: 是否有在過濾內容。 - public func insertTemporaryData(key: String, unigram: Megrez.Unigram, isFiltering: Bool) { + public func insertTemporaryData(keyArray: [String], unigram: Megrez.Unigram, isFiltering: Bool) { + let keyChain = keyArray.joined(separator: "-") _ = isFiltering - ? lmFiltered.temporaryMap[key, default: []].append(unigram) - : lmUserPhrases.temporaryMap[key, default: []].append(unigram) + ? lmFiltered.temporaryMap[keyChain, default: []].append(unigram) + : lmUserPhrases.temporaryMap[keyChain, default: []].append(unigram) } /// 自當前記憶體取得指定使用者子語言模組內的原始資料體。 @@ -279,60 +280,62 @@ extension vChewingLM { } /// 根據給定的索引鍵來確認各個資料庫陣列內是否存在對應的資料。 - /// - Parameter key: 索引鍵。 + /// - Parameter key: 索引鍵陣列。 /// - Returns: 是否在庫。 - public func hasUnigramsFor(key: String) -> Bool { - key == " " || (!unigramsFor(key: key).isEmpty && !key.isEmpty) + public func hasUnigramsFor(keyArray: [String]) -> Bool { + let keyChain = keyArray.joined(separator: "-") + return keyChain == " " || (!unigramsFor(keyArray: keyArray).isEmpty && !keyChain.isEmpty) } /// 根據給定的索引鍵和資料值,確認是否有該具體的資料值在庫。 /// - Parameters: - /// - key: 索引鍵。 + /// - key: 索引鍵陣列。 /// - value: 資料值。 /// - Returns: 是否在庫。 - public func hasKeyValuePairFor(key: String, value: String) -> Bool { - unigramsFor(key: key).map(\.value).contains(value) + public func hasKeyValuePairFor(key: [String], value: String) -> Bool { + unigramsFor(keyArray: key).map(\.value).contains(value) } /// 給定讀音字串,讓 LMI 給出對應的經過處理的單元圖陣列。 /// - Parameter key: 給定的讀音字串。 /// - Returns: 對應的經過處理的單元圖陣列。 - public func unigramsFor(key: String) -> [Megrez.Unigram] { - guard !key.isEmpty else { return [] } + public func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] { + let keyChain = keyArray.joined(separator: "-") + guard !keyChain.isEmpty else { return [] } /// 給空格鍵指定輸出值。 - if key == " " { return [.init(value: " ")] } + if keyChain == " " { return [.init(value: " ")] } /// 準備不同的語言模組容器,開始逐漸往容器陣列內塞入資料。 var rawAllUnigrams: [Megrez.Unigram] = [] - if isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: key) } + if isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) } // 如果有檢測到使用者自訂逐字選字語料庫內的相關資料的話,在這裡先插入。 if isSCPCEnabled { - rawAllUnigrams += lmPlainBopomofo.valuesFor(key: key).map { Megrez.Unigram(value: $0, score: 0) } + rawAllUnigrams += lmPlainBopomofo.valuesFor(key: keyChain).map { Megrez.Unigram(value: $0, score: 0) } } // 用 reversed 指令讓使用者語彙檔案內的詞條優先順序隨著行數增加而逐漸增高。 // 這樣一來就可以在就地新增語彙時徹底複寫優先權。 // 將兩句差分也是為了讓 rawUserUnigrams 的類型不受可能的影響。 - rawAllUnigrams += lmUserPhrases.unigramsFor(key: key).reversed() + rawAllUnigrams += lmUserPhrases.unigramsFor(key: keyChain).reversed() - if !isCassetteEnabled || isCassetteEnabled && key.charComponents[0] == "_" { + if !isCassetteEnabled || isCassetteEnabled && keyChain.charComponents[0] == "_" { // LMMisc 與 LMCore 的 score 在 (-10.0, 0.0) 這個區間內。 - rawAllUnigrams += lmMisc.unigramsFor(key: key) - rawAllUnigrams += lmCore.unigramsFor(key: key) - if isCNSEnabled { rawAllUnigrams += Self.lmCNS.unigramsFor(key: key) } + rawAllUnigrams += lmMisc.unigramsFor(key: keyChain) + rawAllUnigrams += lmCore.unigramsFor(key: keyChain) + if isCNSEnabled { rawAllUnigrams += Self.lmCNS.unigramsFor(key: keyChain) } } if isSymbolEnabled { - rawAllUnigrams += lmUserSymbols.unigramsFor(key: key) + rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain) if !isCassetteEnabled { - rawAllUnigrams += Self.lmSymbols.unigramsFor(key: key) + rawAllUnigrams += Self.lmSymbols.unigramsFor(key: keyChain) } } // 新增與日期、時間、星期有關的單元圖資料 - rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: key)) + rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: keyChain)) // 提前處理語彙置換 if isPhraseReplacementEnabled { @@ -344,7 +347,7 @@ extension vChewingLM { } // 讓單元圖陣列自我過濾。在此基礎之上,對於相同詞值的多個單元圖,僅保留權重最大者。 - rawAllUnigrams.consolidate(filter: .init(lmFiltered.unigramsFor(key: key).map(\.value))) + rawAllUnigrams.consolidate(filter: .init(lmFiltered.unigramsFor(key: keyChain).map(\.value))) return rawAllUnigrams } } diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmUserOverride.swift b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmUserOverride.swift index 414f36b2..7fbe8416 100644 --- a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmUserOverride.swift +++ b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmUserOverride.swift @@ -69,7 +69,7 @@ extension vChewingLM { var headIndex = 0 guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() } let key = vChewingLM.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex) - return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.key) + return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey()) } } } @@ -329,15 +329,15 @@ extension vChewingLM.LMUserOverride { arrNodes = Array(arrNodes.reversed()) let kvCurrent = arrNodes[0].currentPair - guard !kvCurrent.key.contains("_") else { + guard !kvCurrent.joinedKey().contains("_") else { return "" } // 字音數與字數不一致的內容會被拋棄。 - if kvCurrent.key.split(separator: "-").count != kvCurrent.value.count { return "" } + if kvCurrent.keyArray.count != kvCurrent.value.count { return "" } // 前置單元只記錄讀音,在其後的單元則同時記錄讀音與字詞 - let strCurrent = kvCurrent.key + let strCurrent = kvCurrent.joinedKey() var kvPrevious = Megrez.Compositor.KeyValuePaired() var kvAnterior = Megrez.Compositor.KeyValuePaired() var readingStack = "" @@ -354,19 +354,19 @@ extension vChewingLM.LMUserOverride { } if arrNodes.count >= 2, - !kvPrevious.key.contains("_"), - kvPrevious.key.split(separator: "-").count == kvPrevious.value.count + !kvPrevious.joinedKey().contains("_"), + kvPrevious.joinedKey().split(separator: "-").count == kvPrevious.value.count { kvPrevious = arrNodes[1].currentPair - readingStack = kvPrevious.key + readingStack + readingStack = kvPrevious.joinedKey() + readingStack } if arrNodes.count >= 3, - !kvAnterior.key.contains("_"), - kvAnterior.key.split(separator: "-").count == kvAnterior.value.count + !kvAnterior.joinedKey().contains("_"), + kvAnterior.joinedKey().split(separator: "-").count == kvAnterior.value.count { kvAnterior = arrNodes[2].currentPair - readingStack = kvAnterior.key + readingStack + readingStack = kvAnterior.joinedKey() + readingStack } return result diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift index 5ad0602b..d891a44e 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift @@ -9,7 +9,7 @@ import Cocoa public protocol CtlCandidateDelegate { - func candidatePairs(conv: Bool) -> [(String, String)] + func candidatePairs(conv: Bool) -> [([String], String)] func candidatePairSelected(at index: Int) func candidatePairRightClicked(at index: Int, action: CandidateContextMenuAction) func candidates(_ sender: Any!) -> [Any]! diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift index 5825b639..f2f9a3f8 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift @@ -15,7 +15,7 @@ public protocol IMEStateProtocol { var isASCIIMode: Bool { get set } var isVerticalTyping: Bool { get set } var isVerticalCandidateWindow: Bool { get set } - var candidates: [(String, String)] { get set } + var candidates: [([String], String)] { get set } var hasComposition: Bool { get } var isCandidateContainer: Bool { get } var displayedText: String { get } @@ -49,7 +49,7 @@ public protocol IMEStateDataProtocol { var isFilterable: Bool { get } var isVerticalTyping: Bool { get set } var isMarkedLengthValid: Bool { get } - var candidates: [(String, String)] { get set } + var candidates: [([String], String)] { get set } var displayedText: String { get set } var displayedTextConverted: String { get } var tooltipBackupForInputting: String { get set } diff --git a/Source/Modules/IMEState.swift b/Source/Modules/IMEState.swift index f9cbfa51..eb58664c 100644 --- a/Source/Modules/IMEState.swift +++ b/Source/Modules/IMEState.swift @@ -84,7 +84,7 @@ public struct IMEState: IMEStateProtocol { self.data = data self.type = type self.node = node - self.data.candidates = node.members.map { ("", $0.name) } + self.data.candidates = node.members.map { ([""], $0.name) } } } @@ -107,7 +107,7 @@ extension IMEState { return result } - public static func ofAssociates(candidates: [(String, String)]) -> IMEState { + public static func ofAssociates(candidates: [([String], String)]) -> IMEState { var result = IMEState(type: .ofAssociates) result.candidates = candidates return result @@ -132,7 +132,7 @@ extension IMEState { return result } - public static func ofCandidates(candidates: [(String, String)], displayTextSegments: [String], cursor: Int) + public static func ofCandidates(candidates: [([String], String)], displayTextSegments: [String], cursor: Int) -> IMEState { var result = IMEState(displayTextSegments: displayTextSegments, cursor: cursor) @@ -178,7 +178,7 @@ extension IMEState { return result } - public var candidates: [(String, String)] { + public var candidates: [([String], String)] { get { data.candidates } set { data.candidates = newValue } } diff --git a/Source/Modules/IMEStateData.swift b/Source/Modules/IMEStateData.swift index 31a85948..c19f3f95 100644 --- a/Source/Modules/IMEStateData.swift +++ b/Source/Modules/IMEStateData.swift @@ -91,7 +91,7 @@ public struct IMEStateData: IMEStateDataProtocol { public var reading: String = "" public var markedReadings = [String]() - public var candidates = [(String, String)]() + public var candidates = [([String], String)]() public var textToCommit: String = "" public var tooltip: String = "" public var tooltipDuration: Double = 1.0 @@ -172,7 +172,7 @@ public struct IMEStateData: IMEStateDataProtocol { extension IMEStateData { public var doesUserPhraseExist: Bool { let text = displayedText.charComponents[markedRange].joined() - let joined = markedReadings.joined(separator: "-") + let joined = markedReadings.joined(separator: InputHandler.keySeparator) return LMMgr.checkIfUserPhraseExist( userPhrase: text, mode: IMEApp.currentInputMode, key: joined ) @@ -205,7 +205,7 @@ extension IMEStateData { } public var userPhraseKVPair: (String, String) { - let key = markedReadings.joined(separator: "-") + let key = markedReadings.joined(separator: InputHandler.keySeparator) let value = displayedText.charComponents[markedRange].joined() return (key, value) } diff --git a/Source/Modules/InputHandler_Core.swift b/Source/Modules/InputHandler_Core.swift index 90cb7f1d..9c1fe12d 100644 --- a/Source/Modules/InputHandler_Core.swift +++ b/Source/Modules/InputHandler_Core.swift @@ -22,6 +22,8 @@ public protocol InputHandlerProtocol { var currentUOM: vChewingLM.LMUserOverride { get set } var delegate: InputHandlerDelegate? { get set } var composer: Tekkon.Composer { get set } + var keySeparator: String { get } + static var keySeparator: String { get } var isCompositorEmpty: Bool { get } var isComposerUsingPinyin: Bool { get } func clear() @@ -31,7 +33,7 @@ public protocol InputHandlerProtocol { func generateStateOfCandidates() -> IMEStateProtocol func generateStateOfInputting(sansReading: Bool) -> IMEStateProtocol func generateStateOfAssociates(withPair pair: Megrez.Compositor.KeyValuePaired) -> IMEStateProtocol - func consolidateNode(candidate: (String, String), respectCursorPushing: Bool, preConsolidate: Bool) + func consolidateNode(candidate: ([String], String), respectCursorPushing: Bool, preConsolidate: Bool) func updateUnigramData() -> Bool } @@ -149,10 +151,10 @@ public class InputHandler: InputHandlerProtocol { /// - Parameter key: 給定的聯想詞的開頭字。 /// - Returns: 抓取到的聯想詞陣列。 /// 不會是 nil,但那些負責接收結果的函式會對空白陣列結果做出正確的處理。 - func generateArrayOfAssociates(withPair pair: Megrez.Compositor.KeyValuePaired) -> [(String, String)] { - var arrResult: [(String, String)] = [] + func generateArrayOfAssociates(withPair pair: Megrez.Compositor.KeyValuePaired) -> [([String], String)] { + var arrResult: [([String], String)] = [] if currentLM.hasAssociatedPhrasesFor(pair: pair) { - arrResult = currentLM.associatedPhrasesFor(pair: pair).map { ("", $0) } + arrResult = currentLM.associatedPhrasesFor(pair: pair).map { ([""], $0) } } return arrResult } @@ -223,7 +225,7 @@ public class InputHandler: InputHandlerProtocol { for (subPosition, key) in currentNode.keyArray.enumerated() { guard values.count > subPosition else { break } // 防呆,應該沒有發生的可能性 let thePair = Megrez.Compositor.KeyValuePaired( - key: key, value: values[subPosition] + keyArray: [key], value: values[subPosition] ) compositor.overrideCandidate(thePair, at: position) position += 1 @@ -241,9 +243,9 @@ public class InputHandler: InputHandlerProtocol { /// - respectCursorPushing: 若該選項為 true,則會在選字之後始終將游標推送至選字後的節錨的前方。 /// - consolidate: 在固化節點之前,先鞏固上下文。該選項可能會破壞在內文組字區內就地輪替候選字詞時的體驗。 public func consolidateNode( - candidate: (String, String), respectCursorPushing: Bool = true, preConsolidate: Bool = false + candidate: ([String], String), respectCursorPushing: Bool = true, preConsolidate: Bool = false ) { - let theCandidate: Megrez.Compositor.KeyValuePaired = .init(key: candidate.0, value: candidate.1) + let theCandidate: Megrez.Compositor.KeyValuePaired = .init(keyArray: candidate.0, value: candidate.1) /// 必須先鞏固當前組字器游標上下文、以消滅意料之外的影響,但在內文組字區內就地輪替候選字詞時除外。 if preConsolidate { consolidateCursorContext(with: theCandidate) } @@ -284,7 +286,7 @@ public class InputHandler: InputHandlerProtocol { } /// 獲取候選字詞(包含讀音)陣列資料內容。 - func generateArrayOfCandidates(fixOrder: Bool = true) -> [(String, String)] { + func generateArrayOfCandidates(fixOrder: Bool = true) -> [([String], String)] { /// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。 /// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的。 var arrCandidates: [Megrez.Compositor.KeyValuePaired] = { @@ -301,7 +303,7 @@ public class InputHandler: InputHandlerProtocol { // 決定是否根據半衰記憶模組的建議來調整候選字詞的順序。 if !prefs.fetchSuggestionsFromUserOverrideModel || prefs.useSCPCTypingMode || fixOrder { - return arrCandidates.map { ($0.key, $0.value) } + return arrCandidates.map { ($0.keyArray, $0.value) } } let arrSuggestedUnigrams: [(String, Megrez.Unigram)] = retrieveUOMSuggestions(apply: false) @@ -310,8 +312,8 @@ public class InputHandler: InputHandlerProtocol { } arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates arrCandidates = arrCandidates.deduplicated - arrCandidates = arrCandidates.stableSort { $0.key.split(separator: "-").count > $1.key.split(separator: "-").count } - return arrCandidates.map { ($0.key, $0.value) } + arrCandidates = arrCandidates.stableSort { $0.keyArray.count > $1.keyArray.count } + return arrCandidates.map { ($0.keyArray, $0.value) } } /// 向半衰引擎詢問可能的選字建議、且套用給組字器內的當前游標位置。 @@ -437,6 +439,10 @@ public class InputHandler: InputHandlerProtocol { // MARK: - Extracted methods and functions (Megrez). + public var keySeparator: String { compositor.separator } + + public static var keySeparator: String { Megrez.Compositor.theSeparator } + /// 就地增刪詞之後,需要就地更新游標上下文單元圖資料。 public func updateUnigramData() -> Bool { let result = compositor.update(updateExisting: true) diff --git a/Source/Modules/InputHandler_HandleCandidate.swift b/Source/Modules/InputHandler_HandleCandidate.swift index c0fe4b67..bbcce410 100644 --- a/Source/Modules/InputHandler_HandleCandidate.swift +++ b/Source/Modules/InputHandler_HandleCandidate.swift @@ -173,12 +173,12 @@ extension InputHandler { ? currentLM.isThisCassetteKeyAllowed(key: input.text) : composer.inputValidityCheck(key: input.charCode) var shouldAutoSelectCandidate: Bool = - isInputValid || currentLM.hasUnigramsFor(key: customPunctuation) - || currentLM.hasUnigramsFor(key: punctuation) + isInputValid || currentLM.hasUnigramsFor(keyArray: [customPunctuation]) + || currentLM.hasUnigramsFor(keyArray: [punctuation]) if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey { let letter = "_letter_\(input.text)" - if currentLM.hasUnigramsFor(key: letter) { shouldAutoSelectCandidate = true } + if currentLM.hasUnigramsFor(keyArray: [letter]) { shouldAutoSelectCandidate = true } } if shouldAutoSelectCandidate { diff --git a/Source/Modules/InputHandler_HandleComposition.swift b/Source/Modules/InputHandler_HandleComposition.swift index d5322ded..59eb926b 100644 --- a/Source/Modules/InputHandler_HandleComposition.swift +++ b/Source/Modules/InputHandler_HandleComposition.swift @@ -53,7 +53,7 @@ extension InputHandler { theComposer.intonation.clear() // 檢查新的漢字字音是否在庫。 let temporaryReadingKey = theComposer.getComposition() - if currentLM.hasUnigramsFor(key: temporaryReadingKey) { + if currentLM.hasUnigramsFor(keyArray: [temporaryReadingKey]) { compositor.dropKey(direction: .rear) walk() // 這裡必須 Walk 一次、來更新目前被 walk 的內容。 composer = theComposer @@ -92,7 +92,7 @@ extension InputHandler { return handleCtrlCommandEnter() } // 向語言模型詢問是否有對應的記錄。 - if !currentLM.hasUnigramsFor(key: readingKey) { + if !currentLM.hasUnigramsFor(keyArray: [readingKey]) { delegate.callError("B49C0979:語彙庫內無「\(readingKey)」的匹配記錄。") if prefs.keepReadingUponCompositionError { @@ -140,14 +140,14 @@ extension InputHandler { case 2...: delegate.switchState(candidateState) case 1: let firstCandidate = candidateState.candidates.first! // 一定會有,所以強制拆包也無妨。 - let reading: String = firstCandidate.0 + let reading: String = firstCandidate.0.joined(separator: compositor.separator) let text: String = firstCandidate.1 delegate.switchState(IMEState.ofCommitting(textToCommit: text)) if !prefs.associatedPhrasesEnabled { delegate.switchState(IMEState.ofEmpty()) } else { - let associatedPhrases = generateStateOfAssociates(withPair: .init(key: reading, value: text)) + let associatedPhrases = generateStateOfAssociates(withPair: .init(keyArray: [reading], value: text)) delegate.switchState(associatedPhrases.candidates.isEmpty ? IMEState.ofEmpty() : associatedPhrases) } default: break @@ -240,7 +240,7 @@ extension InputHandler { return handleCtrlCommandEnter() } // 向語言模型詢問是否有對應的記錄。 - if !currentLM.hasUnigramsFor(key: calligrapher) { + if !currentLM.hasUnigramsFor(keyArray: [calligrapher]) { delegate.callError("B49C0979_Cassette:語彙庫內無「\(calligrapher)」的匹配記錄。") calligrapher.removeAll() @@ -282,14 +282,14 @@ extension InputHandler { case 2...: delegate.switchState(candidateState) case 1: let firstCandidate = candidateState.candidates.first! // 一定會有,所以強制拆包也無妨。 - let reading: String = firstCandidate.0 + let reading: String = firstCandidate.0.joined(separator: compositor.separator) let text: String = firstCandidate.1 delegate.switchState(IMEState.ofCommitting(textToCommit: text)) if !prefs.associatedPhrasesEnabled { delegate.switchState(IMEState.ofEmpty()) } else { - let associatedPhrases = generateStateOfAssociates(withPair: .init(key: reading, value: text)) + let associatedPhrases = generateStateOfAssociates(withPair: .init(keyArray: [reading], value: text)) delegate.switchState(associatedPhrases.candidates.isEmpty ? IMEState.ofEmpty() : associatedPhrases) } default: break diff --git a/Source/Modules/InputHandler_HandleInput.swift b/Source/Modules/InputHandler_HandleInput.swift index 0f5d4d04..5dc352cb 100644 --- a/Source/Modules/InputHandler_HandleInput.swift +++ b/Source/Modules/InputHandler_HandleInput.swift @@ -190,7 +190,7 @@ extension InputHandler { if input.isSymbolMenuPhysicalKey, !input.isShiftHold, !input.isControlHold, state.type != .ofDeactivated { if input.isOptionHold { - if currentLM.hasUnigramsFor(key: "_punctuation_list") { + if currentLM.hasUnigramsFor(keyArray: ["_punctuation_list"]) { if isComposerOrCalligrapherEmpty, compositor.insertKey("_punctuation_list") { walk() // 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。 diff --git a/Source/Modules/InputHandler_HandleStates.swift b/Source/Modules/InputHandler_HandleStates.swift index 46269acb..0a471a92 100644 --- a/Source/Modules/InputHandler_HandleStates.swift +++ b/Source/Modules/InputHandler_HandleStates.swift @@ -239,7 +239,7 @@ extension InputHandler { func handlePunctuation(_ customPunctuation: String) -> Bool { guard let delegate = delegate else { return false } - if !currentLM.hasUnigramsFor(key: customPunctuation) { + if !currentLM.hasUnigramsFor(keyArray: [customPunctuation]) { return false } @@ -269,7 +269,7 @@ extension InputHandler { case 2...: delegate.switchState(candidateState) case 1: clear() // 這句不要砍,因為下文可能會回呼 candidateState。 - if let candidateToCommit: (String, String) = candidateState.candidates.first, !candidateToCommit.1.isEmpty { + if let candidateToCommit: ([String], String) = candidateState.candidates.first, !candidateToCommit.1.isEmpty { delegate.switchState(IMEState.ofCommitting(textToCommit: candidateToCommit.1)) } else { delegate.switchState(candidateState) @@ -337,13 +337,13 @@ extension InputHandler { var composed = "" for node in compositor.walkedNodes { - var key = node.key + var key = node.keyArray.joined(separator: "\t") if !prefs.cassetteEnabled { if prefs.inlineDumpPinyinInLieuOfZhuyin { key = Tekkon.restoreToneOneInZhuyinKey(target: key) // 恢復陰平標記 key = Tekkon.cnvPhonaToHanyuPinyin(target: key) // 注音轉拼音 key = Tekkon.cnvHanyuPinyinToTextbookStyle(target: key) // 轉教科書式標調 - key = key.replacingOccurrences(of: "-", with: " ") + key = key.replacingOccurrences(of: "\t", with: " ") } else { key = Tekkon.cnvZhuyinChainToTextbookReading(target: key, newSeparator: " ") } @@ -706,7 +706,7 @@ extension InputHandler { return true } - let currentPaired = (currentNode.key, currentNode.value) + let currentPaired = (currentNode.keyArray, currentNode.value) var currentIndex = 0 if !currentNode.isOverriden { diff --git a/Source/Modules/LMMgr.swift b/Source/Modules/LMMgr.swift index cd13d5f5..8fed9cbc 100644 --- a/Source/Modules/LMMgr.swift +++ b/Source/Modules/LMMgr.swift @@ -279,8 +279,8 @@ public class LMMgr { key unigramKey: String ) -> Bool { switch mode { - case .imeModeCHS: return lmCHS.hasKeyValuePairFor(key: unigramKey, value: userPhrase) - case .imeModeCHT: return lmCHT.hasKeyValuePairFor(key: unigramKey, value: userPhrase) + case .imeModeCHS: return lmCHS.hasKeyValuePairFor(key: [unigramKey], value: userPhrase) + case .imeModeCHT: return lmCHT.hasKeyValuePairFor(key: [unigramKey], value: userPhrase) case .imeModeNULL: return false } } diff --git a/Source/Modules/SessionCtl_Delegates.swift b/Source/Modules/SessionCtl_Delegates.swift index 15c5bc10..7d66545d 100644 --- a/Source/Modules/SessionCtl_Delegates.swift +++ b/Source/Modules/SessionCtl_Delegates.swift @@ -57,7 +57,8 @@ extension SessionCtl: InputHandlerDelegate { // 該臨時資料記錄會在接下來的語言模組資料重載過程中被自動清除。 let temporaryScore: Double = SessionCtl.areWeNerfing ? -114.514 : 0 LMMgr.currentLM.insertTemporaryData( - key: rawPair.0, unigram: .init(value: rawPair.1, score: temporaryScore), isFiltering: SessionCtl.areWeNerfing + keyArray: [rawPair.0], unigram: .init(value: rawPair.1, score: temporaryScore), + isFiltering: SessionCtl.areWeNerfing ) // 開始針對使用者半衰模組的清詞處理 LMMgr.bleachSpecifiedSuggestions(targets: [valueCurrent], mode: IMEApp.currentInputMode) @@ -95,12 +96,12 @@ extension SessionCtl: CtlCandidateDelegate { PrefMgr.shared.useIMKCandidateWindow ? "123456789" : PrefMgr.shared.candidateKeys } - public func candidatePairs(conv: Bool = false) -> [(String, String)] { + public func candidatePairs(conv: Bool = false) -> [([String], String)] { if !state.isCandidateContainer || state.candidates.isEmpty { return [] } - if !conv || PrefMgr.shared.cns11643Enabled || state.candidates[0].0.contains("_punctuation") { + if !conv || PrefMgr.shared.cns11643Enabled || state.candidates[0].0.joined().contains("_punctuation") { return state.candidates } - let convertedCandidates: [(String, String)] = state.candidates.map { theCandidatePair -> (String, String) in + let convertedCandidates: [([String], String)] = state.candidates.map { theCandidatePair -> ([String], String) in let theCandidate = theCandidatePair.1 let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate) let result = (theCandidate == theConverted) ? theCandidate : "\(theConverted)(\(theCandidate))" @@ -139,7 +140,7 @@ extension SessionCtl: CtlCandidateDelegate { // 此時是逐字選字模式,所以「selectedValue.1」是單個字、不用追加處理。 if PrefMgr.shared.associatedPhrasesEnabled { let associates = inputHandler.generateStateOfAssociates( - withPair: .init(key: selectedValue.0, value: selectedValue.1) + withPair: .init(keyArray: selectedValue.0, value: selectedValue.1) ) switchState(associates.candidates.isEmpty ? IMEState.ofEmpty() : associates) } else { @@ -162,7 +163,7 @@ extension SessionCtl: CtlCandidateDelegate { } if PrefMgr.shared.associatedPhrasesEnabled { let associates = inputHandler.generateStateOfAssociates( - withPair: .init(key: selectedValue.0, value: String(valueKept)) + withPair: .init(keyArray: selectedValue.0, value: String(valueKept)) ) if !associates.candidates.isEmpty { switchState(associates) @@ -204,7 +205,7 @@ extension SessionCtl: CtlCandidateDelegate { // 該臨時資料記錄會在接下來的語言模組資料重載過程中被自動清除。 let temporaryScore: Double = (action == .toNerf) ? -114.514 : 0 LMMgr.currentLM.insertTemporaryData( - key: rawPair.0, unigram: .init(value: rawPair.1, score: temporaryScore), isFiltering: action == .toFilter + keyArray: rawPair.0, unigram: .init(value: rawPair.1, score: temporaryScore), isFiltering: action == .toFilter ) // 開始針對使用者半衰模組的清詞處理 diff --git a/Source/Modules/SessionCtl_IMKCandidatesData.swift b/Source/Modules/SessionCtl_IMKCandidatesData.swift index 94a45033..f728dc40 100644 --- a/Source/Modules/SessionCtl_IMKCandidatesData.swift +++ b/Source/Modules/SessionCtl_IMKCandidatesData.swift @@ -21,17 +21,19 @@ extension SessionCtl { var arrResult = [String]() // 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。 - func handleIMKCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") { + func handleIMKCandidatesPrepared(_ candidates: [([String], String)], prefix: String = "") { + guard let separator = inputHandler?.keySeparator else { return } for theCandidate in candidates { let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate.1) var result = (theCandidate.1 == theConverted) ? theCandidate.1 : "\(theConverted)\u{1A}(\(theCandidate.1))" if arrResult.contains(result) { let reading: String = PrefMgr.shared.cassetteEnabled - ? theCandidate.0 + ? theCandidate.0.joined(separator: separator) : (PrefMgr.shared.showHanyuPinyinInCompositionBuffer - ? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: theCandidate.0)) - : theCandidate.0) + ? Tekkon.cnvPhonaToHanyuPinyin( + target: Tekkon.restoreToneOneInZhuyinKey(target: theCandidate.0.joined(separator: separator))) + : theCandidate.0.joined(separator: separator)) result = "\(result)\u{17}(\(reading))" } arrResult.append(prefix + result) @@ -82,15 +84,18 @@ extension SessionCtl { var indexDeducted = 0 // 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。 - func handleIMKCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") { + func handleIMKCandidatesSelected(_ candidates: [([String], String)], prefix: String = "") { + guard let separator = inputHandler?.keySeparator else { return } for (i, neta) in candidates.enumerated() { let theConverted = ChineseConverter.kanjiConversionIfRequired(neta.1) let netaShown = (neta.1 == theConverted) ? neta.1 : "\(theConverted)\u{1A}(\(neta.1))" let reading: String = PrefMgr.shared.cassetteEnabled - ? neta.0 + ? neta.0.joined(separator: separator) : (PrefMgr.shared.showHanyuPinyinInCompositionBuffer - ? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: neta.0)) : neta.0) + ? Tekkon.cnvPhonaToHanyuPinyin( + target: Tekkon.restoreToneOneInZhuyinKey(target: neta.0.joined(separator: separator))) + : neta.0.joined(separator: separator)) let netaShownWithPronunciation = "\(netaShown)\u{17}(\(reading))" if candidateString == prefix + netaShownWithPronunciation { indexDeducted = i @@ -104,7 +109,7 @@ extension SessionCtl { } // 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。 - func handleSymbolCandidatesSelected(_ candidates: [(String, String)]) { + func handleSymbolCandidatesSelected(_ candidates: [([String], String)]) { for (i, neta) in candidates.enumerated() { if candidateString == neta.1 { indexDeducted = i