diff --git a/Source/Modules/ControllerModules/KeyHandler_Core.swift b/Source/Modules/ControllerModules/KeyHandler_Core.swift index 7eed670a..9e26285f 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Core.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Core.swift @@ -120,7 +120,7 @@ public class KeyHandler { /// - Parameter key: 給定的聯想詞的開頭字。 /// - Returns: 抓取到的聯想詞陣列。 /// 不會是 nil,但那些負責接收結果的函式會對空白陣列結果做出正確的處理。 - func buildAssociatePhraseArray(withPair pair: Megrez.Compositor.Candidate) -> [(String, String)] { + func buildAssociatePhraseArray(withPair pair: Megrez.Compositor.KeyValuePaired) -> [(String, String)] { var arrResult: [(String, String)] = [] if currentLM.hasAssociatedPhrasesFor(pair: pair) { arrResult = currentLM.associatedPhrasesFor(pair: pair).map { ("", $0) } @@ -142,7 +142,7 @@ public class KeyHandler { /// 威注音輸入法截至 v1.9.3 SP2 版為止都受到上游的這個 Bug 的影響,且在 v1.9.4 版利用該函式修正了這個缺陷。 /// 該修正必須搭配至少天權星組字引擎 v2.0.2 版方可生效。算法可能比較囉唆,但至少在常用情形下不會再發生該問題。 /// - Parameter theCandidate: 要拿來覆寫的詞音配對。 - func consolidateCursorContext(with theCandidate: Megrez.Compositor.Candidate) { + func consolidateCursorContext(with theCandidate: Megrez.Compositor.KeyValuePaired) { var grid = compositor var frontBoundaryEX = actualCandidateCursor + 1 var rearBoundaryEX = actualCandidateCursor @@ -193,7 +193,7 @@ public class KeyHandler { let values = currentNode.currentPair.value.charComponents for (subPosition, key) in currentNode.keyArray.enumerated() { guard values.count > subPosition else { break } // 防呆,應該沒有發生的可能性 - let thePair = Megrez.Compositor.Candidate( + let thePair = Megrez.Compositor.KeyValuePaired( key: key, value: values[subPosition] ) compositor.overrideCandidate(thePair, at: position) @@ -212,7 +212,7 @@ public class KeyHandler { /// - respectCursorPushing: 若該選項為 true,則會在選字之後始終將游標推送至選字後的節錨的前方。 /// - consolidate: 在固化節點之前,先鞏固上下文。該選項可能會破壞在內文組字區內就地輪替候選字詞時的體驗。 func fixNode(candidate: (String, String), respectCursorPushing: Bool = true, preConsolidate: Bool = false) { - let theCandidate: Megrez.Compositor.Candidate = .init(key: candidate.0, value: candidate.1) + let theCandidate: Megrez.Compositor.KeyValuePaired = .init(key: candidate.0, value: candidate.1) /// 必須先鞏固當前組字器游標上下文、以消滅意料之外的影響,但在內文組字區內就地輪替候選字詞時除外。 if preConsolidate { consolidateCursorContext(with: theCandidate) } @@ -256,7 +256,7 @@ public class KeyHandler { func getCandidatesArray(fixOrder: Bool = true) -> [(String, String)] { /// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。 /// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的。 - var arrCandidates: [Megrez.Compositor.Candidate] = { + var arrCandidates: [Megrez.Compositor.KeyValuePaired] = { switch mgrPrefs.useRearCursorMode { case false: return compositor.fetchCandidates(at: actualCandidateCursor, filter: .endAt) @@ -276,8 +276,8 @@ public class KeyHandler { } let arrSuggestedUnigrams: [(String, Megrez.Unigram)] = fetchSuggestionsFromUOM(apply: false) - let arrSuggestedCandidates: [Megrez.Compositor.Candidate] = arrSuggestedUnigrams.map { - Megrez.Compositor.Candidate(key: $0.0, value: $0.1.value) + let arrSuggestedCandidates: [Megrez.Compositor.KeyValuePaired] = arrSuggestedUnigrams.map { + Megrez.Compositor.KeyValuePaired(key: $0.0, value: $0.1.value) } arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates arrCandidates = arrCandidates.deduplicate @@ -302,7 +302,7 @@ public class KeyHandler { if !suggestion.isEmpty, let newestSuggestedCandidate = suggestion.candidates.last { let overrideBehavior: Megrez.Compositor.Node.OverrideType = suggestion.forceHighScoreOverride ? .withHighScore : .withTopUnigramScore - let suggestedPair: Megrez.Compositor.Candidate = .init( + let suggestedPair: Megrez.Compositor.KeyValuePaired = .init( key: newestSuggestedCandidate.0, value: newestSuggestedCandidate.1.value ) IME.prtDebugIntel( diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index 2c30cb47..11f7cd42 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -148,7 +148,7 @@ extension KeyHandler { /// - key: 給定的索引鍵(也就是給定的聯想詞的開頭字)。 /// - Returns: 回呼一個新的聯想詞狀態,來就給定的聯想詞陣列資料內容顯示選字窗。 func buildAssociatePhraseState( - withPair pair: Megrez.Compositor.Candidate + withPair pair: Megrez.Compositor.KeyValuePaired ) -> InputState.Associates! { // 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。 InputState.Associates( diff --git a/Source/Modules/LangModelRelated/LMInstantiator.swift b/Source/Modules/LangModelRelated/LMInstantiator.swift index 6d02c367..829ed8fa 100644 --- a/Source/Modules/LangModelRelated/LMInstantiator.swift +++ b/Source/Modules/LangModelRelated/LMInstantiator.swift @@ -245,11 +245,11 @@ extension vChewing { return !unigramsFor(key: key).isEmpty } - public func associatedPhrasesFor(pair: Megrez.Compositor.Candidate) -> [String] { + public func associatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] { lmAssociates.valuesFor(pair: pair) } - public func hasAssociatedPhrasesFor(pair: Megrez.Compositor.Candidate) -> Bool { + public func hasAssociatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> Bool { lmAssociates.hasValuesFor(pair: pair) } diff --git a/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift b/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift index 4bb7e076..ee6c89b8 100644 --- a/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift +++ b/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift @@ -78,7 +78,7 @@ extension vChewing { // This function will be implemented only if further hard-necessity comes. } - public func valuesFor(pair: Megrez.Compositor.Candidate) -> [String] { + public func valuesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] { var pairs: [String] = [] if let arrRangeRecords: [(Range, Int)] = rangeMap[pair.toNGramKey] { for (netaRange, index) in arrRangeRecords { @@ -98,7 +98,7 @@ extension vChewing { return pairs.filter { set.insert($0).inserted } } - public func hasValuesFor(pair: Megrez.Compositor.Candidate) -> Bool { + public func hasValuesFor(pair: Megrez.Compositor.KeyValuePaired) -> Bool { if rangeMap[pair.toNGramKey] != nil { return true } return rangeMap[pair.value] != nil } diff --git a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift index fade1ead..e7a52382 100644 --- a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift +++ b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift @@ -319,8 +319,8 @@ extension vChewing.LMUserOverride { // 前置單元只記錄讀音,在其後的單元則同時記錄讀音與字詞 let strCurrent = kvCurrent.key - var kvPrevious = Megrez.Compositor.Candidate() - var kvAnterior = Megrez.Compositor.Candidate() + var kvPrevious = Megrez.Compositor.KeyValuePaired() + var kvAnterior = Megrez.Compositor.KeyValuePaired() var readingStack = "" var trigramKey: String { "(\(kvAnterior.toNGramKey),\(kvPrevious.toNGramKey),\(strCurrent))" } var result: String { diff --git a/Source/Modules/LanguageParsers/Megrez/0_Megrez.swift b/Source/Modules/LanguageParsers/Megrez/0_Megrez.swift index 3bdc535c..f30622fa 100644 --- a/Source/Modules/LanguageParsers/Megrez/0_Megrez.swift +++ b/Source/Modules/LanguageParsers/Megrez/0_Megrez.swift @@ -4,9 +4,7 @@ // This code is released under the MIT license (SPDX-License-Identifier: MIT) /// The namespace for this package. -public enum Megrez { - public typealias KeyValuePaired = Compositor.Candidate // 相容性措施。 -} +public enum Megrez {} // 著作權聲明: // 除了 Megrez 專有的修改與實作以外,該套件所有程式邏輯來自於 Gramambular、算法歸 Lukhnos Liu 所有。 diff --git a/Source/Modules/LanguageParsers/Megrez/1_Compositor.swift b/Source/Modules/LanguageParsers/Megrez/1_Compositor.swift index 078fdf97..7c8f86c0 100644 --- a/Source/Modules/LanguageParsers/Megrez/1_Compositor.swift +++ b/Source/Modules/LanguageParsers/Megrez/1_Compositor.swift @@ -23,7 +23,15 @@ extension Megrez { /// 公開:多字讀音鍵當中用以分割漢字讀音的記號的預設值,是「-」。 public static let kDefaultSeparator: String = "-" /// 該組字器的游標位置。 - public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, length)) } } + public var cursor: Int = 0 { + didSet { + cursor = max(0, min(cursor, length)) + marker = cursor + } + } + + /// 該組字器的標記器位置。 + public var marker: Int = 0 { didSet { marker = max(0, min(marker, length)) } } /// 公開:多字讀音鍵當中用以分割漢字讀音的記號,預設為「-」。 public var separator = kDefaultSeparator /// 公開:組字器內已經插入的單筆索引鍵的數量。 @@ -88,37 +96,46 @@ extension Megrez { } /// 按幅位來前後移動游標。 - /// - Parameter direction: 移動方向。 + /// - Parameters: + /// - direction: 移動方向。 + /// - isMarker: 要移動的是否為選擇標記(而非游標)。 /// - Returns: 該操作是否順利完成。 - @discardableResult public mutating func jumpCursorBySpan(to direction: TypingDirection) -> Bool { + @discardableResult public mutating func jumpCursorBySpan(to direction: TypingDirection, isMarker: Bool = false) + -> Bool + { + var target = isMarker ? marker : cursor switch direction { case .front: - if cursor == width { return false } + if target == width { return false } case .rear: - if cursor == 0 { return false } + if target == 0 { return false } } - guard let currentRegion = cursorRegionMap[cursor] else { return false } + guard let currentRegion = cursorRegionMap[target] else { return false } let aRegionForward = max(currentRegion - 1, 0) let currentRegionBorderRear: Int = walkedNodes[0.. walkedNodes.count) ? keys.count : walkedNodes[0...currentRegion].map(\.spanLength).reduce(0, +) case .rear: - cursor = walkedNodes[0.. Bool { + public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { lhs.key == rhs.key && lhs.value == rhs.value } - public static func < (lhs: Candidate, rhs: Candidate) -> Bool { + public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { (lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value) } - public static func > (lhs: Candidate, rhs: Candidate) -> Bool { + public static func > (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { (lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value) } - public static func <= (lhs: Candidate, rhs: Candidate) -> Bool { + public static func <= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { (lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value) } - public static func >= (lhs: Candidate, rhs: Candidate) -> Bool { + public static func >= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { (lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value) } } @@ -60,8 +60,8 @@ extension Megrez.Compositor { /// 話,那麼這裡會用到 location - 1、以免去在呼叫該函式後再處理的麻煩。 /// - Parameter location: 游標位置。 /// - Returns: 候選字音配對陣列。 - public func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [Candidate] { - var result = [Candidate]() + public func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [KeyValuePaired] { + var result = [KeyValuePaired]() guard !keys.isEmpty else { return result } let location = max(min(location, keys.count - 1), 0) // 防呆 let anchors: [NodeAnchor] = fetchOverlappingNodes(at: location).stableSorted { @@ -96,7 +96,7 @@ extension Megrez.Compositor { /// - overrideType: 指定覆寫行為。 /// - Returns: 該操作是否成功執行。 @discardableResult public func overrideCandidate( - _ candidate: Candidate, at location: Int, overrideType: Node.OverrideType = .withHighScore + _ candidate: KeyValuePaired, at location: Int, overrideType: Node.OverrideType = .withHighScore ) -> Bool { @@ -154,7 +154,7 @@ extension Megrez.Compositor { anchor.node.reset() continue } - anchor.node.overridingScore /= 2 + anchor.node.overridingScore /= 4 } } return true diff --git a/Source/Modules/LanguageParsers/Megrez/6_Node.swift b/Source/Modules/LanguageParsers/Megrez/6_Node.swift index ebbc9df9..1ae23d43 100644 --- a/Source/Modules/LanguageParsers/Megrez/6_Node.swift +++ b/Source/Modules/LanguageParsers/Megrez/6_Node.swift @@ -42,7 +42,7 @@ extension Megrez.Compositor { didSet { currentUnigramIndex = min(max(0, currentUnigramIndex), unigrams.count - 1) } } - public var currentPair: Megrez.Compositor.Candidate { .init(key: key, value: value) } + public var currentPair: Megrez.Compositor.KeyValuePaired { .init(key: key, value: value) } public func hash(into hasher: inout Hasher) { hasher.combine(key) @@ -70,8 +70,12 @@ extension Megrez.Compositor { overrideType = .withNoOverrides } + /// 檢查當前節點是否「讀音字長與候選字字長不一致」。 + public var isReadingMismatched: Bool { + keyArray.count != value.count + } + /// 給出目前的最高權重單元圖。該結果可能會受節點覆寫狀態所影響。 - /// - Returns: 目前的最高權重單元圖。該結果可能會受節點覆寫狀態所影響。 public var currentUnigram: Megrez.Unigram { unigrams.isEmpty ? .init() : unigrams[currentUnigramIndex] } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 24d06b15..4837073e 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ 5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */; }; 5B2170E1289FACAD00BE7304 /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */; }; 5B2170E2289FACAD00BE7304 /* 8_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */; }; - 5B2170E3289FACAD00BE7304 /* 3_Candidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */; }; + 5B2170E3289FACAD00BE7304 /* 3_KeyValuePaired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DA289FACAC00BE7304 /* 3_KeyValuePaired.swift */; }; 5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DB289FACAC00BE7304 /* 2_Walker.swift */; }; 5B2170E5289FACAD00BE7304 /* 6_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DC289FACAC00BE7304 /* 6_Node.swift */; }; 5B2170E6289FACAD00BE7304 /* 4_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DD289FACAC00BE7304 /* 4_Span.swift */; }; @@ -231,7 +231,7 @@ 5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 7_LangModel.swift; sourceTree = ""; }; 5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 0_Megrez.swift; sourceTree = ""; }; 5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 8_Unigram.swift; sourceTree = ""; }; - 5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 3_Candidate.swift; sourceTree = ""; }; + 5B2170DA289FACAC00BE7304 /* 3_KeyValuePaired.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 3_KeyValuePaired.swift; sourceTree = ""; }; 5B2170DB289FACAC00BE7304 /* 2_Walker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 2_Walker.swift; sourceTree = ""; }; 5B2170DC289FACAC00BE7304 /* 6_Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6_Node.swift; sourceTree = ""; }; 5B2170DD289FACAC00BE7304 /* 4_Span.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 4_Span.swift; sourceTree = ""; }; @@ -873,7 +873,7 @@ 5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */, 5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */, 5B2170DB289FACAC00BE7304 /* 2_Walker.swift */, - 5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */, + 5B2170DA289FACAC00BE7304 /* 3_KeyValuePaired.swift */, 5B2170DD289FACAC00BE7304 /* 4_Span.swift */, 5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */, 5B2170DC289FACAC00BE7304 /* 6_Node.swift */, @@ -1255,7 +1255,7 @@ 5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */, 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */, 5B62A33827AE79CD00A19448 /* StringUtils.swift in Sources */, - 5B2170E3289FACAD00BE7304 /* 3_Candidate.swift in Sources */, + 5B2170E3289FACAD00BE7304 /* 3_KeyValuePaired.swift in Sources */, 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, 5B2170E6289FACAD00BE7304 /* 4_Span.swift in Sources */, 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */,