From e2bac3eb3242091a5730abb7ff3df27ae1662299 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 19 May 2022 08:54:25 +0800 Subject: [PATCH 01/22] ctlIME // Extend resetKeyHandler() for specified clients. --- Source/Modules/IMEModules/ctlInputMethod.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index fb4a2a33..3433a0a5 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -77,9 +77,11 @@ class ctlInputMethod: IMKInputController { // MARK: - KeyHandler Reset Command - func resetKeyHandler() { - if let currentClient = currentClient { - keyHandler.clear() + func resetKeyHandler(client sender: Any? = nil) { + keyHandler.clear() + if let client = sender as? IMKTextInput { + handle(state: InputState.Empty(), client: client) + } else if let currentClient = currentClient { handle(state: InputState.Empty(), client: currentClient) } } From 93b70dc8305e580fe317e182fd68781a9c6f8626 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 19 May 2022 08:54:47 +0800 Subject: [PATCH 02/22] ctlIME // Reset KeyHandler when commitComposition(). --- Source/Modules/IMEModules/ctlInputMethod.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 3433a0a5..4ff56ecd 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -197,6 +197,13 @@ class ctlInputMethod: IMKInputController { } return result } + + // 有時會出現某些 App 攔截輸入法的 Ctrl+Enter / Shift+Enter 熱鍵的情況。 + // 也就是說 handle(event:) 完全抓不到這個 Event。 + // 這時需要在 commitComposition 這一關做一些收尾處理。 + override func commitComposition(_ sender: Any!) { + resetKeyHandler(client: sender) + } } // MARK: - State Handling From af2bdc4343ac546484b47315bfd2cee0b837f376 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 19 May 2022 08:55:15 +0800 Subject: [PATCH 03/22] ctlIME // Force composedString() results to respect state context. --- Source/Modules/IMEModules/ctlInputMethod.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 4ff56ecd..621af4ab 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -204,6 +204,11 @@ class ctlInputMethod: IMKInputController { override func commitComposition(_ sender: Any!) { resetKeyHandler(client: sender) } + + // 這個函數必須得在對應的狀態下給出對應的內容。 + override func composedString(_ sender: Any!) -> Any! { + (state as? InputState.NotEmpty)?.composingBuffer ?? "" + } } // MARK: - State Handling From 494e9cf637b32cdf631bfe4a83d83b400534f25c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 20 May 2022 01:09:03 +0800 Subject: [PATCH 04/22] KeyHandler & Megrez // Updates to Megrez v1.1.2. --- .../ControllerModules/KeyHandler_Core.swift | 37 ++-- .../ControllerModules/KeyHandler_States.swift | 6 +- .../SubLMs/lmUserOverride.swift | 6 +- .../Megrez/1_BlockReadingBuilder.swift | 175 +++++++++++++++--- .../LanguageParsers/Megrez/1_Walker.swift | 123 ------------ .../LanguageParsers/Megrez/2_Grid.swift | 88 ++++++++- .../LanguageParsers/Megrez/3_NodeAnchor.swift | 39 +++- .../LanguageParsers/Megrez/3_Span.swift | 25 ++- .../LanguageParsers/Megrez/4_Node.swift | 133 +++++++------ .../Megrez/5_LanguageModel.swift | 8 +- .../LanguageParsers/Megrez/6_Bigram.swift | 48 ++--- .../LanguageParsers/Megrez/6_Unigram.swift | 47 ++--- .../Megrez/7_KeyValuePair.swift | 24 +-- vChewing.xcodeproj/project.pbxproj | 4 - 14 files changed, 446 insertions(+), 317 deletions(-) delete mode 100644 Source/Modules/LanguageParsers/Megrez/1_Walker.swift diff --git a/Source/Modules/ControllerModules/KeyHandler_Core.swift b/Source/Modules/ControllerModules/KeyHandler_Core.swift index 74883d32..9a98c651 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Core.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Core.swift @@ -72,7 +72,7 @@ class KeyHandler { } public init() { - _builder = Megrez.BlockReadingBuilder(lm: _languageModel) + _builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-") ensureParser() setInputMode(ctlInputMethod.currentInputMode) } @@ -118,9 +118,7 @@ class KeyHandler { // of the best possible Mandarin characters given the input syllables, // using the Viterbi algorithm implemented in the Megrez library. // The walk() traces the grid to the end, hence no need to use .reversed() here. - _walkedNodes = Megrez.Walker( - grid: _builder.grid() - ).walk(at: _builder.grid().width(), nodesLimit: 3, balanced: true) + _walkedNodes = _builder.walk(at: _builder.grid.width, nodesLimit: 3, balanced: true) } func popOverflowComposingTextAndWalk() -> String { @@ -133,11 +131,11 @@ class KeyHandler { // (i.e. popped out.) var poppedText = "" - if _builder.grid().width() > mgrPrefs.composingBufferSize { + if _builder.grid.width > mgrPrefs.composingBufferSize { if _walkedNodes.count > 0 { let anchor: Megrez.NodeAnchor = _walkedNodes[0] if let theNode = anchor.node { - poppedText = theNode.currentKeyValue().value + poppedText = theNode.currentKeyValue.value } _builder.removeHeadReadings(count: anchor.spanningLength) } @@ -156,7 +154,7 @@ class KeyHandler { func fixNode(value: String) { let cursorIndex: Int = getActualCandidateCursorIndex() - let selectedNode: Megrez.NodeAnchor = _builder.grid().fixNodeSelectedCandidate( + let selectedNode: Megrez.NodeAnchor = _builder.grid.fixNodeSelectedCandidate( location: cursorIndex, value: value ) // 不要針對逐字選字模式啟用臨時半衰記憶模型。 @@ -216,7 +214,7 @@ class KeyHandler { // then use the Swift trick to retrieve the candidates for each node at/crossing the cursor for currentNodeAnchor in arrNodes { if let currentNode = currentNodeAnchor.node { - for currentCandidate in currentNode.candidates() { + for currentCandidate in currentNode.candidates { arrCandidates.append(currentCandidate.value) } } @@ -237,7 +235,7 @@ class KeyHandler { if !overrideValue.isEmpty { IME.prtDebugIntel( "UOM: Suggestion retrieved, overriding the node score of the selected candidate.") - _builder.grid().overrideNodeScoreForSelectedCandidate( + _builder.grid.overrideNodeScoreForSelectedCandidate( location: getActualCandidateCursorIndex(), value: overrideValue, overridingScore: findHighestScore(nodes: getRawNodes(), epsilon: kEpsilon) @@ -251,7 +249,7 @@ class KeyHandler { var highestScore: Double = 0 for currentAnchor in nodes { if let theNode = currentAnchor.node { - let score = theNode.highestUnigramScore() + let score = theNode.highestUnigramScore if score > highestScore { highestScore = score } @@ -262,15 +260,15 @@ class KeyHandler { // MARK: - Extracted methods and functions (Megrez). - func isBuilderEmpty() -> Bool { _builder.grid().width() == 0 } + func isBuilderEmpty() -> Bool { _builder.grid.width == 0 } func getRawNodes() -> [Megrez.NodeAnchor] { /// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。 /// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的,但目前 Megrez 暫時缺乏對該特性的支援。 /// 所以暫時只能將威注音的游標後置風格描述成「跟 Windows 版雅虎奇摩注音一致」。 mgrPrefs.setRearCursorMode - ? _builder.grid().nodesCrossingOrEndingAt(location: getActualCandidateCursorIndex()) - : _builder.grid().nodesEndingAt(location: getActualCandidateCursorIndex()) + ? _builder.grid.nodesCrossingOrEndingAt(location: getActualCandidateCursorIndex()) + : _builder.grid.nodesEndingAt(location: getActualCandidateCursorIndex()) } func setInputModesToLM(isCHS: Bool) { @@ -285,12 +283,11 @@ class KeyHandler { } func createNewBuilder() { - _builder = Megrez.BlockReadingBuilder(lm: _languageModel) // Each Mandarin syllable is separated by a hyphen. - _builder.setJoinSeparator(separator: "-") + _builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-") } - func currentReadings() -> [String] { _builder.readings() } + func currentReadings() -> [String] { _builder.readings } func ifLangModelHasUnigrams(forKey reading: String) -> Bool { _languageModel.hasUnigramsFor(key: reading) @@ -301,15 +298,15 @@ class KeyHandler { } func setBuilderCursorIndex(value: Int) { - _builder.setCursorIndex(newIndex: value) + _builder.cursorIndex = value } func getBuilderCursorIndex() -> Int { - _builder.cursorIndex() + _builder.cursorIndex } func getBuilderLength() -> Int { - _builder.length() + _builder.length } func deleteBuilderReadingInFrontOfCursor() { @@ -321,7 +318,7 @@ class KeyHandler { } func getKeyLengthAtIndexZero() -> Int { - _walkedNodes[0].node?.currentKeyValue().value.count ?? 0 + _walkedNodes[0].node?.currentKeyValue.value.count ?? 0 } // MARK: - Extracted methods and functions (Tekkon). diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index 6a361d80..c5bd47af 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -45,7 +45,7 @@ extension KeyHandler { continue } - let valueString = node.currentKeyValue().value + let valueString = node.currentKeyValue.value composingBuffer += valueString let codepointCount = valueString.count @@ -303,7 +303,7 @@ extension KeyHandler { for theAnchor in _walkedNodes { if let node = theAnchor.node { - var key = node.currentKeyValue().key + var key = node.currentKeyValue.key if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin { key = restoreToneOneInZhuyinKey(target: key) // 恢復陰平標記 key = Tekkon.cnvPhonaToHanyuPinyin(target: key) // 注音轉拼音 @@ -313,7 +313,7 @@ extension KeyHandler { key = cnvZhuyinKeyToTextbookReading(target: key, newSeparator: " ") } - let value = node.currentKeyValue().value + let value = node.currentKeyValue.value if key.contains("_") { // 不要給標點符號等特殊元素加注音 composed += value } else { diff --git a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift index 7791275b..45b2fdd0 100644 --- a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift +++ b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift @@ -186,7 +186,7 @@ extension vChewing { var strPrevious = "()" var strAnterior = "()" - guard let kvCurrent = arrNodesReversed[0].node?.currentKeyValue(), + guard let kvCurrent = arrNodesReversed[0].node?.currentKeyValue, !arrEndingPunctuation.contains(kvCurrent.value) else { return "" @@ -196,14 +196,14 @@ extension vChewing { strCurrent = kvCurrent.key if arrNodesReversed.count >= 2, - let kvPrevious = arrNodesReversed[1].node?.currentKeyValue(), + let kvPrevious = arrNodesReversed[1].node?.currentKeyValue, !arrEndingPunctuation.contains(kvPrevious.value) { strPrevious = "(\(kvPrevious.key),\(kvPrevious.value))" } if arrNodesReversed.count >= 3, - let kvAnterior = arrNodesReversed[2].node?.currentKeyValue(), + let kvAnterior = arrNodesReversed[2].node?.currentKeyValue, !arrEndingPunctuation.contains(kvAnterior.value) { strAnterior = "(\(kvAnterior.key),\(kvAnterior.value))" diff --git a/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift b/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift index 78b659f0..2bf8cb76 100644 --- a/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift +++ b/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift @@ -24,33 +24,55 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { + /// 分節讀音槽。 public class BlockReadingBuilder { - var mutMaximumBuildSpanLength = 10 - var mutCursorIndex: Int = 0 - var mutReadings: [String] = [] - var mutGrid: Grid = .init() - var mutLM: LanguageModel - var mutJoinSeparator: String = "" + /// 該分節讀音曹內可以允許的最大詞長。 + private var mutMaximumBuildSpanLength = 10 + /// 該分節讀音槽的游標位置。 + private var mutCursorIndex: Int = 0 + /// 該分節讀音槽的讀音陣列。 + private var mutReadings: [String] = [] + /// 該分節讀音槽的軌格。 + private var mutGrid: Grid = .init() + /// 該分節讀音槽所使用的語言模型。 + private var mutLM: LanguageModel - public init(lm: LanguageModel, length: Int = 10) { - mutLM = lm - mutMaximumBuildSpanLength = length + /// 公開:多字讀音鍵當中用以分割漢字讀音的記號,預設為空。 + public var joinSeparator: String = "" + /// 公開:該分節讀音槽的游標位置。 + public var cursorIndex: Int { + get { mutCursorIndex } + set { mutCursorIndex = (newValue < 0) ? 0 : min(newValue, mutReadings.count) } } + /// 公開:該分節讀音槽的軌格(唯讀)。 + public var grid: Grid { mutGrid } + /// 公開:該分節讀音槽的長度,也就是內建漢字讀音的數量(唯讀)。 + public var length: Int { mutReadings.count } + /// 公開:該分節讀音槽的讀音陣列(唯讀)。 + public var readings: [String] { mutReadings } + + /// 分節讀音槽。 + /// - Parameters: + /// - lm: 語言模型。可以是任何基於 Megrez.LanguageModel 的衍生型別。 + /// - length: 指定該分節讀音曹內可以允許的最大詞長,預設為 10 字。 + /// - separator: 多字讀音鍵當中用以分割漢字讀音的記號,預設為空。 + public init(lm: LanguageModel, length: Int = 10, separator: String = "") { + mutLM = lm + mutMaximumBuildSpanLength = length + joinSeparator = separator + } + + /// 分節讀音槽自我清空專用函數。 public func clear() { mutCursorIndex = 0 mutReadings.removeAll() mutGrid.clear() } - public func length() -> Int { mutReadings.count } - - public func cursorIndex() -> Int { mutCursorIndex } - - public func setCursorIndex(newIndex: Int) { - mutCursorIndex = min(newIndex, mutReadings.count) - } - + /// 在游標位置插入給定的讀音。 + /// - Parameters: + /// - reading: 要插入的讀音。 public func insertReadingAtCursor(reading: String) { mutReadings.insert(reading, at: mutCursorIndex) mutGrid.expandGridByOneAt(location: mutCursorIndex) @@ -58,8 +80,8 @@ extension Megrez { mutCursorIndex += 1 } - public func readings() -> [String] { mutReadings } - + /// 朝著與文字輸入方向相反的方向、砍掉一個與游標相鄰的讀音。 + /// 在威注音的術語體系當中,「與文字輸入方向相反的方向」為向後(Rear)。 @discardableResult public func deleteReadingAtTheRearOfCursor() -> Bool { if mutCursorIndex == 0 { return false @@ -72,6 +94,8 @@ extension Megrez { return true } + /// 朝著往文字輸入方向、砍掉一個與游標相鄰的讀音。 + /// 在威注音的術語體系當中,「文字輸入方向」為向前(Front)。 @discardableResult public func deleteReadingToTheFrontOfCursor() -> Bool { if mutCursorIndex == mutReadings.count { return false @@ -83,8 +107,12 @@ extension Megrez { return true } + /// 移除該分節讀音槽的第一個讀音單元。 + /// + /// 用於輸入法組字區長度上限處理: + /// 將該位置要溢出的敲字內容遞交之後、再執行這個函數。 @discardableResult public func removeHeadReadings(count: Int) -> Bool { - if count > length() { + if count > length { return false } @@ -100,17 +128,108 @@ extension Megrez { return true } - public func setJoinSeparator(separator: String) { - mutJoinSeparator = separator + // MARK: - Walker + + /// 對已給定的軌格按照給定的位置與條件進行正向爬軌。 + /// + /// 其實就是將反向爬軌的結果顛倒順序再給出來而已,省得使用者自己再顛倒一遍。 + /// - Parameters: + /// - at: 開始爬軌的位置。 + /// - score: 給定累計權重,非必填參數。預設值為 0。 + /// - nodesLimit: 限定最多只爬多少個節點。 + /// - balanced: 啟用平衡權重,在節點權重的基礎上根據節點幅位長度來加權。 + public func walk( + at location: Int, + score accumulatedScore: Double = 0.0, + nodesLimit: Int = 0, + balanced: Bool = false + ) -> [NodeAnchor] { + Array( + reverseWalk( + at: location, score: accumulatedScore, + nodesLimit: nodesLimit, balanced: balanced + ).reversed()) } - public func joinSeparator() -> String { mutJoinSeparator } + /// 對已給定的軌格按照給定的位置與條件進行反向爬軌。 + /// - Parameters: + /// - at: 開始爬軌的位置。 + /// - score: 給定累計權重,非必填參數。預設值為 0。 + /// - nodesLimit: 限定最多只爬多少個節點。 + /// - balanced: 啟用平衡權重,在節點權重的基礎上根據節點幅位長度來加權。 + public func reverseWalk( + at location: Int, + score accumulatedScore: Double = 0.0, + nodesLimit: Int = 0, + balanced: Bool = false + ) -> [NodeAnchor] { + if location == 0 || location > mutGrid.width { + return [] as [NodeAnchor] + } - public func grid() -> Grid { mutGrid } + var paths: [[NodeAnchor]] = [] + var nodes: [NodeAnchor] = mutGrid.nodesEndingAt(location: location) - public func build() { - // if (mutLM == nil) { return } // 這個出不了 nil,所以註釋掉。 + if balanced { + nodes.sort { + $0.balancedScore > $1.balancedScore + } + } + for (i, n) in nodes.enumerated() { + // 只檢查前 X 個 NodeAnchor 是否有 node。 + // 這裡有 abs 是為了防止有白癡填負數。 + if abs(nodesLimit) > 0, i == abs(nodesLimit) - 1 { + break + } + + var n = n + guard let nNode = n.node else { + continue + } + + n.accumulatedScore = accumulatedScore + nNode.score + + // 利用幅位長度來決定權重。 + // 這樣一來,例:「再見」比「在」與「見」的權重更高。 + if balanced { + let weightedScore: Double = (Double(n.spanningLength) - 1) * 2 + n.accumulatedScore += weightedScore + } + + var path: [NodeAnchor] = reverseWalk( + at: location - n.spanningLength, + score: n.accumulatedScore + ) + + path.insert(n, at: 0) + + paths.append(path) + + // 始終使用固定的候選字詞 + if balanced, nNode.score >= 0 { + break + } + } + + if !paths.isEmpty { + if var result = paths.first { + for value in paths { + if let vLast = value.last, let rLast = result.last { + if vLast.accumulatedScore > rLast.accumulatedScore { + result = value + } + } + } + return result + } + } + return [] as [NodeAnchor] + } + + // MARK: - Private functions + + private func build() { let itrBegin: Int = (mutCursorIndex < mutMaximumBuildSpanLength) ? 0 : mutCursorIndex - mutMaximumBuildSpanLength let itrEnd: Int = min(mutCursorIndex + mutMaximumBuildSpanLength, mutReadings.count) @@ -121,7 +240,7 @@ extension Megrez { break } let strSlice = mutReadings[p..<(p + q)] - let combinedReading: String = join(slice: strSlice, separator: mutJoinSeparator) + let combinedReading: String = join(slice: strSlice, separator: joinSeparator) if !mutGrid.hasMatchedNode(location: p, spanningLength: q, key: combinedReading) { let unigrams: [Unigram] = mutLM.unigramsFor(key: combinedReading) @@ -134,7 +253,7 @@ extension Megrez { } } - public func join(slice strSlice: ArraySlice, separator: String) -> String { + private func join(slice strSlice: ArraySlice, separator: String) -> String { var arrResult: [String] = [] for value in strSlice { arrResult.append(value) diff --git a/Source/Modules/LanguageParsers/Megrez/1_Walker.swift b/Source/Modules/LanguageParsers/Megrez/1_Walker.swift deleted file mode 100644 index 5bd934b8..00000000 --- a/Source/Modules/LanguageParsers/Megrez/1_Walker.swift +++ /dev/null @@ -1,123 +0,0 @@ -// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). -// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -extension Megrez { - public class Walker { - var mutGrid: Grid - - public init(grid: Megrez.Grid = Megrez.Grid()) { - mutGrid = grid - } - - public func walk( - at location: Int, - score accumulatedScore: Double = 0.0, - nodesLimit: Int = 0, - balanced: Bool = false - ) -> [NodeAnchor] { - var arrReturn: [NodeAnchor] = [] - let arrReversedSource = reverseWalk( - at: location, score: accumulatedScore, - nodesLimit: nodesLimit, balanced: balanced - ).reversed() - - for neta in arrReversedSource { - arrReturn.append(neta) - } - - return arrReturn - } - - public func reverseWalk( - at location: Int, - score accumulatedScore: Double = 0.0, - nodesLimit: Int = 0, - balanced: Bool = false - ) -> [NodeAnchor] { - if location == 0 || location > mutGrid.width() { - return [] as [NodeAnchor] - } - - var paths: [[NodeAnchor]] = [] - var nodes: [NodeAnchor] = mutGrid.nodesEndingAt(location: location) - - if balanced { - nodes.sort { - $0.balancedScore > $1.balancedScore - } - } - - for (i, n) in nodes.enumerated() { - // 只檢查前 X 個 NodeAnchor 是否有 node。 - // 這裡有 abs 是為了防止有白癡填負數。 - if abs(nodesLimit) > 0, i == abs(nodesLimit) - 1 { - break - } - - var n = n - guard let nNode = n.node else { - continue - } - - n.accumulatedScore = accumulatedScore + nNode.score() - - // 利用 Spanning Length 來決定權重。 - // 這樣一來,例:「再見」比「在」與「見」的權重更高。 - if balanced { - let weightedScore: Double = (Double(n.spanningLength) - 1) * 2 - n.accumulatedScore += weightedScore - } - - var path: [NodeAnchor] = reverseWalk( - at: location - n.spanningLength, - score: n.accumulatedScore - ) - - path.insert(n, at: 0) - - paths.append(path) - - // 始終使用固定的候選字 - if balanced, nNode.score() >= 0 { - break - } - } - - if !paths.isEmpty { - if var result = paths.first { - for value in paths { - if let vLast = value.last, let rLast = result.last { - if vLast.accumulatedScore > rLast.accumulatedScore { - result = value - } - } - } - return result - } - } - return [] as [NodeAnchor] - } - } -} diff --git a/Source/Modules/LanguageParsers/Megrez/2_Grid.swift b/Source/Modules/LanguageParsers/Megrez/2_Grid.swift index 9f71f3b5..da532564 100644 --- a/Source/Modules/LanguageParsers/Megrez/2_Grid.swift +++ b/Source/Modules/LanguageParsers/Megrez/2_Grid.swift @@ -24,17 +24,28 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { + /// 軌格。 public class Grid { - var mutSpans: [Megrez.Span] + /// 幅位陣列。 + private var mutSpans: [Megrez.Span] + + /// 軌格的寬度,也就是其內的幅位陣列當中的幅位數量。 + var width: Int { mutSpans.count } public init() { mutSpans = [Megrez.Span]() } + /// 自我清空該軌格的內容。 public func clear() { mutSpans = [Megrez.Span]() } + /// 往該軌格的指定位置插入指定幅位長度的指定節點。 + /// - Parameters: + /// - node: 節點。 + /// - location: 位置。 + /// - spanningLength: 給定的幅位長度。 public func insertNode(node: Node, location: Int, spanningLength: Int) { if location >= mutSpans.count { let diff = location - mutSpans.count + 1 @@ -45,15 +56,23 @@ extension Megrez { mutSpans[location].insert(node: node, length: spanningLength) } + /// 給定索引鍵、位置、幅位長度,在該軌格內確認是否有對應的節點存在。 + /// - Parameters: + /// - location: 位置。 + /// - spanningLength: 給定的幅位長度。 + /// - key: 索引鍵。 public func hasMatchedNode(location: Int, spanningLength: Int, key: String) -> Bool { if location > mutSpans.count { return false } let n = mutSpans[location].node(length: spanningLength) - return n == nil ? false : key == n?.key() + return n == nil ? false : key == n?.key } + /// 在該軌格的指定位置擴增一個幅位。 + /// - Parameters: + /// - location: 位置。 public func expandGridByOneAt(location: Int) { // 這裡加入 abs 完全是一個防呆設計 mutSpans.insert(Span(), at: abs(location)) @@ -65,6 +84,9 @@ extension Megrez { } } + /// 在該軌格的指定位置減少一個幅位。 + /// - Parameters: + /// - location: 位置。 public func shrinkGridByOneAt(location: Int) { if location >= mutSpans.count { return @@ -77,8 +99,9 @@ extension Megrez { } } - public func width() -> Int { mutSpans.count } - + /// 給定位置,枚舉出所有在這個位置結尾的節點。 + /// - Parameters: + /// - location: 位置。 public func nodesEndingAt(location: Int) -> [NodeAnchor] { var results: [NodeAnchor] = [] if !mutSpans.isEmpty, location <= mutSpans.count { @@ -100,6 +123,9 @@ extension Megrez { return results } + /// 給定位置,枚舉出所有在這個位置結尾、或者橫跨該位置的節點。 + /// - Parameters: + /// - location: 位置。 public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] { var results: [NodeAnchor] = [] if !mutSpans.isEmpty, location <= mutSpans.count { @@ -126,14 +152,18 @@ extension Megrez { return results } - public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor { + /// 將給定位置的節點的候選字詞改為與給定的字串一致的候選字詞。 + /// - Parameters: + /// - location: 位置。 + /// - value: 給定字串。 + @discardableResult public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor { var node = NodeAnchor() for nodeAnchor in nodesCrossingOrEndingAt(location: location) { guard let theNode = nodeAnchor.node else { continue } - let candidates = theNode.candidates() - // Reset the candidate-fixed state of every node at the location. + let candidates = theNode.candidates + // 將該位置的所有節點的候選字詞鎖定狀態全部重設。 theNode.resetCandidate() for (i, candidate) in candidates.enumerated() { if candidate.value == value { @@ -146,13 +176,18 @@ extension Megrez { return node } + /// 將給定位置的節點的與給定的字串一致的候選字詞的權重複寫為給定權重數值。 + /// - Parameters: + /// - location: 位置。 + /// - value: 給定字串。 + /// - overridingScore: 給定權重數值。 public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) { for nodeAnchor in nodesCrossingOrEndingAt(location: location) { guard let theNode = nodeAnchor.node else { continue } - let candidates = theNode.candidates() - // Reset the candidate-fixed state of every node at the location. + let candidates = theNode.candidates + // 將該位置的所有節點的候選字詞鎖定狀態全部重設。 theNode.resetCandidate() for (i, candidate) in candidates.enumerated() { if candidate.value == value { @@ -164,3 +199,38 @@ extension Megrez { } } } + +// MARK: - DumpDOT-related functions. + +extension Megrez.Grid { + public var dumpDOT: String { + var sst = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n" + for (p, span) in mutSpans.enumerated() { + for ni in 0...(span.maximumLength) { + guard let np: Megrez.Node = span.node(length: ni) else { + continue + } + if p == 0 { + sst += "BOS -> \(np.currentKeyValue.value);\n" + } + + sst += "\(np.currentKeyValue.value);\n" + + if (p + ni) < mutSpans.count { + let dstSpan = mutSpans[p + ni] + for q in 0...(dstSpan.maximumLength) { + if let dn = dstSpan.node(length: q) { + sst += np.currentKeyValue.value + " -> " + dn.currentKeyValue.value + ";\n" + } + } + } + + if (p + ni) == mutSpans.count { + sst += np.currentKeyValue.value + " -> EOS;\n" + } + } + } + sst += "EOS;\n}\n" + return sst + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift b/Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift index 938262f4..b450b6a2 100644 --- a/Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift +++ b/Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift @@ -24,19 +24,52 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { - @frozen public struct NodeAnchor { + /// 節锚。 + @frozen public struct NodeAnchor: CustomStringConvertible { + /// 節點。一個節锚內不一定有節點。 public var node: Node? + /// 節锚所在的位置。 public var location: Int = 0 + /// 幅位長度。 public var spanningLength: Int = 0 + /// 累計權重。 public var accumulatedScore: Double = 0.0 + /// 索引鍵的長度。 public var keyLength: Int { - node?.key().count ?? 0 + node?.key.count ?? 0 } + /// 將當前節锚列印成一個字串。 + public var description: String { + var stream = "" + stream += "{@(" + String(location) + "," + String(spanningLength) + ")," + if let node = node { + stream += node.description + } else { + stream += "null" + } + stream += "}" + return stream + } + + /// 獲取平衡權重。 public var balancedScore: Double { let weightedScore: Double = (Double(spanningLength) - 1) * 2 - let nodeScore: Double = node?.score() ?? 0 + let nodeScore: Double = node?.score ?? 0 return weightedScore + nodeScore } } } + +// MARK: - DumpDOT-related functions. + +extension Array where Element == Megrez.NodeAnchor { + /// 將節锚陣列列印成一個字串。 + public var description: String { + var arrOutputContent = [""] + for anchor in self { + arrOutputContent.append(anchor.description) + } + return arrOutputContent.joined(separator: "<-") + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/3_Span.swift b/Source/Modules/LanguageParsers/Megrez/3_Span.swift index 5b5eee7a..d99238ad 100644 --- a/Source/Modules/LanguageParsers/Megrez/3_Span.swift +++ b/Source/Modules/LanguageParsers/Megrez/3_Span.swift @@ -24,23 +24,28 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { + /// 幅位。 @frozen public struct Span { - private var mutLengthNodeMap: [Int: Megrez.Node] - private var mutMaximumLength: Int + /// 辭典:以節點長度為索引,以節點為資料值。 + private var mutLengthNodeMap: [Int: Megrez.Node] = [:] + /// 最大節點長度。 + private var mutMaximumLength: Int = 0 + + /// 公開:最長幅距(唯讀)。 var maximumLength: Int { mutMaximumLength } - public init() { - mutLengthNodeMap = [:] - mutMaximumLength = 0 - } - + /// 自我清空,各項參數歸零。 mutating func clear() { mutLengthNodeMap.removeAll() mutMaximumLength = 0 } + /// 往自身插入一個節點、及給定的節點長度。 + /// - Parameters: + /// - node: 節點。 + /// - length: 給定的節點長度。 mutating func insert(node: Node, length: Int) { mutLengthNodeMap[length] = node if length > mutMaximumLength { @@ -48,6 +53,9 @@ extension Megrez { } } + /// 移除任何比給定的長度更長的節點。 + /// - Parameters: + /// - length: 給定的節點長度。 mutating func removeNodeOfLengthGreaterThan(_ length: Int) { if length > mutMaximumLength { return } var max = 0 @@ -67,6 +75,9 @@ extension Megrez { mutMaximumLength = max } + /// 給定節點長度,獲取節點。 + /// - Parameters: + /// - length: 給定的節點長度。 public func node(length: Int) -> Node? { mutLengthNodeMap[length] } diff --git a/Source/Modules/LanguageParsers/Megrez/4_Node.swift b/Source/Modules/LanguageParsers/Megrez/4_Node.swift index be518b3e..5b658e1c 100644 --- a/Source/Modules/LanguageParsers/Megrez/4_Node.swift +++ b/Source/Modules/LanguageParsers/Megrez/4_Node.swift @@ -24,55 +24,69 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { - public class Node { - let mutLM: LanguageModel - var mutKey: String - var mutScore: Double = 0 - var mutUnigrams: [Unigram] - var mutCandidates: [KeyValuePair] - var mutValueUnigramIndexMap: [String: Int] - var mutPrecedingBigramMap: [KeyValuePair: [Megrez.Bigram]] - - var mutCandidateFixed: Bool = false - var mutSelectedUnigramIndex: Int = 0 - - let kSelectedCandidateScore: Double = 99 - - public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) { - mutLM = LanguageModel() - - mutKey = key - mutScore = 0 - - mutUnigrams = unigrams - mutCandidates = [] - mutValueUnigramIndexMap = [:] - mutPrecedingBigramMap = [:] - - mutCandidateFixed = false - mutSelectedUnigramIndex = 0 - - if bigrams == [] { - node(key: key, unigrams: unigrams, bigrams: bigrams) - } else { - node(key: key, unigrams: unigrams) - } + /// 節點。 + public class Node: CustomStringConvertible { + /// 當前節點對應的語言模型。 + private let mutLM: LanguageModel = .init() + /// 鍵。 + private var mutKey: String = "" + /// 當前節點的當前被選中的候選字詞「在該節點內的」目前的權重。 + private var mutScore: Double = 0 + /// 單元圖陣列。 + private var mutUnigrams: [Unigram] + /// 雙元圖陣列。 + private var mutBigrams: [Bigram] + /// 候選字詞陣列,以鍵值陣列的形式存在。 + private var mutCandidates: [KeyValuePair] = [] + /// 專門「用單元圖資料值來調查索引值」的辭典。 + private var mutValueUnigramIndexMap: [String: Int] = [:] + /// 專門「用給定鍵值來取對應的雙元圖陣列」的辭典。 + private var mutPrecedingBigramMap: [KeyValuePair: [Megrez.Bigram]] = [:] + /// 狀態標記變數,用來記載當前節點是否處於候選字詞鎖定狀態。 + private var mutCandidateFixed: Bool = false + /// 用來登記「當前選中的單元圖」的索引值的變數。 + private var mutSelectedUnigramIndex: Int = 0 + /// 用來登記要施加給「『被標記為選中狀態』的候選字詞」的複寫權重的數值。 + private let kSelectedCandidateScore: Double = 99 + /// 將當前節點列印成一個字串。 + public var description: String { + "(node,key:\(mutKey),fixed:\(mutCandidateFixed ? "true" : "false"),selected:\(mutSelectedUnigramIndex),\(mutUnigrams))" } - public func node(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) { - var unigrams = unigrams + /// 公開:候選字詞陣列(唯讀),以鍵值陣列的形式存在。 + var candidates: [KeyValuePair] { mutCandidates } + /// 公開:用來登記「當前選中的單元圖」的索引值的變數(唯讀)。 + var isCandidateFixed: Bool { mutCandidateFixed } + + /// 公開:鍵(唯讀)。 + var key: String { mutKey } + /// 公開:當前節點的當前被選中的候選字詞「在該節點內的」目前的權重(唯讀)。 + var score: Double { mutScore } + /// 公開:當前被選中的候選字詞的鍵值配對。 + var currentKeyValue: KeyValuePair { + mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePair() : mutCandidates[mutSelectedUnigramIndex] + } + + /// 公開:給出當前單元圖陣列內最高的權重數值。 + var highestUnigramScore: Double { mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score } + + /// 初期化一個節點。 + /// - Parameters: + /// - key: 索引鍵。 + /// - unigrams: 單元圖陣列。 + /// - bigrams: 雙元圖陣列(非必填)。 + public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) { mutKey = key - unigrams.sort { + mutUnigrams = unigrams + mutBigrams = bigrams + + mutUnigrams.sort { $0.score > $1.score } - if !mutUnigrams.isEmpty { - mutScore = mutUnigrams[0].score - } - - for (i, theGram) in unigrams.enumerated() { - mutValueUnigramIndexMap[theGram.keyValue.value] = i - mutCandidates.append(theGram.keyValue) + for (i, gram) in mutUnigrams.enumerated() { + mutValueUnigramIndexMap[gram.keyValue.value] = i + mutCandidates.append(gram.keyValue) } for gram in bigrams { @@ -80,11 +94,14 @@ extension Megrez { } } + /// 對擁有「給定的前述鍵值陣列」的節點提權。 + /// - Parameters: + /// - precedingKeyValues: 前述鍵值陣列。 public func primeNodeWith(precedingKeyValues: [KeyValuePair]) { var newIndex = mutSelectedUnigramIndex var max = mutScore - if !isCandidateFixed() { + if !isCandidateFixed { for neta in precedingKeyValues { let bigrams = mutPrecedingBigramMap[neta] ?? [] for bigram in bigrams { @@ -107,16 +124,17 @@ extension Megrez { } } - public func isCandidateFixed() -> Bool { mutCandidateFixed } - - public func candidates() -> [KeyValuePair] { mutCandidates } - + /// 選中位於給定索引位置的候選字詞。 + /// - Parameters: + /// - index: 索引位置。 + /// - fix: 是否將當前解點標記為「候選詞已鎖定」的狀態。 public func selectCandidateAt(index: Int = 0, fix: Bool = false) { mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index mutCandidateFixed = fix mutScore = kSelectedCandidateScore } + /// 重設該節點的候選字詞狀態。 public func resetCandidate() { mutSelectedUnigramIndex = 0 mutCandidateFixed = false @@ -125,16 +143,19 @@ extension Megrez { } } + /// 選中位於給定索引位置的候選字詞、且施加給定的權重。 + /// - Parameters: + /// - index: 索引位置。 + /// - score: 給定權重條件。 public func selectFloatingCandidateAt(index: Int, score: Double) { mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index mutCandidateFixed = false mutScore = score } - public func key() -> String { mutKey } - - public func score() -> Double { mutScore } - + /// 藉由給定的候選字詞字串,找出在庫的單元圖權重數值。沒有的話就找零。 + /// - Parameters: + /// - candidate: 給定的候選字詞字串。 public func scoreFor(candidate: String) -> Double { for unigram in mutUnigrams { if unigram.keyValue.value == candidate { @@ -144,14 +165,6 @@ extension Megrez { return 0.0 } - public func currentKeyValue() -> KeyValuePair { - mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePair() : mutCandidates[mutSelectedUnigramIndex] - } - - public func highestUnigramScore() -> Double { - mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score - } - public static func == (lhs: Node, rhs: Node) -> Bool { lhs.mutUnigrams == rhs.mutUnigrams && lhs.mutCandidates == rhs.mutCandidates && lhs.mutValueUnigramIndexMap == rhs.mutValueUnigramIndexMap diff --git a/Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift b/Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift index 383cdbc9..776f6442 100644 --- a/Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift +++ b/Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift @@ -24,19 +24,23 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { - // 這裡充其量只是框架,回頭實際使用時需要派生一個型別、且重寫相關函數。 - // 這裡寫了一點假內容,不然有些 Swift 格式化工具會破壞掉函數的參數設計。 + /// 語言模型框架,回頭實際使用時需要派生一個型別、且重寫相關函數。 open class LanguageModel { public init() {} + // 這裡寫了一點假內容,不然有些 Swift 格式化工具會破壞掉函數的參數設計。 + + /// 給定鍵,讓語言模型找給一筆單元圖。 open func unigramsFor(key: String) -> [Megrez.Unigram] { key.isEmpty ? [Megrez.Unigram]() : [Megrez.Unigram]() } + /// 給定當前鍵與前述鍵,讓語言模型找給一筆雙元圖。 open func bigramsForKeys(precedingKey: String, key: String) -> [Megrez.Bigram] { precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]() } + /// 給定鍵, open func hasUnigramsFor(key: String) -> Bool { key.count != 0 } diff --git a/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift b/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift index f934f1a9..cca1069f 100644 --- a/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift +++ b/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift @@ -24,17 +24,28 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { - @frozen public struct Bigram: Equatable { + /// 雙元圖。 + @frozen public struct Bigram: Equatable, CustomStringConvertible { + /// 當前鍵值。 public var keyValue: KeyValuePair + /// 前述鍵值。 public var precedingKeyValue: KeyValuePair + /// 權重。 public var score: Double - // var paired: String + /// 將當前雙元圖列印成一個字串。 + public var description: String { + "(" + keyValue.description + "|" + precedingKeyValue.description + "," + String(score) + ")" + } + /// 初期化一筆「雙元圖」。一筆雙元圖由一組前述鍵值配對、一組當前鍵值配對、與一筆權重數值組成。 + /// - Parameters: + /// - precedingKeyValue: 前述鍵值。 + /// - keyValue: 當前鍵值。 + /// - score: 權重(雙精度小數)。 public init(precedingKeyValue: KeyValuePair, keyValue: KeyValuePair, score: Double) { self.keyValue = keyValue self.precedingKeyValue = precedingKeyValue self.score = score - // paired = "(" + keyValue.paired + "|" + precedingKeyValue.paired + "," + String(score) + ")" } public func hash(into hasher: inout Hasher) { @@ -44,16 +55,6 @@ extension Megrez { // hasher.combine(paired) } - // static func getPairedBigrams(grams: [Bigram]) -> String { - // var arrOutputContent = [""] - // var index = 0 - // for gram in grams { - // arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.paired]) - // index += 1 - // } - // return "[" + String(grams.count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}" - // } - public static func == (lhs: Bigram, rhs: Bigram) -> Bool { lhs.precedingKeyValue == rhs.precedingKeyValue && lhs.keyValue == rhs.keyValue && lhs.score == rhs.score } @@ -62,13 +63,18 @@ extension Megrez { lhs.precedingKeyValue < rhs.precedingKeyValue || (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue)) } - - var description: String { - "\(keyValue):\(score)" - } - - var debugDescription: String { - "Bigram(keyValue: \(keyValue), score: \(score))" - } + } +} + +// MARK: - DumpDOT-related functions. + +extension Array where Element == Megrez.Bigram { + /// 將雙元圖陣列列印成一個字串。 + public var description: String { + var arrOutputContent = [""] + for (index, gram) in enumerated() { + arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description]) + } + return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}" } } diff --git a/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift b/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift index 793b5db6..62b0726c 100644 --- a/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift +++ b/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift @@ -24,21 +24,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { - @frozen public struct Unigram: Equatable { + /// 單元圖。 + @frozen public struct Unigram: Equatable, CustomStringConvertible { + /// 鍵值。 public var keyValue: KeyValuePair + /// 權重。 public var score: Double - // var paired: String + /// 將當前單元圖列印成一個字串。 + public var description: String { + "(" + keyValue.description + "," + String(score) + ")" + } + /// 初期化一筆「單元圖」。一筆單元圖由一組鍵值配對與一筆權重數值組成。 + /// - Parameters: + /// - keyValue: 鍵值。 + /// - score: 權重(雙精度小數)。 public init(keyValue: KeyValuePair, score: Double) { self.keyValue = keyValue self.score = score - // paired = "(" + keyValue.paired + "," + String(score) + ")" } public func hash(into hasher: inout Hasher) { hasher.combine(keyValue) hasher.combine(score) - // hasher.combine(paired) } // 這個函數不再需要了。 @@ -46,16 +54,6 @@ extension Megrez { a.score > b.score } - // static func getPairedUnigrams(grams: [Unigram]) -> String { - // var arrOutputContent = [""] - // var index = 0 - // for gram in grams { - // arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.paired]) - // index += 1 - // } - // return "[" + String(grams.count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}" - // } - public static func == (lhs: Unigram, rhs: Unigram) -> Bool { lhs.keyValue == rhs.keyValue && lhs.score == rhs.score } @@ -63,13 +61,18 @@ extension Megrez { public static func < (lhs: Unigram, rhs: Unigram) -> Bool { lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue) } - - var description: String { - "\(keyValue):\(score)" - } - - var debugDescription: String { - "Unigram(keyValue: \(keyValue), score: \(score))" - } + } +} + +// MARK: - DumpDOT-related functions. + +extension Array where Element == Megrez.Unigram { + /// 將單元圖陣列列印成一個字串。 + public var description: String { + var arrOutputContent = [""] + for (index, gram) in enumerated() { + arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description]) + } + return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}" } } diff --git a/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift b/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift index b10a9e83..851ab3df 100644 --- a/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift +++ b/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift @@ -24,21 +24,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extension Megrez { - @frozen public struct KeyValuePair: Equatable, Hashable, Comparable { + /// 鍵值配對。 + @frozen public struct KeyValuePair: Equatable, Hashable, Comparable, CustomStringConvertible { + /// 鍵。一般情況下用來放置讀音等可以用來作為索引的內容。 public var key: String + /// 資料值。 public var value: String - // public var paired: String + /// 將當前鍵值列印成一個字串。 + public var description: String { + "(" + key + "," + value + ")" + } + /// 初期化一組鍵值配對 + /// - Parameters: + /// - key: 鍵。一般情況下用來放置讀音等可以用來作為索引的內容。 + /// - value: 資料值。 public init(key: String = "", value: String = "") { self.key = key self.value = value - // paired = "(" + key + "," + value + ")" } public func hash(into hasher: inout Hasher) { hasher.combine(key) hasher.combine(value) - // hasher.combine(paired) } public static func == (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { @@ -60,13 +68,5 @@ extension Megrez { public static func >= (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { (lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value) } - - public var description: String { - "(\(key), \(value))" - } - - public var debugDescription: String { - "KeyValuePair(key: \(key), value: \(value))" - } } } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index eb37c133..4f66c33a 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */; }; 5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */; }; 5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */; }; - 5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */; }; 5B38F5A1281E2E49007D5F5D /* 1_BlockReadingBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */; }; 5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */; }; 5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */; }; @@ -300,7 +299,6 @@ 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_NodeAnchor.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_Span.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Unigram.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_Walker.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = ""; }; @@ -769,7 +767,6 @@ children = ( 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */, 6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */, - 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */, 6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */, 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */, 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */, @@ -1086,7 +1083,6 @@ 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */, 5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */, 5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */, - 5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */, 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */, 5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */, 5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */, From 6ef9e59c7f1c902ba4ead2a6df11adae4a8e705f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 17:02:38 +0800 Subject: [PATCH 05/22] KeyHandler // Enlarge walk range from 3 to 10. --- Source/Modules/ControllerModules/KeyHandler_Core.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Modules/ControllerModules/KeyHandler_Core.swift b/Source/Modules/ControllerModules/KeyHandler_Core.swift index 9a98c651..8ae21fb4 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Core.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Core.swift @@ -118,7 +118,7 @@ class KeyHandler { // of the best possible Mandarin characters given the input syllables, // using the Viterbi algorithm implemented in the Megrez library. // The walk() traces the grid to the end, hence no need to use .reversed() here. - _walkedNodes = _builder.walk(at: _builder.grid.width, nodesLimit: 3, balanced: true) + _walkedNodes = _builder.walk(at: _builder.grid.width, nodesLimit: 10, balanced: true) } func popOverflowComposingTextAndWalk() -> String { From e77d67370bf0c86b30332371bb363e3029b9e72c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 20 May 2022 17:40:12 +0800 Subject: [PATCH 06/22] Tekkon // Remove redundant data from documentation. --- .../ControllerModules/SyllableComposer.swift | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/Source/Modules/ControllerModules/SyllableComposer.swift b/Source/Modules/ControllerModules/SyllableComposer.swift index e391977d..827963f6 100644 --- a/Source/Modules/ControllerModules/SyllableComposer.swift +++ b/Source/Modules/ControllerModules/SyllableComposer.swift @@ -96,7 +96,7 @@ public struct Tekkon { ] /// 引擎僅接受這些記號作為介母 - public static let allowedsemivowels = ["ㄧ", "ㄨ", "ㄩ"] + public static let allowedSemivowels = ["ㄧ", "ㄨ", "ㄩ"] /// 引擎僅接受這些記號作為韻母 public static let allowedVowels = [ @@ -109,7 +109,7 @@ public struct Tekkon { /// 引擎僅接受這些記號作為注音(聲介韻調四個集合加起來) public static var allowedPhonabets: [String] { - allowedConsonants + allowedsemivowels + allowedVowels + allowedIntonations + allowedConsonants + allowedSemivowels + allowedVowels + allowedIntonations } // MARK: - Phonabet Structure @@ -132,18 +132,7 @@ public struct Tekkon { if !input.isEmpty { if allowedPhonabets.contains(String(input.reversed()[0])) { valueStorage = String(input.reversed()[0]) - if Tekkon.allowedConsonants.contains(value) { - type = .consonant - } else if Tekkon.allowedsemivowels.contains(value) { - type = .semivowel - } else if Tekkon.allowedVowels.contains(value) { - type = .vowel - } else if Tekkon.allowedIntonations.contains(value) { - type = .intonation - } else { - type = .null - valueStorage = "" - } + ensureType() } } } @@ -159,6 +148,23 @@ public struct Tekkon { /// - strWith: 要取代成的內容。 mutating func selfReplace(_ strOf: String, _ strWith: String = "") { valueStorage = valueStorage.replacingOccurrences(of: strOf, with: strWith) + ensureType() + } + + /// 用來自動更新自身的屬性值的函數。 + mutating func ensureType() { + if Tekkon.allowedConsonants.contains(value) { + type = .consonant + } else if Tekkon.allowedSemivowels.contains(value) { + type = .semivowel + } else if Tekkon.allowedVowels.contains(value) { + type = .vowel + } else if Tekkon.allowedIntonations.contains(value) { + type = .intonation + } else { + type = .null + valueStorage = "" + } } // MARK: - Misc Definitions @@ -192,7 +198,7 @@ public struct Tekkon { /// 注音並擊處理的對外介面以注拼槽(Syllable Composer)的形式存在。 /// 使用時需要單獨初期化為一個副本變數(因為是 Struct 所以必須得是變數)。 /// 注拼槽只有四格:聲、介、韻、調。 - /// @--DISCUSSION--@ + /// /// 因為是 String Literal,所以初期化時可以藉由 @input 參數指定初期已經傳入的按鍵訊號。 /// 還可以在初期化時藉由 @arrange 參數來指定注音排列(預設為「.ofDachen」大千佈局)。 @frozen public struct Composer: Equatable, Hashable, ExpressibleByStringLiteral { @@ -303,7 +309,7 @@ public struct Tekkon { // MARK: - Public Functions /// 用於檢測「某個輸入字符訊號的合規性」的函數。 - /// @--DISCUSSION--@ + /// /// 注意:回傳結果會受到當前注音排列 parser 屬性的影響。 /// - Parameters: /// - key: 傳入的 UniChar 內容。 @@ -338,7 +344,7 @@ public struct Tekkon { /// 接受傳入的按鍵訊號時的處理,處理對象為 String。 /// 另有同名函數可處理 UniChar 訊號。 - /// @--DISCUSSION--@ + /// /// 如果是諸如複合型注音排列的話,翻譯結果有可能為空,但翻譯過程已經處理好聲介韻調分配了。 /// - Parameters: /// - fromString: 傳入的 String 內容。 @@ -364,7 +370,7 @@ public struct Tekkon { /// 接受傳入的按鍵訊號時的處理,處理對象為 UniChar。 /// 其實也就是先將 UniChar 轉為 String 再交給某個同名異參的函數來處理而已。 - /// @--DISCUSSION--@ + /// /// 如果是諸如複合型注音排列的話,翻譯結果有可能為空,但翻譯過程已經處理好聲介韻調分配了。 /// - Parameters: /// - fromCharCode: 傳入的 UniChar 內容。 @@ -446,7 +452,7 @@ public struct Tekkon { /// 專門用來響應使用者摁下 BackSpace 按鍵時的行為。 /// 刪除順序:調、韻、介、聲。 - /// @--DISCUSSION--@ + /// /// 基本上就是按順序從游標前方開始往後刪。 public mutating func doBackSpace() { if [.ofHanyuPinyin, .ofSecondaryPinyin, .ofYalePinyin, .ofHualuoPinyin, .ofUniversalPinyin].contains(parser), @@ -490,7 +496,7 @@ public struct Tekkon { // 注拼槽對內處理用函數都在這一小節。 /// 根據目前的注音排列設定來翻譯傳入的 String 訊號。 - /// @--DISCUSSION--@ + /// /// 倚天或許氏鍵盤的處理函數會將分配過程代為處理過,此時回傳結果為空字串。 /// - Parameters: /// - key: 傳入的 String 訊號。 @@ -521,7 +527,7 @@ public struct Tekkon { } /// 倚天忘形注音排列比較麻煩,需要單獨處理。 - /// @--DISCUSSION--@ + /// /// 回傳結果是空字串的話,不要緊,因為該函數內部已經處理過分配過程了。 /// - Parameters: /// - key: 傳入的 String 訊號。 @@ -605,7 +611,7 @@ public struct Tekkon { } /// 許氏鍵盤與倚天忘形一樣同樣也比較麻煩,需要單獨處理。 - /// @--DISCUSSION--@ + /// /// 回傳結果是空的話,不要緊,因為該函數內部已經處理過分配過程了。 /// - Parameters: /// - key: 傳入的 String 訊號。 @@ -726,7 +732,7 @@ public struct Tekkon { } /// 大千忘形一樣同樣也比較麻煩,需要單獨處理。 - /// @--DISCUSSION--@ + /// /// 回傳結果是空的話,不要緊,因為該函數內部已經處理過分配過程了。 /// - Parameters: /// - key: 傳入的 String 訊號。 @@ -1237,7 +1243,7 @@ public struct Tekkon { // MARK: - Maps for Keyboard-to-Phonabet parsers /// 標準大千排列專用處理陣列。 - /// @--DISCUSSION--@ + /// /// 威注音輸入法 macOS 版使用了 Ukelele 佈局來完成對諸如倚天傳統等其它注音鍵盤排列的支援。 /// 如果要將鐵恨模組拿給別的平台的輸入法使用的話,恐怕需要針對這些注音鍵盤排列各自新增專用陣列才可以。 static let mapQwertyDachen: [String: String] = [ @@ -1248,7 +1254,7 @@ public struct Tekkon { ] /// 大千忘形排列專用處理陣列,但未包含全部的處理內容。 - /// @--DISCUSSION--@ + /// /// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。 /// 這裡提前對複音按鍵做處理,然後再用程式判斷介母類型、據此判斷是否需要做複音切換。 static let mapDachenCP26StaticKeys: [String: String] = [ @@ -1258,7 +1264,7 @@ public struct Tekkon { ] /// 許氏排列專用處理陣列,但未包含全部的映射內容。 - /// @--DISCUSSION--@ + /// /// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。 /// 這裡提前對複音按鍵做處理,然後再用程式判斷介母類型、據此判斷是否需要做複音切換。 static let mapHsuStaticKeys: [String: String] = [ @@ -1268,7 +1274,7 @@ public struct Tekkon { ] /// 倚天忘形排列預處理專用陣列,但未包含全部的映射內容。 - /// @--DISCUSSION--@ + /// /// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。 /// 這裡提前對ㄓ/ㄍ/ㄕ做處理,然後再用程式判斷介母類型、據此判斷是否需要換成ㄒ/ㄑ/ㄐ。 static let mapEten26StaticKeys: [String: String] = [ From 45c877b3f981086ae1da9bd0216b81d87d632cc5 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 20 May 2022 23:03:50 +0800 Subject: [PATCH 07/22] KeyHandler // Refactoring with modern Swift expressions. --- .../ControllerModules/InputState.swift | 2 +- .../ControllerModules/KeyHandler_Core.swift | 190 ++++++++---------- .../KeyHandler_HandleCandidate.swift | 8 +- .../KeyHandler_HandleInput.swift | 26 +-- .../ControllerModules/KeyHandler_Misc.swift | 10 +- .../ControllerModules/KeyHandler_States.swift | 64 +++--- .../Modules/IMEModules/ctlInputMethod.swift | 2 +- 7 files changed, 144 insertions(+), 158 deletions(-) diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index c262c933..5844c9e9 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -289,7 +289,7 @@ class InputState { "" } - func convertToInputting() -> Inputting { + var convertedToInputting: Inputting { let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) state.tooltip = tooltipForInputting return state diff --git a/Source/Modules/ControllerModules/KeyHandler_Core.swift b/Source/Modules/ControllerModules/KeyHandler_Core.swift index 8ae21fb4..3eb722d6 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Core.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Core.swift @@ -68,13 +68,35 @@ class KeyHandler { return InputMode.imeModeNULL } } - set { setInputMode(newValue.rawValue) } + set { + let isCHS: Bool = (newValue == InputMode.imeModeCHS) + + // 緊接著將新的簡繁輸入模式提報給 ctlInputMethod: + ctlInputMethod.currentInputMode = isCHS ? InputMode.imeModeCHS.rawValue : InputMode.imeModeCHT.rawValue + mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode + + // 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定: + if _inputMode != ctlInputMethod.currentInputMode { + // Reinitiate language models if necessary + _languageModel = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT + _userOverrideModel = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT + + // Synchronize the sub-languageModel state settings to the new LM. + syncBaseLMPrefs() + + // Create new grid builder and clear the composer. + createNewBuilder() + _composer.clear() + } + // 直接寫到衛星模組內,省得類型轉換 + _inputMode = ctlInputMethod.currentInputMode + } } public init() { _builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-") ensureParser() - setInputMode(ctlInputMethod.currentInputMode) + inputMode = InputMode(rawValue: ctlInputMethod.currentInputMode) ?? InputMode.imeModeNULL } func clear() { @@ -83,34 +105,6 @@ class KeyHandler { _walkedNodes.removeAll() } - func setInputMode(_ value: String) { - // 下面這句的「isKindOfClass」是做類型檢查, - // 為了應對出現輸入法 plist 被改壞掉這樣的極端情況。 - let isCHS: Bool = (value == InputMode.imeModeCHS.rawValue) - - // 緊接著將新的簡繁輸入模式提報給 ctlInputMethod: - ctlInputMethod.currentInputMode = isCHS ? InputMode.imeModeCHS.rawValue : InputMode.imeModeCHT.rawValue - mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode - - // 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定: - if _inputMode != ctlInputMethod.currentInputMode { - // Reinitiate language models if necessary - setInputModesToLM(isCHS: isCHS) - - // Synchronize the sub-languageModel state settings to the new LM. - syncBaseLMPrefs() - - // Create new grid builder. - createNewBuilder() - - if !_composer.isEmpty { - _composer.clear() - } - } - // 直接寫到衛星模組內,省得類型轉換 - _inputMode = ctlInputMethod.currentInputMode - } - // MARK: - Functions dealing with Megrez. func walk() { @@ -121,7 +115,7 @@ class KeyHandler { _walkedNodes = _builder.walk(at: _builder.grid.width, nodesLimit: 10, balanced: true) } - func popOverflowComposingTextAndWalk() -> String { + var popOverflowComposingTextAndWalk: String { // In ideal situations we can allow users to type infinitely in a buffer. // However, Viberti algorithm has a complexity of O(N^2), the walk will // become slower as the number of nodes increase. Therefore, we need to @@ -153,7 +147,7 @@ class KeyHandler { } func fixNode(value: String) { - let cursorIndex: Int = getActualCandidateCursorIndex() + let cursorIndex: Int = actualCandidateCursorIndex let selectedNode: Megrez.NodeAnchor = _builder.grid.fixNodeSelectedCandidate( location: cursorIndex, value: value ) @@ -192,16 +186,16 @@ class KeyHandler { if nextPosition >= cursorIndex { break } nextPosition += node.spanningLength } - if nextPosition <= getBuilderLength() { - setBuilderCursorIndex(value: nextPosition) + if nextPosition <= builderLength { + builderCursorIndex = nextPosition } } } - func getCandidatesArray() -> [String] { + var candidatesArray: [String] { var arrCandidates: [String] = [] var arrNodes: [Megrez.NodeAnchor] = [] - arrNodes.append(contentsOf: getRawNodes()) + arrNodes.append(contentsOf: rawNodes) /// 原理:nodes 這個回饋結果包含一堆子陣列,分別對應不同詞長的候選字。 /// 這裡先對陣列排序、讓最長候選字的子陣列的優先權最高。 @@ -228,7 +222,7 @@ class KeyHandler { mgrPrefs.useSCPCTypingMode ? "" : _userOverrideModel.suggest( - walkedNodes: _walkedNodes, cursorIndex: getBuilderCursorIndex(), + walkedNodes: _walkedNodes, cursorIndex: builderCursorIndex, timestamp: NSDate().timeIntervalSince1970 ) @@ -236,9 +230,9 @@ class KeyHandler { IME.prtDebugIntel( "UOM: Suggestion retrieved, overriding the node score of the selected candidate.") _builder.grid.overrideNodeScoreForSelectedCandidate( - location: getActualCandidateCursorIndex(), + location: actualCandidateCursorIndex, value: overrideValue, - overridingScore: findHighestScore(nodes: getRawNodes(), epsilon: kEpsilon) + overridingScore: findHighestScore(nodes: rawNodes, epsilon: kEpsilon) ) } else { IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.") @@ -258,69 +252,6 @@ class KeyHandler { return highestScore + epsilon } - // MARK: - Extracted methods and functions (Megrez). - - func isBuilderEmpty() -> Bool { _builder.grid.width == 0 } - - func getRawNodes() -> [Megrez.NodeAnchor] { - /// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。 - /// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的,但目前 Megrez 暫時缺乏對該特性的支援。 - /// 所以暫時只能將威注音的游標後置風格描述成「跟 Windows 版雅虎奇摩注音一致」。 - mgrPrefs.setRearCursorMode - ? _builder.grid.nodesCrossingOrEndingAt(location: getActualCandidateCursorIndex()) - : _builder.grid.nodesEndingAt(location: getActualCandidateCursorIndex()) - } - - func setInputModesToLM(isCHS: Bool) { - _languageModel = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT - _userOverrideModel = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT - } - - func syncBaseLMPrefs() { - _languageModel.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled - _languageModel.isCNSEnabled = mgrPrefs.cns11643Enabled - _languageModel.isSymbolEnabled = mgrPrefs.symbolInputEnabled - } - - func createNewBuilder() { - // Each Mandarin syllable is separated by a hyphen. - _builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-") - } - - func currentReadings() -> [String] { _builder.readings } - - func ifLangModelHasUnigrams(forKey reading: String) -> Bool { - _languageModel.hasUnigramsFor(key: reading) - } - - func insertReadingToBuilderAtCursor(reading: String) { - _builder.insertReadingAtCursor(reading: reading) - } - - func setBuilderCursorIndex(value: Int) { - _builder.cursorIndex = value - } - - func getBuilderCursorIndex() -> Int { - _builder.cursorIndex - } - - func getBuilderLength() -> Int { - _builder.length - } - - func deleteBuilderReadingInFrontOfCursor() { - _builder.deleteReadingAtTheRearOfCursor() - } - - func deleteBuilderReadingToTheFrontOfCursor() { - _builder.deleteReadingToTheFrontOfCursor() - } - - func getKeyLengthAtIndexZero() -> Int { - _walkedNodes[0].node?.currentKeyValue.value.count ?? 0 - } - // MARK: - Extracted methods and functions (Tekkon). func ensureParser() { @@ -357,4 +288,59 @@ class KeyHandler { } _composer.clear() } + + // MARK: - Extracted methods and functions (Megrez). + + var isBuilderEmpty: Bool { _builder.grid.width == 0 } + + var rawNodes: [Megrez.NodeAnchor] { + /// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。 + /// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的,但目前 Megrez 暫時缺乏對該特性的支援。 + /// 所以暫時只能將威注音的游標後置風格描述成「跟 Windows 版雅虎奇摩注音一致」。 + mgrPrefs.setRearCursorMode + ? _builder.grid.nodesCrossingOrEndingAt(location: actualCandidateCursorIndex) + : _builder.grid.nodesEndingAt(location: actualCandidateCursorIndex) + } + + func syncBaseLMPrefs() { + _languageModel.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled + _languageModel.isCNSEnabled = mgrPrefs.cns11643Enabled + _languageModel.isSymbolEnabled = mgrPrefs.symbolInputEnabled + } + + func createNewBuilder() { + // Each Mandarin syllable is separated by a hyphen. + _builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-") + } + + var currentReadings: [String] { _builder.readings } + + func ifLangModelHasUnigrams(forKey reading: String) -> Bool { + _languageModel.hasUnigramsFor(key: reading) + } + + func insertReadingToBuilderAtCursor(reading: String) { + _builder.insertReadingAtCursor(reading: reading) + } + + var builderCursorIndex: Int { + get { _builder.cursorIndex } + set { _builder.cursorIndex = newValue } + } + + var builderLength: Int { + _builder.length + } + + func deleteBuilderReadingInFrontOfCursor() { + _builder.deleteReadingAtTheRearOfCursor() + } + + func deleteBuilderReadingToTheFrontOfCursor() { + _builder.deleteReadingToTheFrontOfCursor() + } + + var keyLengthAtIndexZero: Int { + _walkedNodes[0].node?.currentKeyValue.value.count ?? 0 + } } diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift index f2aefffd..dc0d4bbe 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift @@ -47,16 +47,16 @@ extension KeyHandler { if cancelCandidateKey { if (state is InputState.AssociatedPhrases) || mgrPrefs.useSCPCTypingMode - || isBuilderEmpty() + || isBuilderEmpty { // 如果此時發現當前組字緩衝區為真空的情況的話, // 就將當前的組字緩衝區析構處理、強制重設輸入狀態。 // 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。 - // 所以這裡需要對 isBuilderEmpty() 做判定。 + // 所以這裡需要對 isBuilderEmpty 做判定。 clear() stateCallback(InputState.EmptyIgnoringPreviousState()) } else { - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) } return true } @@ -320,7 +320,7 @@ extension KeyHandler { punctuationNamePrefix = "_punctuation_" } - let parser = getCurrentMandarinParser() + let parser = currentMandarinParser let arrCustomPunctuations: [String] = [ punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 2b0df485..3124f8c4 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -135,7 +135,7 @@ extension KeyHandler { ) { return true } - state = marking.convertToInputting() + state = marking.convertedToInputting stateCallback(state) } @@ -154,7 +154,7 @@ extension KeyHandler { // update the composing buffer. let composeReading = _composer.hasToneMarker() if !composeReading { - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) return true } } @@ -166,7 +166,7 @@ extension KeyHandler { // However, Swift does not support "|=". composeReading = composeReading || (!_composer.isEmpty && (input.isSpace || input.isEnter)) if composeReading { - if input.isSpace && !_composer.hasToneMarker() { + if input.isSpace, !_composer.hasToneMarker() { _composer.receiveKey(fromString: " ") // 補上空格。 } let reading = _composer.getComposition() @@ -176,7 +176,7 @@ extension KeyHandler { IME.prtDebugIntel("B49C0979:語彙庫內無「\(reading)」的匹配記錄。") errorCallback() _composer.clear() - stateCallback((getBuilderLength() == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState()) + stateCallback((builderLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState) return true } @@ -184,7 +184,7 @@ extension KeyHandler { insertReadingToBuilderAtCursor(reading: reading) // ... then walk the grid... - let poppedText = popOverflowComposingTextAndWalk() + let poppedText = popOverflowComposingTextAndWalk // ... get and tweak override model suggestion if possible... dealWithOverrideModelSuggestions() @@ -192,7 +192,7 @@ extension KeyHandler { // ... then update the text. _composer.clear() - let inputting = buildInputtingState() + let inputting = buildInputtingState inputting.poppedText = poppedText stateCallback(inputting) @@ -233,7 +233,7 @@ extension KeyHandler { // but does not compose. Only sequences such as "ㄧˊ", "ˊㄧˊ", "ˊㄧˇ", or "ˊㄧ " // would compose. if keyConsumedByReading { - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) return true } @@ -247,7 +247,7 @@ extension KeyHandler { if input.isSpace { // If the Space key is NOT set to be a selection key if input.isShiftHold || !mgrPrefs.chooseCandidateUsingSpace { - if getBuilderCursorIndex() >= getBuilderLength() { + if builderCursorIndex >= builderLength { let composingBuffer = currentState.composingBuffer if !composingBuffer.isEmpty { stateCallback(InputState.Committing(poppedText: composingBuffer)) @@ -257,8 +257,8 @@ extension KeyHandler { stateCallback(InputState.Empty()) } else if ifLangModelHasUnigrams(forKey: " ") { insertReadingToBuilderAtCursor(reading: " ") - let poppedText = popOverflowComposingTextAndWalk() - let inputting = buildInputtingState() + let poppedText = popOverflowComposingTextAndWalk + let inputting = buildInputtingState inputting.poppedText = poppedText stateCallback(inputting) } @@ -355,8 +355,8 @@ extension KeyHandler { if ifLangModelHasUnigrams(forKey: "_punctuation_list") { if _composer.isEmpty { insertReadingToBuilderAtCursor(reading: "_punctuation_list") - let poppedText: String! = popOverflowComposingTextAndWalk() - let inputting = buildInputtingState() + let poppedText: String! = popOverflowComposingTextAndWalk + let inputting = buildInputtingState inputting.poppedText = poppedText stateCallback(inputting) stateCallback(buildCandidate(state: inputting, useVerticalMode: input.useVerticalMode)) @@ -392,7 +392,7 @@ extension KeyHandler { punctuationNamePrefix = "_punctuation_" } - let parser = getCurrentMandarinParser() + let parser = currentMandarinParser let arrCustomPunctuations: [String] = [ punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), ] diff --git a/Source/Modules/ControllerModules/KeyHandler_Misc.swift b/Source/Modules/ControllerModules/KeyHandler_Misc.swift index 449a43bd..e0b7594d 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Misc.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Misc.swift @@ -29,22 +29,22 @@ import Cocoa // MARK: - § Misc functions. extension KeyHandler { - func getCurrentMandarinParser() -> String { + var currentMandarinParser: String { mgrPrefs.mandarinParserName + "_" } - func getActualCandidateCursorIndex() -> Int { - var cursorIndex = getBuilderCursorIndex() + var actualCandidateCursorIndex: Int { + var cursorIndex = builderCursorIndex // Windows Yahoo Kimo IME style, phrase is *at the rear of* the cursor. // (i.e. the cursor is always *before* the phrase.) // This is different from MS Phonetics IME style ... // ... since Windows Yahoo Kimo allows "node crossing". if (mgrPrefs.setRearCursorMode - && (cursorIndex < getBuilderLength())) + && (cursorIndex < builderLength)) || cursorIndex == 0 { if cursorIndex == 0, !mgrPrefs.setRearCursorMode { - cursorIndex += getKeyLengthAtIndexZero() + cursorIndex += keyLengthAtIndexZero } else { cursorIndex += 1 } diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index c5bd47af..50be9612 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -31,14 +31,14 @@ import Cocoa extension KeyHandler { // MARK: - 構築狀態(State Building) - func buildInputtingState() -> InputState.Inputting { + var buildInputtingState: InputState.Inputting { // "Updating the composing buffer" means to request the client // to "refresh" the text input buffer with our "composing text" var composingBuffer = "" var composedStringCursorIndex = 0 var readingCursorIndex = 0 - let builderCursorIndex = getBuilderCursorIndex() + let builderCursorIndex = builderCursorIndex for theAnchor in _walkedNodes { guard let node = theAnchor.node else { @@ -106,7 +106,7 @@ extension KeyHandler { InputState.ChoosingCandidate( composingBuffer: currentState.composingBuffer, cursorIndex: currentState.cursorIndex, - candidates: getCandidatesArray(), + candidates: candidatesArray, useVerticalMode: useVerticalMode ) } @@ -115,7 +115,7 @@ extension KeyHandler { // 這次重寫時,針對「buildAssociatePhraseStateWithKey」這個(用以生成帶有 // 聯想詞候選清單的結果的狀態回呼的)函數進行了小幅度的重構處理,使其始終 - // 可以從 ObjC 部分的「buildAssociatePhraseArray」函數獲取到一個內容類型 + // 可以從 Core 部分的「buildAssociatePhraseArray」函數獲取到一個內容類型 // 為「String」的標準 Swift 陣列。這樣一來,該聯想詞狀態回呼函數將始終能 // 夠傳回正確的結果形態、永遠也無法傳回 nil。於是,所有在用到該函數時以 // 回傳結果類型判斷作為合法性判斷依據的函數,全都將依據改為檢查傳回的陣列 @@ -139,7 +139,7 @@ extension KeyHandler { errorCallback: @escaping () -> Void ) -> Bool { if input.isESC { - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) return true } @@ -152,7 +152,7 @@ extension KeyHandler { return true } } - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) return true } @@ -168,7 +168,7 @@ extension KeyHandler { readings: state.readings ) marking.tooltipForInputting = state.tooltipForInputting - stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking) + stateCallback(marking.markedRange.length == 0 ? marking.convertedToInputting : marking) } else { IME.prtDebugIntel("1149908D") errorCallback() @@ -191,7 +191,7 @@ extension KeyHandler { readings: state.readings ) marking.tooltipForInputting = state.tooltipForInputting - stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking) + stateCallback(marking.markedRange.length == 0 ? marking.convertedToInputting : marking) } else { IME.prtDebugIntel("9B51408D") errorCallback() @@ -217,8 +217,8 @@ extension KeyHandler { if _composer.isEmpty { insertReadingToBuilderAtCursor(reading: customPunctuation) - let poppedText = popOverflowComposingTextAndWalk() - let inputting = buildInputtingState() + let poppedText = popOverflowComposingTextAndWalk + let inputting = buildInputtingState inputting.poppedText = poppedText stateCallback(inputting) @@ -273,7 +273,7 @@ extension KeyHandler { ) -> Bool { guard state is InputState.Inputting else { return false } - var composingBuffer = currentReadings().joined(separator: "-") + var composingBuffer = currentReadings.joined(separator: "-") if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin { composingBuffer = restoreToneOneInZhuyinKey(target: composingBuffer) // 恢復陰平標記 composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) // 注音轉拼音 @@ -341,7 +341,7 @@ extension KeyHandler { if _composer.hasToneMarker(withNothingElse: true) { _composer.clear() } else if _composer.isEmpty { - if getBuilderCursorIndex() >= 0 { + if builderCursorIndex >= 0 { deleteBuilderReadingInFrontOfCursor() walk() } else { @@ -354,10 +354,10 @@ extension KeyHandler { _composer.doBackSpace() } - if _composer.isEmpty, getBuilderLength() == 0 { + if _composer.isEmpty, builderLength == 0 { stateCallback(InputState.EmptyIgnoringPreviousState()) } else { - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) } return true } @@ -372,10 +372,10 @@ extension KeyHandler { guard state is InputState.Inputting else { return false } if _composer.isEmpty { - if getBuilderCursorIndex() != getBuilderLength() { + if builderCursorIndex != builderLength { deleteBuilderReadingToTheFrontOfCursor() walk() - let inputting = buildInputtingState() + let inputting = buildInputtingState // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 if inputting.composingBuffer.isEmpty { stateCallback(InputState.EmptyIgnoringPreviousState()) @@ -428,9 +428,9 @@ extension KeyHandler { return true } - if getBuilderCursorIndex() != 0 { - setBuilderCursorIndex(value: 0) - stateCallback(buildInputtingState()) + if builderCursorIndex != 0 { + builderCursorIndex = 0 + stateCallback(buildInputtingState) } else { IME.prtDebugIntel("66D97F90") errorCallback() @@ -456,9 +456,9 @@ extension KeyHandler { return true } - if getBuilderCursorIndex() != getBuilderLength() { - setBuilderCursorIndex(value: getBuilderLength()) - stateCallback(buildInputtingState()) + if builderCursorIndex != builderLength { + builderCursorIndex = builderLength + stateCallback(buildInputtingState) } else { IME.prtDebugIntel("9B69908E") errorCallback() @@ -490,10 +490,10 @@ extension KeyHandler { // If reading is not empty, we cancel the reading. if !_composer.isEmpty { _composer.clear() - if getBuilderLength() == 0 { + if builderLength == 0 { stateCallback(InputState.EmptyIgnoringPreviousState()) } else { - stateCallback(buildInputtingState()) + stateCallback(buildInputtingState) } } } @@ -526,7 +526,7 @@ extension KeyHandler { composingBuffer: currentState.composingBuffer, cursorIndex: currentState.cursorIndex, markerIndex: UInt(nextPosition), - readings: currentReadings() + readings: currentReadings ) marking.tooltipForInputting = currentState.tooltip stateCallback(marking) @@ -536,9 +536,9 @@ extension KeyHandler { stateCallback(state) } } else { - if getBuilderCursorIndex() < getBuilderLength() { - setBuilderCursorIndex(value: getBuilderCursorIndex() + 1) - stateCallback(buildInputtingState()) + if builderCursorIndex < builderLength { + builderCursorIndex += 1 + stateCallback(buildInputtingState) } else { IME.prtDebugIntel("A96AAD58") errorCallback() @@ -575,7 +575,7 @@ extension KeyHandler { composingBuffer: currentState.composingBuffer, cursorIndex: currentState.cursorIndex, markerIndex: UInt(previousPosition), - readings: currentReadings() + readings: currentReadings ) marking.tooltipForInputting = currentState.tooltip stateCallback(marking) @@ -585,9 +585,9 @@ extension KeyHandler { stateCallback(state) } } else { - if getBuilderCursorIndex() > 0 { - setBuilderCursorIndex(value: getBuilderCursorIndex() - 1) - stateCallback(buildInputtingState()) + if builderCursorIndex > 0 { + builderCursorIndex -= 1 + stateCallback(buildInputtingState) } else { IME.prtDebugIntel("7045E6F3") errorCallback() diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 621af4ab..5e677281 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -648,7 +648,7 @@ extension ctlInputMethod: ctlCandidateDelegate { let selectedValue = state.candidates[Int(index)] keyHandler.fixNode(value: selectedValue) - let inputting = keyHandler.buildInputtingState() + let inputting = keyHandler.buildInputtingState if mgrPrefs.useSCPCTypingMode { keyHandler.clear() From b41068c0cbd4e48d14d8bc613589dfa3c0a2598c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 00:22:50 +0800 Subject: [PATCH 08/22] ctlIME // Block unprintable ASCII chars from being committed out. --- Source/Modules/IMEModules/ctlInputMethod.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 5e677281..bfe39811 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -260,8 +260,20 @@ extension ctlInputMethod { if buffer.isEmpty { return } + + var bufferOutput = "" + + // 防止輸入法輸出不可列印的字元。 + for theChar in buffer { + if let charCode = theChar.utf16.first { + if !(theChar.isASCII && !(charCode.isPrintable())) { + bufferOutput += String(theChar) + } + } + } + (client as? IMKTextInput)?.insertText( - buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + bufferOutput, replacementRange: NSRange(location: NSNotFound, length: NSNotFound) ) } From 1eff1cece884a8df4cd090f1d47fcfdbb37564aa Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 00:04:37 +0800 Subject: [PATCH 09/22] ctlIME & InputState // Handle InputState.SymbolTable. --- .../Modules/ControllerModules/InputState.swift | 16 ++++++++++++++++ Source/Modules/IMEModules/ctlInputMethod.swift | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index 5844c9e9..6b485755 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -415,6 +415,22 @@ class InputState { ) } + // InputState.SymbolTable 這個狀態比較特殊,不能把真空組字區交出去。 + // 不然的話,在絕大多數終端機類應用當中、以及在 MS Word 等軟體當中 + // 會出現符號選字窗無法響應方向鍵的問題。 + // 如有誰要修奇摩注音的一點通選單的話,修復原理也是一樣的。 + // Crediting Qwertyyb: https://github.com/qwertyyb/Fire/issues/55#issuecomment-1133497700 + override var attributedString: NSAttributedString { + let attributedSting = NSAttributedString( + string: " ", + attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0, + ] + ) + return attributedSting + } + override var description: String { "" } diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index bfe39811..1c5c3372 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -234,6 +234,8 @@ extension ctlInputMethod { handle(state: newState, previous: previous, client: client) } else if let newState = newState as? InputState.AssociatedPhrases { handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.SymbolTable { + handle(state: newState, previous: previous, client: client) } } @@ -411,6 +413,22 @@ extension ctlInputMethod { show(candidateWindowWith: state, client: client) } + private func handle(state: InputState.SymbolTable, previous _: InputState, client: Any?) { + hideTooltip() + guard let client = client as? IMKTextInput else { + ctlCandidateCurrent?.visible = false + return + } + + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put this composing buffer + client.setMarkedText( + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + show(candidateWindowWith: state, client: client) + } + private func handle(state: InputState.AssociatedPhrases, previous _: InputState, client: Any?) { hideTooltip() guard let client = client as? IMKTextInput else { From dc3c1e2f1d2c73e623be82a4d473e63110796151 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 00:38:19 +0800 Subject: [PATCH 10/22] KeyHandler // ReEnable libChewing symbol menu by default. --- Source/Modules/ControllerModules/KeyHandler_HandleInput.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 3124f8c4..a6610dfc 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -351,7 +351,7 @@ extension KeyHandler { // MARK: Punctuation list if input.isSymbolMenuPhysicalKey && !input.isShiftHold { - if !input.isOptionHold { + if input.isOptionHold { if ifLangModelHasUnigrams(forKey: "_punctuation_list") { if _composer.isEmpty { insertReadingToBuilderAtCursor(reading: "_punctuation_list") From 384a4138f29a27c88cc213e743ccef96fd0b5f22 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 09:04:31 +0800 Subject: [PATCH 11/22] InputHandler // Rebranding to InputSignal, plus refactoring. --- .../{InputHandler.swift => InputSignal.swift} | 44 ++++++++----------- .../KeyHandler_HandleCandidate.swift | 2 +- .../KeyHandler_HandleInput.swift | 2 +- .../ControllerModules/KeyHandler_States.swift | 6 +-- .../Modules/IMEModules/ctlInputMethod.swift | 4 +- vChewing.xcodeproj/project.pbxproj | 8 ++-- 6 files changed, 29 insertions(+), 37 deletions(-) rename Source/Modules/ControllerModules/{InputHandler.swift => InputSignal.swift} (85%) diff --git a/Source/Modules/ControllerModules/InputHandler.swift b/Source/Modules/ControllerModules/InputSignal.swift similarity index 85% rename from Source/Modules/ControllerModules/InputHandler.swift rename to Source/Modules/ControllerModules/InputSignal.swift index 67360a2a..4bc1acbb 100644 --- a/Source/Modules/ControllerModules/InputHandler.swift +++ b/Source/Modules/ControllerModules/InputSignal.swift @@ -117,7 +117,7 @@ enum CharCode: UInt /* 16 */ { // ... but only focuses on which physical key is pressed. } -class InputHandler: NSObject { +struct InputSignal: CustomStringConvertible { private(set) var useVerticalMode: Bool private(set) var inputText: String? private(set) var inputTextIgnoringModifiers: String? @@ -125,15 +125,15 @@ class InputHandler: NSObject { private(set) var keyCode: UInt16 private var isFlagChanged: Bool private var flags: NSEvent.ModifierFlags - private var cursorForwardKey: KeyCode - private var cursorBackwardKey: KeyCode - private var extraChooseCandidateKey: KeyCode - private var extraChooseCandidateKeyReverse: KeyCode - private var absorbedArrowKey: KeyCode - private var verticalModeOnlyChooseCandidateKey: KeyCode + private var cursorForwardKey: KeyCode = .kNone + private var cursorBackwardKey: KeyCode = .kNone + private var extraChooseCandidateKey: KeyCode = .kNone + private var extraChooseCandidateKeyReverse: KeyCode = .kNone + private var absorbedArrowKey: KeyCode = .kNone + private var verticalModeOnlyChooseCandidateKey: KeyCode = .kNone private(set) var emacsKey: vChewingEmacsKey - init( + public init( inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil ) { @@ -150,17 +150,11 @@ class InputHandler: NSObject { emacsKey = EmacsKeyHelper.detect( charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags ) - // Define Arrow Keys - cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow - cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow - extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow - extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow - absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow - verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone - super.init() + // Define Arrow Keys in the same way above. + defineArrowKeys() } - init(event: NSEvent, isVerticalMode: Bool) { + public init(event: NSEvent, isVerticalMode: Bool) { inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( event.charactersIgnoringModifiers ?? "") @@ -181,22 +175,20 @@ class InputHandler: NSObject { charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags ) // Define Arrow Keys in the same way above. + defineArrowKeys() + } + + mutating func defineArrowKeys() { cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone - super.init() } - override var description: String { - charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") - inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( - inputTextIgnoringModifiers ?? "") - return - "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>" + var description: String { + "" } // 除了 ANSI charCode 以外,其餘一律過濾掉,免得純 Swift 版 KeyHandler 被餵屎。 @@ -365,7 +357,7 @@ enum vChewingEmacsKey: UInt16 { case nextPage = 22 // V } -class EmacsKeyHelper: NSObject { +enum EmacsKeyHelper { static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) if flags.contains(.control) { diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift index dc0d4bbe..0c73494b 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift @@ -31,7 +31,7 @@ import Cocoa extension KeyHandler { func handleCandidate( state: InputState, - input: InputHandler, + input: InputSignal, stateCallback: @escaping (InputState) -> Void, errorCallback: @escaping () -> Void ) -> Bool { diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index a6610dfc..dbf50d43 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -30,7 +30,7 @@ import Cocoa extension KeyHandler { func handle( - input: InputHandler, + input: InputSignal, state: InputState, stateCallback: @escaping (InputState) -> Void, errorCallback: @escaping () -> Void diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index 50be9612..c20ef4a5 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -134,7 +134,7 @@ extension KeyHandler { func handleMarkingState( _ state: InputState.Marking, - input: InputHandler, + input: InputSignal, stateCallback: @escaping (InputState) -> Void, errorCallback: @escaping () -> Void ) -> Bool { @@ -504,7 +504,7 @@ extension KeyHandler { func handleForward( state: InputState, - input: InputHandler, + input: InputSignal, stateCallback: @escaping (InputState) -> Void, errorCallback: @escaping () -> Void ) -> Bool { @@ -553,7 +553,7 @@ extension KeyHandler { func handleBackward( state: InputState, - input: InputHandler, + input: InputSignal, stateCallback: @escaping (InputState) -> Void, errorCallback: @escaping () -> Void ) -> Bool { diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 1c5c3372..300c2a26 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -51,7 +51,7 @@ class ctlInputMethod: IMKInputController { // 想讓 KeyHandler 能夠被外界調查狀態與參數的話,就得對 KeyHandler 做常態處理。 // 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。 - // 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 InputHandler 無法協同處理。 + // 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 InputSignal 無法協同處理。 // 所以才需要「currentKeyHandler」這個假 KeyHandler。 // 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。 static var currentKeyHandler: KeyHandler = .init() @@ -182,7 +182,7 @@ class ctlInputMethod: IMKInputController { IME.areWeUsingOurOwnPhraseEditor = false } - let input = InputHandler(event: event, isVerticalMode: useVerticalMode) + let input = InputSignal(event: event, isVerticalMode: useVerticalMode) // 無法列印的訊號輸入,一概不作處理。 // 這個過程不能放在 KeyHandler 內,否則不會起作用。 diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 4f66c33a..88c7a454 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -106,7 +106,7 @@ 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; }; 6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; }; D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; }; - D456576E279E4F7B00DF6BC9 /* InputHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* InputHandler.swift */; }; + D456576E279E4F7B00DF6BC9 /* InputSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* InputSignal.swift */; }; D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */; }; @@ -311,7 +311,7 @@ 6ACA41F315FC1D9000935EF6 /* Installer-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Installer-Prefix.pch"; path = "Installer/Installer-Prefix.pch"; sourceTree = SOURCE_ROOT; }; D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "vChewing-Bridging-Header.h"; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - D456576D279E4F7B00DF6BC9 /* InputHandler.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputHandler.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D456576D279E4F7B00DF6BC9 /* InputSignal.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputSignal.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputState.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = main.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; @@ -411,7 +411,7 @@ 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */, D4E569DA27A34CC100AC2CEF /* CTools.h */, D4E569DB27A34CC100AC2CEF /* CTools.m */, - D456576D279E4F7B00DF6BC9 /* InputHandler.swift */, + D456576D279E4F7B00DF6BC9 /* InputSignal.swift */, D461B791279DAC010070E734 /* InputState.swift */, 5BD0113C2818543900609769 /* KeyHandler_Core.swift */, 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */, @@ -1051,7 +1051,7 @@ D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */, 5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */, 5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */, - D456576E279E4F7B00DF6BC9 /* InputHandler.swift in Sources */, + D456576E279E4F7B00DF6BC9 /* InputSignal.swift in Sources */, 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */, 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */, From f0bb232d353956a6caa258f299680c2d96dd365f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 10:43:41 +0800 Subject: [PATCH 12/22] InputState // Typo fix: attributedSting -> attributedString; etc. --- .../ControllerModules/InputState.swift | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index 6b485755..02a54025 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -26,6 +26,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa +// 註:所有 InputState 型別均不適合使用 Struct,因為 Struct 無法相互繼承派生。 + /// Represents the states for the input method controller. /// /// An input method is actually a finite state machine. It receives the inputs @@ -44,10 +46,10 @@ import Cocoa /// create a new state object to replace the current state instead of modifying /// the existing one. /// -/// vChewing's input controller has following possible states: +/// The input controller has following possible states: /// -/// - Deactivated: The user is not using vChewing yet. -/// - Empty: The user has switched to vChewing but did not input anything yet, +/// - Deactivated: The user is not using the input method yet. +/// - Empty: The user has switched to this input method but inputted nothing yet, /// or, he or she has committed text into the client apps and starts a new /// input phase. /// - Committing: The input controller is sending text to the client apps. @@ -136,14 +138,14 @@ class InputState { } var attributedString: NSAttributedString { - let attributedSting = NSAttributedString( + let attributedString = NSAttributedString( string: composingBuffer, attributes: [ .underlineStyle: NSUnderlineStyle.single.rawValue, .markedClauseSegment: 0, ] ) - return attributedSting + return attributedString } override var description: String { @@ -257,22 +259,22 @@ class InputState { } var attributedString: NSAttributedString { - let attributedSting = NSMutableAttributedString(string: composingBuffer) + let attributedString = NSMutableAttributedString(string: composingBuffer) let end = markedRange.location + markedRange.length - attributedSting.setAttributes( + attributedString.setAttributes( [ .underlineStyle: NSUnderlineStyle.single.rawValue, .markedClauseSegment: 0, ], range: NSRange(location: 0, length: markedRange.location) ) - attributedSting.setAttributes( + attributedString.setAttributes( [ .underlineStyle: NSUnderlineStyle.thick.rawValue, .markedClauseSegment: 1, ], range: markedRange ) - attributedSting.setAttributes( + attributedString.setAttributes( [ .underlineStyle: NSUnderlineStyle.single.rawValue, .markedClauseSegment: 2, @@ -282,7 +284,7 @@ class InputState { length: (composingBuffer as NSString).length - end ) ) - return attributedSting + return attributedString } override var description: String { @@ -296,7 +298,7 @@ class InputState { } var validToWrite: Bool { - /// vChewing allows users to input a string whose length differs + /// The input method allows users to input a string whose length differs /// from the amount of Bopomofo readings. In this case, the range /// in the composing buffer and the readings could not match, so /// we disable the function to write user phrases in this case. @@ -370,14 +372,14 @@ class InputState { } var attributedString: NSAttributedString { - let attributedSting = NSAttributedString( + let attributedString = NSAttributedString( string: composingBuffer, attributes: [ .underlineStyle: NSUnderlineStyle.single.rawValue, .markedClauseSegment: 0, ] ) - return attributedSting + return attributedString } override var description: String { @@ -421,14 +423,14 @@ class InputState { // 如有誰要修奇摩注音的一點通選單的話,修復原理也是一樣的。 // Crediting Qwertyyb: https://github.com/qwertyyb/Fire/issues/55#issuecomment-1133497700 override var attributedString: NSAttributedString { - let attributedSting = NSAttributedString( + let attributedString = NSAttributedString( string: " ", attributes: [ .underlineStyle: NSUnderlineStyle.single.rawValue, .markedClauseSegment: 0, ] ) - return attributedSting + return attributedString } override var description: String { From b781400f45d9c33890022c41546f855eea7a635f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 11:17:35 +0800 Subject: [PATCH 13/22] AppDelegate // Merge method checkForUpdate(). --- Source/Modules/AppDelegate.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index 92ac189d..1beee07c 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -105,11 +105,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega NSApp.setActivationPolicy(.accessory) } - func checkForUpdate() { - checkForUpdate(forced: false) - } - - func checkForUpdate(forced: Bool) { + func checkForUpdate(forced: Bool = false) { if checkTask != nil { // busy return From b84fe50964d9640c3fcfe2ebe3662a927950baf9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 14:55:10 +0800 Subject: [PATCH 14/22] mgrLM // Simplify userDataFolderExists(). --- Source/Modules/IMEModules/ctlInputMethod_Menu.swift | 2 +- Source/Modules/LangModelRelated/mgrLangModel.swift | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift index 82236031..9f83bc33 100644 --- a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift @@ -302,7 +302,7 @@ extension ctlInputMethod { } @objc func openUserDataFolder(_: Any?) { - if !mgrLangModel.checkIfUserDataFolderExists() { + if !mgrLangModel.userDataFolderExists { return } NSWorkspace.shared.openFile( diff --git a/Source/Modules/LangModelRelated/mgrLangModel.swift b/Source/Modules/LangModelRelated/mgrLangModel.swift index ab6a17d7..8850e23e 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.swift +++ b/Source/Modules/LangModelRelated/mgrLangModel.swift @@ -271,7 +271,7 @@ enum mgrLangModel { } static func chkUserLMFilesExist(_ mode: InputMode) -> Bool { - if !checkIfUserDataFolderExists() { + if !userDataFolderExists { return false } if !ensureFileExists(userPhrasesDataPath(mode)) @@ -309,9 +309,8 @@ enum mgrLangModel { return true } - // ⚠︎ 私有函數:檢查且糾偏,不接受任何傳入變數。該函數不用於其他型別。 - // 待辦事項:擇日合併至另一個同類型的函數當中。 - static func checkIfUserDataFolderExists() -> Bool { + // 檢查給定的目錄是否存在寫入合規性、且糾偏,不接受任何傳入變數。 + static var userDataFolderExists: Bool { let folderPath = mgrLangModel.dataFolderPath(isDefaultFolder: false) var isFolder = ObjCBool(false) var folderExist = FileManager.default.fileExists(atPath: folderPath, isDirectory: &isFolder) From cda914b5c4cd4040e57a444a61994764ed4ae48a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 17:53:47 +0800 Subject: [PATCH 15/22] KeyHandler // Re-enable NSString in buildInputtingState(). --- .../ControllerModules/KeyHandler_States.swift | 79 +++++++++---------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index c20ef4a5..0f96f902 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -36,37 +36,42 @@ extension KeyHandler { // to "refresh" the text input buffer with our "composing text" var composingBuffer = "" var composedStringCursorIndex = 0 - var readingCursorIndex = 0 - let builderCursorIndex = builderCursorIndex - - for theAnchor in _walkedNodes { - guard let node = theAnchor.node else { - continue - } - - let valueString = node.currentKeyValue.value - composingBuffer += valueString - let codepointCount = valueString.count - - let spanningLength = theAnchor.spanningLength - if readingCursorIndex + spanningLength <= builderCursorIndex { - composedStringCursorIndex += valueString.count - readingCursorIndex += spanningLength - } else { - if codepointCount == spanningLength { - for _ in 0.. builderCursorIndex { - readingCursorIndex = builderCursorIndex + if codepointCount == spanningLength { + var i = 0 + while i < codepointCount, readingCursorIndex < builderCursorIndex { + composedStringCursorIndex += arrSplit[i].length + readingCursorIndex += 1 + i += 1 + } + } else { + if readingCursorIndex < builderCursorIndex { + composedStringCursorIndex += (strNodeValue as NSString).length + readingCursorIndex += spanningLength + if readingCursorIndex > builderCursorIndex { + readingCursorIndex = builderCursorIndex + } } } } @@ -76,21 +81,9 @@ extension KeyHandler { // Now, we gather all the intel, separate the composing buffer to two parts (head and tail), // and insert the reading text (the Mandarin syllable) in between them. // The reading text is what the user is typing. - - var rawHead = "" - var rawEnd = "" - - for (i, n) in composingBuffer.enumerated() { - if i < composedStringCursorIndex { - rawHead += String(n) - } else { - rawEnd += String(n) - } - } - - let head = rawHead + let head = String((composingBuffer as NSString).substring(to: composedStringCursorIndex)) let reading = _composer.getInlineCompositionForIMK(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer) - let tail = rawEnd + let tail = String((composingBuffer as NSString).substring(from: composedStringCursorIndex)) let composedText = head + reading + tail let cursorIndex = composedStringCursorIndex + reading.count From a456f1a50f3da8141059e13dfd85ca7832fadd09 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 20 May 2022 23:38:45 +0800 Subject: [PATCH 16/22] ctlIME // Preventing clang-format from destroying certain variables. --- .../Modules/IMEModules/ctlInputMethod.swift | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 300c2a26..54b6654a 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -114,7 +114,8 @@ class ctlInputMethod: IMKInputController { handle(state: .Deactivated(), client: client) } - override func setValue(_ value: Any!, forTag _: Int, client: Any!) { + override func setValue(_ value: Any!, forTag tag: Int, client: Any!) { + _ = tag // Stop clang-format from ruining the parameters of this function. var newInputMode = InputMode(rawValue: value as? String ?? "") ?? InputMode.imeModeNULL switch newInputMode { case InputMode.imeModeCHS: @@ -145,7 +146,8 @@ class ctlInputMethod: IMKInputController { // MARK: - IMKServerInput protocol methods - override func recognizedEvents(_: Any!) -> Int { + override func recognizedEvents(_ sender: Any!) -> Int { + _ = sender // Stop clang-format from ruining the parameters of this function. let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged] return Int(events.rawValue) } @@ -207,7 +209,8 @@ class ctlInputMethod: IMKInputController { // 這個函數必須得在對應的狀態下給出對應的內容。 override func composedString(_ sender: Any!) -> Any! { - (state as? InputState.NotEmpty)?.composingBuffer ?? "" + _ = sender // Stop clang-format from ruining the parameters of this function. + return (state as? InputState.NotEmpty)?.composingBuffer ?? "" } } @@ -279,7 +282,8 @@ extension ctlInputMethod { ) } - private func handle(state _: InputState.Deactivated, previous: InputState, client: Any?) { + private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { + _ = state // Stop clang-format from ruining the parameters of this function. currentClient = nil ctlCandidateCurrent?.delegate = nil @@ -295,7 +299,8 @@ extension ctlInputMethod { ) } - private func handle(state _: InputState.Empty, previous: InputState, client: Any?) { + private func handle(state: InputState.Empty, previous: InputState, client: Any?) { + _ = state // Stop clang-format from ruining the parameters of this function. ctlCandidateCurrent?.visible = false hideTooltip() @@ -313,8 +318,10 @@ extension ctlInputMethod { } private func handle( - state _: InputState.EmptyIgnoringPreviousState, previous _: InputState, client: Any! + state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any! ) { + _ = state // Stop clang-format from ruining the parameters of this function. + _ = previous // Stop clang-format from ruining the parameters of this function. ctlCandidateCurrent?.visible = false hideTooltip() @@ -328,7 +335,8 @@ extension ctlInputMethod { ) } - private func handle(state: InputState.Committing, previous _: InputState, client: Any?) { + private func handle(state: InputState.Committing, previous: InputState, client: Any?) { + _ = previous // Stop clang-format from ruining the parameters of this function. ctlCandidateCurrent?.visible = false hideTooltip() @@ -346,7 +354,8 @@ extension ctlInputMethod { ) } - private func handle(state: InputState.Inputting, previous _: InputState, client: Any?) { + private func handle(state: InputState.Inputting, previous: InputState, client: Any?) { + _ = previous // Stop clang-format from ruining the parameters of this function. ctlCandidateCurrent?.visible = false hideTooltip() @@ -373,7 +382,8 @@ extension ctlInputMethod { } } - private func handle(state: InputState.Marking, previous _: InputState, client: Any?) { + private func handle(state: InputState.Marking, previous: InputState, client: Any?) { + _ = previous // Stop clang-format from ruining the parameters of this function. ctlCandidateCurrent?.visible = false guard let client = client as? IMKTextInput else { hideTooltip() @@ -397,7 +407,8 @@ extension ctlInputMethod { } } - private func handle(state: InputState.ChoosingCandidate, previous _: InputState, client: Any?) { + private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) { + _ = previous // Stop clang-format from ruining the parameters of this function. hideTooltip() guard let client = client as? IMKTextInput else { ctlCandidateCurrent?.visible = false @@ -413,7 +424,8 @@ extension ctlInputMethod { show(candidateWindowWith: state, client: client) } - private func handle(state: InputState.SymbolTable, previous _: InputState, client: Any?) { + private func handle(state: InputState.SymbolTable, previous: InputState, client: Any?) { + _ = previous // Stop clang-format from ruining the parameters of this function. hideTooltip() guard let client = client as? IMKTextInput else { ctlCandidateCurrent?.visible = false @@ -429,7 +441,8 @@ extension ctlInputMethod { show(candidateWindowWith: state, client: client) } - private func handle(state: InputState.AssociatedPhrases, previous _: InputState, client: Any?) { + private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) { + _ = previous // Stop clang-format from ruining the parameters of this function. hideTooltip() guard let client = client as? IMKTextInput else { ctlCandidateCurrent?.visible = false @@ -591,14 +604,16 @@ extension ctlInputMethod { // MARK: - extension ctlInputMethod: KeyHandlerDelegate { - func ctlCandidate(for _: KeyHandler) -> Any { - ctlCandidateCurrent ?? .vertical + func ctlCandidate(for keyHandler: KeyHandler) -> Any { + _ = keyHandler // Stop clang-format from ruining the parameters of this function. + return ctlCandidateCurrent ?? .vertical } func keyHandler( - _: KeyHandler, didSelectCandidateAt index: Int, + _ keyHandler: KeyHandler, didSelectCandidateAt index: Int, ctlCandidate controller: Any ) { + _ = keyHandler // Stop clang-format from ruining the parameters of this function. if let controller = controller as? ctlCandidate { ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index)) } @@ -636,7 +651,8 @@ extension ctlInputMethod: KeyHandlerDelegate { // MARK: - extension ctlInputMethod: ctlCandidateDelegate { - func candidateCountForController(_: ctlCandidate) -> UInt { + func candidateCountForController(_ controller: ctlCandidate) -> UInt { + _ = controller // Stop clang-format from ruining the parameters of this function. if let state = state as? InputState.ChoosingCandidate { return UInt(state.candidates.count) } else if let state = state as? InputState.AssociatedPhrases { @@ -645,9 +661,10 @@ extension ctlInputMethod: ctlCandidateDelegate { return 0 } - func ctlCandidate(_: ctlCandidate, candidateAtIndex index: UInt) + func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt) -> String { + _ = controller // Stop clang-format from ruining the parameters of this function. if let state = state as? InputState.ChoosingCandidate { return state.candidates[Int(index)] } else if let state = state as? InputState.AssociatedPhrases { @@ -656,7 +673,8 @@ extension ctlInputMethod: ctlCandidateDelegate { return "" } - func ctlCandidate(_: ctlCandidate, didSelectCandidateAtIndex index: UInt) { + func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) { + _ = controller // Stop clang-format from ruining the parameters of this function. let client = currentClient if let state = state as? InputState.SymbolTable, From 27a8abe1b33730e04cdfeb6d13831c2f51f0e4fa Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 12:55:44 +0800 Subject: [PATCH 17/22] PrefUI // i18n fix. --- Source/Resources/zh-Hans.lproj/Localizable.strings | 2 +- Source/Resources/zh-Hant.lproj/Localizable.strings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index ab0f3f6a..8d5515ab 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -105,7 +105,7 @@ "Choose your preferred layout of the candidate window." = "选择您所偏好的候选字窗布局。"; "Cursor Selection:" = "选字游标:"; "Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千排列 (微软标准/王安/零壹/仲鼎/国乔)"; -"Dachen 26 (libChewing)" = "酷音大千二十六键"; +"Dachen 26 (libChewing)" = "酷音大千二十六键排列"; "Debug Mode" = "侦错模式"; "Dictionary" = "辞典"; "Emulating select-candidate-per-character mode" = "模拟 90 年代前期注音逐字选字输入风格"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index 0347c7db..416b102e 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -105,7 +105,7 @@ "Choose your preferred layout of the candidate window." = "選擇您所偏好的候選字窗佈局。"; "Cursor Selection:" = "選字游標:"; "Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千排列 (微軟標準/王安/零壹/仲鼎/國喬)"; -"Dachen 26 (libChewing)" = "酷音大千二十六鍵"; +"Dachen 26 (libChewing)" = "酷音大千二十六鍵排列"; "Debug Mode" = "偵錯模式"; "Dictionary" = "辭典"; "Emulating select-candidate-per-character mode" = "模擬 90 年代前期注音逐字選字輸入風格"; From 5c27e5f102138863f9ab0de33209a100de22050b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 14:04:39 +0800 Subject: [PATCH 18/22] Repo // Update the date tag of CNS Data. --- Source/Resources/Base.lproj/Localizable.strings | 2 +- Source/Resources/en.lproj/Localizable.strings | 2 +- Source/Resources/ja.lproj/Localizable.strings | 2 +- Source/Resources/zh-Hans.lproj/Localizable.strings | 2 +- Source/Resources/zh-Hant.lproj/Localizable.strings | 2 +- Source/UI/PrefUI/suiPrefPaneDictionary.swift | 2 +- Source/WindowNIBs/Base.lproj/frmPrefWindow.xib | 2 +- Source/WindowNIBs/en.lproj/frmPrefWindow.strings | 4 ++-- Source/WindowNIBs/ja.lproj/frmPrefWindow.strings | 4 ++-- Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings | 4 ++-- Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings | 4 ++-- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index 750ae13e..a39e8b80 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -109,7 +109,7 @@ "Debug Mode" = "Debug Mode"; "Dictionary" = "Dictionary"; "Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode"; -"Enable CNS11643 Support (2022-01-27)" = "Enable CNS11643 Support (2022-01-27)"; +"Enable CNS11643 Support (2022-04-27)" = "Enable CNS11643 Support (2022-04-27)"; "Enable Space key for calling candidate window" = "Enable Space key for calling candidate window"; "Enable symbol input support (incl. certain emoji symbols)" = "Enable symbol input support (incl. certain emoji symbols)"; "English" = "English"; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index 750ae13e..a39e8b80 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -109,7 +109,7 @@ "Debug Mode" = "Debug Mode"; "Dictionary" = "Dictionary"; "Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode"; -"Enable CNS11643 Support (2022-01-27)" = "Enable CNS11643 Support (2022-01-27)"; +"Enable CNS11643 Support (2022-04-27)" = "Enable CNS11643 Support (2022-04-27)"; "Enable Space key for calling candidate window" = "Enable Space key for calling candidate window"; "Enable symbol input support (incl. certain emoji symbols)" = "Enable symbol input support (incl. certain emoji symbols)"; "English" = "English"; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index 361b74b4..180e54a4 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -109,7 +109,7 @@ "Debug Mode" = "欠陥辿着モード"; "Dictionary" = "辞書設定"; "Emulating select-candidate-per-character mode" = "漢字1つづつ全候補選択入力モード"; -"Enable CNS11643 Support (2022-01-27)" = "全字庫モード // 入力可能な漢字数を倍増す (2022-01-07)"; +"Enable CNS11643 Support (2022-04-27)" = "全字庫モード // 入力可能な漢字数を倍増す (2022-04-27)"; "Enable Space key for calling candidate window" = "Space キーで入力候補を呼び出す"; "Enable symbol input support (incl. certain emoji symbols)" = "僅かなる絵文字も含む符号入力サポートを起用"; "English" = "英語"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 8d5515ab..5d844616 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -109,7 +109,7 @@ "Debug Mode" = "侦错模式"; "Dictionary" = "辞典"; "Emulating select-candidate-per-character mode" = "模拟 90 年代前期注音逐字选字输入风格"; -"Enable CNS11643 Support (2022-01-27)" = "启用 CNS11643 全字库支援 (2022-01-07)"; +"Enable CNS11643 Support (2022-04-27)" = "启用 CNS11643 全字库支援 (2022-04-27)"; "Enable Space key for calling candidate window" = "敲空格键以呼出候选字窗"; "Enable symbol input support (incl. certain emoji symbols)" = "启用包括少许绘文字在内的符号输入支援"; "English" = "英语"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index 416b102e..cd1edbe9 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -109,7 +109,7 @@ "Debug Mode" = "偵錯模式"; "Dictionary" = "辭典"; "Emulating select-candidate-per-character mode" = "模擬 90 年代前期注音逐字選字輸入風格"; -"Enable CNS11643 Support (2022-01-27)" = "啟用 CNS11643 全字庫支援 (2022-01-07)"; +"Enable CNS11643 Support (2022-04-27)" = "啟用 CNS11643 全字庫支援 (2022-04-27)"; "Enable Space key for calling candidate window" = "敲空格鍵以呼出候選字窗"; "Enable symbol input support (incl. certain emoji symbols)" = "啟用包括少許繪文字在內的符號輸入支援"; "English" = "英語"; diff --git a/Source/UI/PrefUI/suiPrefPaneDictionary.swift b/Source/UI/PrefUI/suiPrefPaneDictionary.swift index ba9e5170..e8376b3e 100644 --- a/Source/UI/PrefUI/suiPrefPaneDictionary.swift +++ b/Source/UI/PrefUI/suiPrefPaneDictionary.swift @@ -113,7 +113,7 @@ struct suiPrefPaneDictionary: View { mgrPrefs.shouldAutoReloadUserDataFiles = value } Divider() - Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643) + Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-04-27)"), isOn: $selEnableCNS11643) .onChange(of: selEnableCNS11643) { value in mgrPrefs.cns11643Enabled = value mgrLangModel.setCNSEnabled(value) diff --git a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib index 2c071710..1dc1d24c 100644 --- a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib +++ b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib @@ -682,7 +682,7 @@ - + diff --git a/Source/WindowNIBs/en.lproj/frmPrefWindow.strings b/Source/WindowNIBs/en.lproj/frmPrefWindow.strings index 0178fff6..f7c834dc 100644 --- a/Source/WindowNIBs/en.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/en.lproj/frmPrefWindow.strings @@ -152,8 +152,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "Output Settings"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-01-07)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-04-27)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "Keyboard Layout"; diff --git a/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings b/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings index 3d8e876a..ae3a0e98 100644 --- a/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings @@ -152,8 +152,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "出力設定"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-01-07)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-04-27)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "キーボード"; diff --git a/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings b/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings index 8bcf7678..6a282458 100644 --- a/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings @@ -152,8 +152,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "输出设定"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-01-07)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-04-27)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "键盘布局"; diff --git a/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings b/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings index 2f939ed2..fb1bc669 100644 --- a/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings @@ -152,8 +152,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "輸出設定"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-01-07)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-04-27)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "鍵盤佈局"; From 5aa7da47ef7ffaf1a729b5c931d5cd5192f7955b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 01:24:50 +0800 Subject: [PATCH 19/22] CONTRIBUTING // Update wish list. --- CONTRIBUTING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7392e009..eba07d2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,12 +2,11 @@ 威注音輸入法歡迎有熱心的志願者們參與。 -威注音目前的 codebase 更能代表一個先進的 macOS 輸入法雛形專案的形態。目前的 dev 分支除了 Mandarin 模組(以及其與 KeyHandler 的對接的部分)以外被威注音使用的部分全都是清一色的 Swift codebase,一目了然,方便他人參與,比某些其它開源品牌旗下的專案更具程式方面的生命力。為什麼這樣講呢?那些傳統開源品牌的專案主要使用 C++ 這門不太友好的語言(Mandarin 模組現在對我而言仍舊是天書,一大堆針對記憶體指針的操作完全看不懂。搞不清楚在這一層之上的功能邏輯的話,就無法制定 Swift 版的 coding 策略),這也是我這次用 Swift 重寫了語言模型引擎的原因(也是為後來者行方便)。 +威注音目前的 codebase 更能代表一個先進的 macOS 輸入法雛形專案的形態。目前的 dev 分支除了第三方 OpenCC 模組以外被威注音使用的部分全都是清一色的 Swift codebase,一目了然,方便他人參與,比某些其它開源品牌旗下的專案更具程式方面的生命力。為什麼這樣講呢?那些傳統開源品牌的專案主要使用 C++ 這門不太友好的語言(Mandarin 模組現在對我而言仍舊是天書,一大堆針對記憶體指針的操作完全看不懂。搞不清楚在這一層之上的功能邏輯的話,就無法制定 Swift 版的 coding 策略),這也是我這次用 Swift 重寫了語言模型引擎與注音拼音並擊處理引擎、來換掉 Gramambular 與 OVMandarin 的原因(也是為後來者行方便)。 為了不讓參與者們浪費各自的熱情,特設此文以說明該專案目前最需要協助的地方。 -1. 讓 Alt+波浪鍵選單能夠在諸如 MS Word 以及終端機內正常工作(可以用方向鍵控制高亮候選內容,等)。 - - 原理上而言恐怕得欺騙當前正在接受輸入的應用、使其誤以為當前有組字區。這只是推測。 +1. 將選字窗換成 IMK 內建的矩陣選字窗。 除了上述各項以外的貢獻,除非特邀、或者有足夠的說服理由與吸引力(比如語法錯誤或更好的重構方法等),否則敝專案可能會無視或者拒絕。 From 3e50a4fc85610e64509816f727119fad3d6ef0ff Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 12:37:41 +0800 Subject: [PATCH 20/22] README // Description update. --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d489ecac..b651cd8f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ 威注音輸入法基於小麥注音二次開發,是**原生簡體中文、原生繁體中文注音輸入法**: +- 威注音是業界現階段支援注音排列種類數量與輸入用拼音種類數量最多的注音輸入法。 + - 受威注音自家的鐵恨注音並擊引擎加持。 - 威注音的原廠詞庫內不存在任何可以妨礙該輸入法在世界上任何地方傳播的內容。 - 相比中州韻(鼠須管)而言,威注音能夠做到真正的大千聲韻並擊。 @@ -14,7 +16,7 @@ >- 支援 macOS 螢幕模擬鍵盤(僅傳統大千與傳統倚天佈局)。 >- 可以將自己打的繁體中文自動轉成日本 JIS 新字體來輸出(包括基礎的字詞轉換)、也可以轉成康熙繁體來輸出。 >- 簡繁體中文語料庫彼此分離,徹底杜絕任何繁簡轉換過程可能造成的失誤。 ->- 支援最新的全字型檔漢字輸入。 +>- 支援近年的全字庫漢字輸入。 >- 可以自動整理使用者語彙檔案格式、自訂聯想詞。 >- …… @@ -96,8 +98,14 @@ 請洽該倉庫內的「[CONTRIBUTING.md](./CONTRIBUTING.md)」檔案。 -## 特殊勸告 +## 其他 為了您的精神衛生,任何使用威注音輸入法時遇到的產品問題、請勿提報至小麥注音,除非您確信小麥注音也有該問題。即便如此,也請在他們那邊不要提及威注音。 -濫用沉默權來浪費對方的時間與熱情,也是一種暴力。 +濫用沉默權來浪費對方的時間與熱情,也是一種暴力。**當對方最最最開始就把你當敵人的時候,你連呼吸都是錯的**。 + +其實我滿懷念上游專案還沒被 Lukhnos Liu 接管收入 OpenVanilla 的那個年代。MJHsieh 主導開發小麥注音的時候,且不討論他立場怎樣,但基礎的技術交流是完全沒問題的。LibChewing 那邊也是,正常交流完全沒問題。 + +有些事情,繼續爭論下去也沒用。本來我想著重寫 ctlInputMethod 撤掉 zonble 的狀態管理引擎的,畢竟有大陸同鄉寫的火山五筆輸入法的框架套上我的鐵恨注音並擊引擎可以直接用。但這樣賭氣對誰都沒好處。眼下,威注音 macOS 版還需要一些小維護。之後我就得開始考慮用 Rust 重寫鐵恨注音並擊引擎與天權星語彙引擎、方便接下來威注音的 Windows 版本的研發。能將 Lukhnos 的 C++ 內容全部換掉、徹底砸碎套在威注音身上的名為 C++ 的枷鎖、讓威注音有一個自由的未來,我已經知足了。讓更多的人用上好用的輸入法,才是最重要的。**這個重要性,不是 zonble 用「私人需求」這種帽子扣過來、就可以泯滅了的**。 + +$ EOF. From 75024a94bb9b8526ee5aa6f1ca6628908e2fc3a6 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 16:10:28 +0800 Subject: [PATCH 21/22] Update Data - 20220521 --- Source/Data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Data b/Source/Data index 4e5f1dc1..6c4d659d 160000 --- a/Source/Data +++ b/Source/Data @@ -1 +1 @@ -Subproject commit 4e5f1dc11a9b477b7907f5e59594e636a8830ce8 +Subproject commit 6c4d659d148ef5be3255427bfd4429fa8690631d From 3cb6e32497448bbc264ac4eaab8f3be7b1ba1850 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 21 May 2022 16:28:43 +0800 Subject: [PATCH 22/22] Bump version to 1.6.0 Build 1960. --- Update-Info.plist | 4 ++-- vChewing.pkgproj | 2 +- vChewing.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Update-Info.plist b/Update-Info.plist index e7e30f11..1e03e069 100644 --- a/Update-Info.plist +++ b/Update-Info.plist @@ -3,9 +3,9 @@ CFBundleShortVersionString - 1.5.9 + 1.6.0 CFBundleVersion - 1959 + 1960 UpdateInfoEndpoint https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist UpdateInfoSite diff --git a/vChewing.pkgproj b/vChewing.pkgproj index ed9d49b0..5d799050 100644 --- a/vChewing.pkgproj +++ b/vChewing.pkgproj @@ -726,7 +726,7 @@ USE_HFS+_COMPRESSION VERSION - 1.5.9 + 1.6.0 TYPE 0 diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 88c7a454..2fe1eb44 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -1292,7 +1292,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1959; + CURRENT_PROJECT_VERSION = 1960; DEBUG_INFORMATION_FORMAT = dwarf; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; @@ -1315,7 +1315,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.6.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1348,7 +1348,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1959; + CURRENT_PROJECT_VERSION = 1960; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1367,7 +1367,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.6.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1482,7 +1482,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1959; + CURRENT_PROJECT_VERSION = 1960; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -1517,7 +1517,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.6.0; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1549,7 +1549,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1959; + CURRENT_PROJECT_VERSION = 1960; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1579,7 +1579,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1662,7 +1662,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1959; + CURRENT_PROJECT_VERSION = 1960; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -1687,7 +1687,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.6.0; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1714,7 +1714,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1959; + CURRENT_PROJECT_VERSION = 1960; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -1734,7 +1734,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "";