From 97062ab731a99c794787b115f63b7f3828740669 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 3 Sep 2022 20:29:45 +0800 Subject: [PATCH] Repo // Refactoring InputFSM - preparation. --- .../Modules/ControllerModules/IMEState.swift | 182 +++++++++ .../ControllerModules/IMEStateData.swift | 268 ++++++++++++ .../ControllerModules/InputState.swift | 380 ++++++++---------- .../KeyHandler_HandleCandidate.swift | 20 +- .../KeyHandler_HandleComposition.swift | 8 +- .../KeyHandler_HandleInput.swift | 14 +- .../ControllerModules/KeyHandler_States.swift | 78 ++-- .../ctlInputMethod_Common.swift | 8 +- .../ctlInputMethod_Core.swift | 18 +- .../ctlInputMethod_Delegates.swift | 22 +- .../ctlInputMethod_HandleDisplay.swift | 18 +- .../ctlInputMethod_HandleStates.swift | 20 +- .../ctlInputMethod_Menu.swift | 72 ++-- Source/Modules/IMEModules/mgrPrefs.swift | 3 + .../LangModelRelated/LMSymbolNode.swift | 2 +- vChewing.xcodeproj/project.pbxproj | 8 + 16 files changed, 743 insertions(+), 378 deletions(-) create mode 100644 Source/Modules/ControllerModules/IMEState.swift create mode 100644 Source/Modules/ControllerModules/IMEStateData.swift diff --git a/Source/Modules/ControllerModules/IMEState.swift b/Source/Modules/ControllerModules/IMEState.swift new file mode 100644 index 00000000..55082a7f --- /dev/null +++ b/Source/Modules/ControllerModules/IMEState.swift @@ -0,0 +1,182 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Foundation + +// 用以讓每個狀態自描述的 enum。 +public enum StateType { + case ofDeactivated + case ofEmpty + case ofAbortion // 該狀態會自動轉為 Empty + case ofCommitting + case ofAssociates + case ofNotEmpty + case ofInputting + case ofMarking + case ofCandidates + case ofSymbolTable +} + +// 所有 InputState 均遵守該協定: +public protocol InputStateProtocol { + var type: StateType { get } + var data: StateData { get } + var hasBuffer: Bool { get } + var isCandidateContainer: Bool { get } + var displayedText: String { get } + var textToCommit: String { get set } + var tooltip: String { get set } + var attributedString: NSAttributedString { get } + var node: SymbolNode { get set } +} + +public struct IMEState { + public var type: StateType = .ofEmpty + public var data: StateData = .init() + init(_ data: StateData = .init(), type: StateType = .ofEmpty) { + self.data = data + self.type = type + } +} + +// MARK: - 針對不同的狀態,規定不同的構造器 + +extension IMEState { + public static func Deactivated() -> IMEState { .init(type: .ofDeactivated) } + public static func Empty() -> IMEState { .init(type: .ofEmpty) } + public static func Abortion() -> IMEState { .init(type: .ofAbortion) } + public static func Committing(textToCommit: String) -> IMEState { + var result = IMEState(type: .ofCommitting) + result.data.textToCommit = textToCommit + ChineseConverter.ensureCurrencyNumerals(target: &result.data.textToCommit) + return result + } + + public static func Associates(candidates: [(String, String)]) -> IMEState { + var result = IMEState(type: .ofAssociates) + result.data.candidates = candidates + return result + } + + public static func NotEmpty(nodeValues: [String], reading: String = "", cursor: Int) -> IMEState { + var result = IMEState(type: .ofNotEmpty) + // 注意資料的設定順序:nodeValuesArray 必須比 reading 先設定。 + result.data.nodeValuesArray = nodeValues + if !reading.isEmpty { + result.data.reading = reading // 會在被寫入資料值後自動更新 nodeValuesArray + } + // 此時 nodeValuesArray 已經被塞上讀音,直接使用即可。 + result.data.displayedText = result.data.nodeValuesArray.joined() + result.data.cursor = cursor + return result + } + + public static func Inputting(nodeValues: [String], reading: String = "", cursor: Int) -> IMEState { + var result = IMEState.NotEmpty(nodeValues: nodeValues, reading: reading, cursor: cursor) + result.type = .ofInputting + return result + } + + public static func Marking(nodeValues: [String], nodeReadings: [String], cursor: Int, marker: Int) -> IMEState { + var result = IMEState.NotEmpty(nodeValues: nodeValues, cursor: cursor) + result.type = .ofMarking + result.data.nodeReadingsArray = nodeReadings + result.data.marker = marker + StateData.Marking.updateParameters(&result.data) + return result + } + + public static func Candidates(candidates: [(String, String)], nodeValues: [String], cursor: Int) -> IMEState { + var result = IMEState.NotEmpty(nodeValues: nodeValues, cursor: cursor) + result.type = .ofCandidates + result.data.candidates = candidates + return result + } + + public static func SymbolTable(node: SymbolNode, previous: SymbolNode? = nil) -> IMEState { + let candidates = { node.children?.map(\.title) ?? [String]() }().map { ("", $0) } + var result = IMEState.Candidates(candidates: candidates, nodeValues: [], cursor: 0) + result.type = .ofSymbolTable + result.data.node = node + if let previous = previous { + result.data.node.previous = previous + } + return result + } +} + +// MARK: - 規定一個狀態該怎樣返回自己的資料值 + +extension IMEState: InputStateProtocol { + public var convertedToInputting: IMEState { + if type == .ofInputting { return self } + var result = IMEState.Inputting(nodeValues: data.nodeValuesArray, reading: data.reading, cursor: data.cursor) + result.tooltip = data.tooltipBackupForInputting + return result + } + + public var textToCommit: String { + get { + data.textToCommit + } + set { + data.textToCommit = newValue + } + } + + public var tooltip: String { + get { + data.tooltip + } + set { + data.tooltip = newValue + } + } + + public var attributedString: NSAttributedString { + switch type { + case .ofMarking: return data.attributedStringMarking + case .ofAssociates, .ofSymbolTable: return data.attributedStringPlaceholder + default: return data.attributedStringNormal + } + } + + public var node: SymbolNode { + get { + data.node + } + set { + data.node = newValue + } + } + + public var tooltipBackupForInputting: String { + get { + data.tooltipBackupForInputting + } + set { + data.tooltipBackupForInputting = newValue + } + } + + public var hasBuffer: Bool { + switch type { + case .ofNotEmpty, .ofInputting, .ofMarking, .ofCandidates: return true + default: return false + } + } + + public var isCandidateContainer: Bool { + switch type { + case .ofCandidates, .ofAssociates, .ofSymbolTable: return true + default: return false + } + } + + public var displayedText: String { data.displayedText } +} diff --git a/Source/Modules/ControllerModules/IMEStateData.swift b/Source/Modules/ControllerModules/IMEStateData.swift new file mode 100644 index 00000000..a4276527 --- /dev/null +++ b/Source/Modules/ControllerModules/IMEStateData.swift @@ -0,0 +1,268 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Foundation + +public struct StateData { + var displayedText: String = "" { + didSet { + let result = IME.kanjiConversionIfRequired(displayedText) + if result.utf16.count == displayedText.utf16.count, result.count == displayedText.count { + displayedText = result + } + } + } + + // MARK: Cursor & Marker & Range for UTF8 + + var cursor: Int = 0 { + didSet { + cursor = min(max(cursor, 0), displayedText.count) + } + } + + var marker: Int = 0 { + didSet { + marker = min(max(marker, 0), displayedText.count) + } + } + + var markedRange: Range { + min(cursor, marker).. { + min(u16Cursor, u16Marker).. String { + var arrOutput = [String]() + for neta in data.nodeReadingsArray[data.markedRange] { + var neta = neta + if neta.isEmpty { continue } + if neta.contains("_") { + arrOutput.append("??") + continue + } + if mgrPrefs.showHanyuPinyinInCompositionBuffer { // 恢復陰平標記->注音轉拼音->轉教科書式標調 + neta = Tekkon.restoreToneOneInZhuyinKey(target: neta) + neta = Tekkon.cnvPhonaToHanyuPinyin(target: neta) + neta = Tekkon.cnvHanyuPinyinToTextbookStyle(target: neta) + } else { + neta = Tekkon.cnvZhuyinChainToTextbookReading(target: neta) + } + arrOutput.append(neta) + } + return arrOutput.joined(separator: " ") + } + + /// 更新工具提示內容、以及對應配對是否在庫。 + /// - Parameter data: 要處理的狀態資料包。 + public static func updateParameters(_ data: inout StateData) { + var tooltipGenerated: String { + if data.displayedText.count != data.nodeReadingsArray.count { + ctlInputMethod.tooltipController.setColor(state: .redAlert) + return NSLocalizedString( + "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "" + ) + } + if mgrPrefs.phraseReplacementEnabled { + ctlInputMethod.tooltipController.setColor(state: .warning) + return NSLocalizedString( + "⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "" + ) + } + if data.markedRange.isEmpty { + return "" + } + + let text = data.displayedText.charComponents[data.markedRange].joined() + if data.markedRange.count < mgrPrefs.allowedMarkRange.lowerBound { + ctlInputMethod.tooltipController.setColor(state: .denialInsufficiency) + return String( + format: NSLocalizedString( + "\"%@\" length must ≥ 2 for a user phrase.", comment: "" + ) + "\n// " + generateReadingThread(data), text + ) + } else if data.markedRange.count > mgrPrefs.allowedMarkRange.upperBound { + ctlInputMethod.tooltipController.setColor(state: .denialOverflow) + return String( + format: NSLocalizedString( + "\"%@\" length should ≤ %d for a user phrase.", comment: "" + ) + "\n// " + generateReadingThread(data), text, mgrPrefs.allowedMarkRange.upperBound + ) + } + + let selectedReadings = data.nodeReadingsArray[data.markedRange] + let joined = selectedReadings.joined(separator: "-") + let exist = mgrLangModel.checkIfUserPhraseExist( + userPhrase: text, mode: IME.currentInputMode, key: joined + ) + if exist { + data.markedTargetExists = exist + ctlInputMethod.tooltipController.setColor(state: .prompt) + return String( + format: NSLocalizedString( + "\"%@\" already exists: ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude.", + comment: "" + ) + "\n// " + generateReadingThread(data), text + ) + } + ctlInputMethod.tooltipController.resetColor() + return String( + format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n// " + + generateReadingThread(data), + text + ) + } + data.tooltip = tooltipGenerated + } + } +} diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index b3b59103..7300ad05 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -12,27 +12,6 @@ import Foundation // 註:所有 InputState 型別均不適合使用 Struct,因為 Struct 無法相互繼承派生。 -// 用以讓每個狀態自描述的 enum。 -public enum StateType { - case ofDeactivated - case ofAssociatedPhrases - case ofEmpty - case ofEmptyIgnoringPreviousState - case ofCommitting - case ofNotEmpty - case ofInputting - case ofMarking - case ofChoosingCandidate - case ofSymbolTable -} - -// 所有 InputState 均遵守該協定: -public protocol InputStateProtocol { - var type: StateType { get } - var hasBuffer: Bool { get } - var isCandidateContainer: Bool { get } -} - /// 此型別用以呈現輸入法控制器(ctlInputMethod)的各種狀態。 /// /// 從實際角度來看,輸入法屬於有限態械(Finite State Machine)。其藉由滑鼠/鍵盤 @@ -56,7 +35,7 @@ public protocol InputStateProtocol { /// 組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。 /// - .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。抑或是剛剛敲字遞交給 /// 客體應用、準備新的輸入行為。 -/// - .EmptyIgnoringPreviousState: 與 Empty 類似,但會扔掉上一個狀態的內容、不將這些 +/// - .Abortion: 與 Empty 類似,但會扔掉上一個狀態的內容、不將這些 /// 內容遞交給客體應用。該狀態在處理完畢之後會被立刻切換至 .Empty()。 /// - .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。 /// - .NotEmpty: 非空狀態,是一種狀態大類、用以派生且代表下述諸狀態。 @@ -68,6 +47,12 @@ public protocol InputStateProtocol { public enum InputState { /// .Deactivated: 使用者沒在使用輸入法。 class Deactivated: InputStateProtocol { + var node: SymbolNode = .init("") + var attributedString: NSAttributedString = .init() + var data: StateData = .init() + var textToCommit: String = "" + var tooltip: String = "" + let displayedText: String = "" let hasBuffer: Bool = false let isCandidateContainer: Bool = false public var type: StateType { .ofDeactivated } @@ -78,32 +63,41 @@ public enum InputState { /// .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。 /// 抑或是剛剛敲字遞交給客體應用、準備新的輸入行為。 class Empty: InputStateProtocol { + var node: SymbolNode = .init("") + var attributedString: NSAttributedString = .init() + var data: StateData = .init() + var textToCommit: String = "" + var tooltip: String = "" let hasBuffer: Bool = false let isCandidateContainer: Bool = false public var type: StateType { .ofEmpty } - let composingBuffer: String = "" + let displayedText: String = "" } // MARK: - - /// .EmptyIgnoringPreviousState: 與 Empty 類似, + /// .Abortion: 與 Empty 類似, /// 但會扔掉上一個狀態的內容、不將這些內容遞交給客體應用。 /// 該狀態在處理完畢之後會被立刻切換至 .Empty()。 - class EmptyIgnoringPreviousState: Empty { - override public var type: StateType { .ofEmptyIgnoringPreviousState } + class Abortion: Empty { + override public var type: StateType { .ofAbortion } } // MARK: - /// .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。 class Committing: InputStateProtocol { + var node: SymbolNode = .init("") + var attributedString: NSAttributedString = .init() + var data: StateData = .init() + var tooltip: String = "" + var textToCommit: String = "" + let displayedText: String = "" let hasBuffer: Bool = false let isCandidateContainer: Bool = false public var type: StateType { .ofCommitting } - private(set) var textToCommit: String = "" - convenience init(textToCommit: String) { - self.init() + init(textToCommit: String) { self.textToCommit = textToCommit ChineseConverter.ensureCurrencyNumerals(target: &self.textToCommit) } @@ -113,26 +107,29 @@ public enum InputState { /// .AssociatedPhrases: 逐字選字模式內的聯想詞輸入狀態。 /// 因為逐字選字模式不需要在組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。 - class AssociatedPhrases: InputStateProtocol { + class Associates: InputStateProtocol { + var node: SymbolNode = .init("") + var attributedString: NSAttributedString = .init() + var data: StateData = .init() + var textToCommit: String = "" + var tooltip: String = "" + let displayedText: String = "" let hasBuffer: Bool = false let isCandidateContainer: Bool = true - public var type: StateType { .ofAssociatedPhrases } - private(set) var candidates: [(String, String)] = [] - private(set) var isTypingVertical: Bool = false - init(candidates: [(String, String)], isTypingVertical: Bool) { - self.candidates = candidates - self.isTypingVertical = isTypingVertical - } - - var attributedString: NSMutableAttributedString { - let attributedString = NSMutableAttributedString( - string: " ", - attributes: [ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 0, - ] - ) - return attributedString + public var type: StateType { .ofAssociates } + var candidates: [(String, String)] { data.candidates } + init(candidates: [(String, String)]) { + data.candidates = candidates + attributedString = { + let attributedString = NSMutableAttributedString( + string: " ", + attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0, + ] + ) + return attributedString + }() } } @@ -145,27 +142,32 @@ public enum InputState { /// - .ChoosingCandidate: 叫出選字窗、允許使用者選字。 /// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。 class NotEmpty: InputStateProtocol { + var node: SymbolNode = .init("") + var attributedString: NSAttributedString = .init() + var data: StateData = .init() + var tooltip: String = "" + var textToCommit: String = "" let hasBuffer: Bool = true var isCandidateContainer: Bool { false } public var type: StateType { .ofNotEmpty } - private(set) var composingBuffer: String + private(set) var displayedText: String private(set) var cursorIndex: Int = 0 { didSet { cursorIndex = max(cursorIndex, 0) } } private(set) var reading: String = "" private(set) var nodeValuesArray = [String]() - public var composingBufferConverted: String { - let converted = IME.kanjiConversionIfRequired(composingBuffer) - if converted.utf16.count != composingBuffer.utf16.count - || converted.count != composingBuffer.count + public var displayedTextConverted: String { + let converted = IME.kanjiConversionIfRequired(displayedText) + if converted.utf16.count != displayedText.utf16.count + || converted.count != displayedText.count { - return composingBuffer + return displayedText } return converted } - public var committingBufferConverted: String { composingBufferConverted } + public var committingBufferConverted: String { displayedTextConverted } - init(composingBuffer: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) { - self.composingBuffer = composingBuffer + init(displayedText: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) { + self.displayedText = displayedText self.reading = reading // 為了簡化運算,將 reading 本身也變成一個字詞節點。 if !reading.isEmpty { @@ -189,25 +191,26 @@ public enum InputState { } else { self.nodeValuesArray = nodeValuesArray } - defer { self.cursorIndex = cursorIndex } - } - - var attributedString: NSMutableAttributedString { - /// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況, - /// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。 - let attributedString = NSMutableAttributedString(string: composingBufferConverted) - var newBegin = 0 - for (i, neta) in nodeValuesArray.enumerated() { - attributedString.setAttributes( - [ - /// 不能用 .thick,否則會看不到游標。 - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: i, - ], range: NSRange(location: newBegin, length: neta.utf16.count) - ) - newBegin += neta.utf16.count + defer { + self.cursorIndex = cursorIndex + self.attributedString = { + /// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況, + /// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。 + let attributedString = NSMutableAttributedString(string: displayedTextConverted) + var newBegin = 0 + for (i, neta) in nodeValuesArray.enumerated() { + attributedString.setAttributes( + [ + /// 不能用 .thick,否則會看不到游標。 + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: i, + ], range: NSRange(location: newBegin, length: neta.utf16.count) + ) + newBegin += neta.utf16.count + } + return attributedString + }() } - return attributedString } } @@ -216,23 +219,20 @@ public enum InputState { /// .Inputting: 使用者輸入了內容。此時會出現組字區(Compositor)。 class Inputting: NotEmpty { override public var type: StateType { .ofInputting } - var textToCommit: String = "" - var tooltip: String = "" - override public var committingBufferConverted: String { let committingBuffer = nodeValuesArray.joined() let converted = IME.kanjiConversionIfRequired(committingBuffer) - if converted.utf16.count != composingBuffer.utf16.count - || converted.count != composingBuffer.count + if converted.utf16.count != displayedText.utf16.count + || converted.count != displayedText.count { - return composingBuffer + return displayedText } return converted } - override init(composingBuffer: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) { + override init(displayedText: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) { super.init( - composingBuffer: composingBuffer, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray + displayedText: displayedText, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray ) } } @@ -247,8 +247,8 @@ public enum InputState { private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } } private(set) var markedRange: Range private var literalMarkedRange: Range { - let lowerBoundLiteral = composingBuffer.charIndexLiteral(from: markedRange.lowerBound) - let upperBoundLiteral = composingBuffer.charIndexLiteral(from: markedRange.upperBound) + let lowerBoundLiteral = displayedText.charIndexLiteral(from: markedRange.lowerBound) + let upperBoundLiteral = displayedText.charIndexLiteral(from: markedRange.upperBound) return lowerBoundLiteral.. = { - switch mgrPrefs.useRearCursorMode { - case false: return (max(0, cursorIndex - chosenCandidateString.utf16.count)).. = { - switch mgrPrefs.useRearCursorMode { - case false: return (max(0, cursorIndexU8 - chosenCandidateString.count))..= compositor.length { - let composingBuffer = currentState.composingBuffer - if !composingBuffer.isEmpty { - stateCallback(InputState.Committing(textToCommit: composingBuffer)) + let displayedText = currentState.displayedText + if !displayedText.isEmpty { + stateCallback(InputState.Committing(textToCommit: displayedText)) } stateCallback(InputState.Committing(textToCommit: " ")) stateCallback(InputState.Empty()) diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index a2763e77..2c30cb47 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -110,7 +110,7 @@ extension KeyHandler { /// 這裡生成準備要拿來回呼的「正在輸入」狀態,但還不能立即使用,因為工具提示仍未完成。 return InputState.Inputting( - composingBuffer: cleanedComposition, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray + displayedText: cleanedComposition, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray ) } @@ -123,13 +123,12 @@ extension KeyHandler { /// - Returns: 回呼一個新的選詞狀態,來就給定的候選字詞陣列資料內容顯示選字窗。 func buildCandidate( state currentState: InputState.NotEmpty, - isTypingVertical: Bool = false + isTypingVertical _: Bool = false ) -> InputState.ChoosingCandidate { InputState.ChoosingCandidate( - composingBuffer: currentState.composingBuffer, + displayedText: currentState.displayedText, cursorIndex: currentState.cursorIndex, candidates: getCandidatesArray(fixOrder: mgrPrefs.useFixecCandidateOrderOnSelection), - isTypingVertical: isTypingVertical, nodeValuesArray: compositor.walkedNodes.values ) } @@ -147,16 +146,13 @@ extension KeyHandler { /// 是否為空:如果陣列為空的話,直接回呼一個空狀態。 /// - Parameters: /// - key: 給定的索引鍵(也就是給定的聯想詞的開頭字)。 - /// - isTypingVertical: 是否縱排輸入? /// - Returns: 回呼一個新的聯想詞狀態,來就給定的聯想詞陣列資料內容顯示選字窗。 func buildAssociatePhraseState( - withPair pair: Megrez.Compositor.Candidate, - isTypingVertical: Bool - ) -> InputState.AssociatedPhrases! { + withPair pair: Megrez.Compositor.Candidate + ) -> InputState.Associates! { // 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。 - InputState.AssociatedPhrases( - candidates: buildAssociatePhraseArray(withPair: pair), isTypingVertical: isTypingVertical - ) + InputState.Associates( + candidates: buildAssociatePhraseArray(withPair: pair)) } // MARK: - 用以處理就地新增自訂語彙時的行為 @@ -190,7 +186,7 @@ extension KeyHandler { if input.isEnter { if let keyHandlerDelegate = delegate { // 先判斷是否是在摁了降權組合鍵的時候目標不在庫。 - if input.isShiftHold, input.isCommandHold, !state.validToFilter { + if input.isShiftHold, input.isCommandHold, !state.isFilterable { IME.prtDebugIntel("2EAC1F7A") errorCallback() return true @@ -207,7 +203,7 @@ extension KeyHandler { // BackSpace & Delete if input.isBackSpace || input.isDelete { if let keyHandlerDelegate = delegate { - if !state.validToFilter { + if !state.isFilterable { IME.prtDebugIntel("1F88B191") errorCallback() return true @@ -226,15 +222,15 @@ extension KeyHandler { if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold { var index = state.markerIndex if index > 0 { - index = state.composingBuffer.utf16PreviousPosition(for: index) + index = state.displayedText.utf16PreviousPosition(for: index) let marking = InputState.Marking( - composingBuffer: state.composingBuffer, + displayedText: state.displayedText, cursorIndex: state.cursorIndex, markerIndex: index, readings: state.readings, nodeValuesArray: compositor.walkedNodes.values ) - marking.tooltipForInputting = state.tooltipForInputting + marking.tooltipBackupForInputting = state.tooltipBackupForInputting stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking) } else { IME.prtDebugIntel("1149908D") @@ -247,16 +243,16 @@ extension KeyHandler { // Shift + Right if input.isCursorForward || input.emacsKey == EmacsKey.forward, input.isShiftHold { var index = state.markerIndex - if index < (state.composingBuffer.utf16.count) { - index = state.composingBuffer.utf16NextPosition(for: index) + if index < (state.displayedText.utf16.count) { + index = state.displayedText.utf16NextPosition(for: index) let marking = InputState.Marking( - composingBuffer: state.composingBuffer, + displayedText: state.displayedText, cursorIndex: state.cursorIndex, markerIndex: index, readings: state.readings, nodeValuesArray: compositor.walkedNodes.values ) - marking.tooltipForInputting = state.tooltipForInputting + marking.tooltipBackupForInputting = state.tooltipBackupForInputting stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking) } else { IME.prtDebugIntel("9B51408D") @@ -336,7 +332,7 @@ extension KeyHandler { ) -> Bool { guard let currentState = state as? InputState.Inputting else { return false } - stateCallback(InputState.Committing(textToCommit: currentState.composingBuffer)) + stateCallback(InputState.Committing(textToCommit: currentState.displayedText)) stateCallback(InputState.Empty()) return true } @@ -354,17 +350,17 @@ extension KeyHandler { ) -> Bool { guard state is InputState.Inputting else { return false } - var composingBuffer = compositor.keys.joined(separator: "-") + var displayedText = compositor.keys.joined(separator: "-") if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin { - composingBuffer = Tekkon.restoreToneOneInZhuyinKey(target: composingBuffer) // 恢復陰平標記 - composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) // 注音轉拼音 + displayedText = Tekkon.restoreToneOneInZhuyinKey(target: displayedText) // 恢復陰平標記 + displayedText = Tekkon.cnvPhonaToHanyuPinyin(target: displayedText) // 注音轉拼音 } if let delegate = delegate, !delegate.clientBundleIdentifier.contains("vChewingPhraseEditor") { - composingBuffer = composingBuffer.replacingOccurrences(of: "-", with: " ") + displayedText = displayedText.replacingOccurrences(of: "-", with: " ") } - stateCallback(InputState.Committing(textToCommit: composingBuffer)) + stateCallback(InputState.Committing(textToCommit: displayedText)) stateCallback(InputState.Empty()) return true } @@ -434,14 +430,14 @@ extension KeyHandler { stateCallback(buildInputtingState) return true case 1: - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) return true default: break } if input.isShiftHold, input.isOptionHold { - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) return true } @@ -465,7 +461,7 @@ extension KeyHandler { switch composer.isEmpty && compositor.isEmpty { case false: stateCallback(buildInputtingState) case true: - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) } return true @@ -489,7 +485,7 @@ extension KeyHandler { guard state is InputState.Inputting else { return false } if input.isShiftHold { - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) return true } @@ -510,10 +506,10 @@ extension KeyHandler { let inputting = buildInputtingState // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 - switch inputting.composingBuffer.isEmpty { + switch inputting.displayedText.isEmpty { case false: stateCallback(inputting) case true: - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) } return true @@ -625,7 +621,7 @@ extension KeyHandler { if mgrPrefs.escToCleanInputBuffer { /// 若啟用了該選項,則清空組字器的內容與注拼槽的內容。 /// 此乃 macOS 內建注音輸入法預設之行為,但不太受 Windows 使用者群體之待見。 - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) } else { if composer.isEmpty { return true } @@ -634,7 +630,7 @@ extension KeyHandler { switch compositor.isEmpty { case false: stateCallback(buildInputtingState) case true: - stateCallback(InputState.EmptyIgnoringPreviousState()) + stateCallback(InputState.Abortion()) stateCallback(InputState.Empty()) } } @@ -667,16 +663,16 @@ extension KeyHandler { if input.isShiftHold { // Shift + Right - if currentState.cursorIndex < currentState.composingBuffer.utf16.count { - let nextPosition = currentState.composingBuffer.utf16NextPosition( + if currentState.cursorIndex < currentState.displayedText.utf16.count { + let nextPosition = currentState.displayedText.utf16NextPosition( for: currentState.cursorIndex) let marking: InputState.Marking! = InputState.Marking( - composingBuffer: currentState.composingBuffer, + displayedText: currentState.displayedText, cursorIndex: currentState.cursorIndex, markerIndex: nextPosition, readings: compositor.keys ) - marking.tooltipForInputting = currentState.tooltip + marking.tooltipBackupForInputting = currentState.tooltip stateCallback(marking) } else { IME.prtDebugIntel("BB7F6DB9") @@ -742,15 +738,15 @@ extension KeyHandler { if input.isShiftHold { // Shift + left if currentState.cursorIndex > 0 { - let previousPosition = currentState.composingBuffer.utf16PreviousPosition( + let previousPosition = currentState.displayedText.utf16PreviousPosition( for: currentState.cursorIndex) let marking: InputState.Marking! = InputState.Marking( - composingBuffer: currentState.composingBuffer, + displayedText: currentState.displayedText, cursorIndex: currentState.cursorIndex, markerIndex: previousPosition, readings: compositor.keys ) - marking.tooltipForInputting = currentState.tooltip + marking.tooltipBackupForInputting = currentState.tooltip stateCallback(marking) } else { IME.prtDebugIntel("D326DEA3") diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Common.swift b/Source/Modules/ControllerModules/ctlInputMethod_Common.swift index 76e6b900..56fbfe74 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Common.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Common.swift @@ -31,11 +31,9 @@ extension ctlInputMethod { if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) { NotifierController.notify( message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n" - + { - toggleASCIIMode() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + toggleASCIIMode() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } if shouldUseHandle { diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift index 4dd5a663..aecd81c6 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift @@ -71,7 +71,7 @@ class ctlInputMethod: IMKInputController { } if let state = state as? InputState.NotEmpty { /// 將傳回的新狀態交給調度函式。 - handle(state: InputState.Committing(textToCommit: state.composingBufferConverted)) + handle(state: InputState.Committing(textToCommit: state.displayedTextConverted)) } handle(state: InputState.Empty()) } @@ -115,11 +115,9 @@ class ctlInputMethod: IMKInputController { } else { NotifierController.notify( message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n" - + { - isASCIIMode - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + isASCIIMode + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } } @@ -325,7 +323,7 @@ class ctlInputMethod: IMKInputController { } } - if let state = state as? InputState.AssociatedPhrases { + if let state = state as? InputState.Associates { handleCandidatesPrepared(state.candidates, prefix: "⇧") } else if let state = state as? InputState.SymbolTable { // 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。 @@ -363,9 +361,9 @@ class ctlInputMethod: IMKInputController { /// - Parameter candidateString: 已經確認的候選字詞內容。 override open func candidateSelected(_ candidateString: NSAttributedString!) { let candidateString: NSAttributedString = candidateString ?? .init(string: "") - if state is InputState.AssociatedPhrases { + if state is InputState.Associates { if !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter { - handle(state: InputState.EmptyIgnoringPreviousState()) + handle(state: InputState.Abortion()) handle(state: InputState.Empty()) return } @@ -403,7 +401,7 @@ class ctlInputMethod: IMKInputController { } } - if let state = state as? InputState.AssociatedPhrases { + if let state = state as? InputState.Associates { handleCandidatesSelected(state.candidates, prefix: "⇧") } else if let state = state as? InputState.SymbolTable { handleSymbolCandidatesSelected(state.candidates) diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift b/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift index c3226eef..b0c63631 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift @@ -65,7 +65,7 @@ extension ctlInputMethod: KeyHandlerDelegate { // MARK: - Candidate Controller Delegate extension ctlInputMethod: ctlCandidateDelegate { - var isAssociatedPhrasesState: Bool { state is InputState.AssociatedPhrases } + var isAssociatedPhrasesState: Bool { state is InputState.Associates } /// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。 /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 @@ -80,7 +80,7 @@ extension ctlInputMethod: ctlCandidateDelegate { _ = controller // 防止格式整理工具毀掉與此對應的參數。 if let state = state as? InputState.ChoosingCandidate { return state.candidates.count - } else if let state = state as? InputState.AssociatedPhrases { + } else if let state = state as? InputState.Associates { return state.candidates.count } return 0 @@ -93,7 +93,7 @@ extension ctlInputMethod: ctlCandidateDelegate { _ = controller // 防止格式整理工具毀掉與此對應的參數。 if let state = state as? InputState.ChoosingCandidate { return state.candidates - } else if let state = state as? InputState.AssociatedPhrases { + } else if let state = state as? InputState.Associates { return state.candidates } return .init() @@ -105,7 +105,7 @@ extension ctlInputMethod: ctlCandidateDelegate { _ = controller // 防止格式整理工具毀掉與此對應的參數。 if let state = state as? InputState.ChoosingCandidate { return state.candidates[index] - } else if let state = state as? InputState.AssociatedPhrases { + } else if let state = state as? InputState.Associates { return state.candidates[index] } return ("", "") @@ -119,9 +119,7 @@ extension ctlInputMethod: ctlCandidateDelegate { { if let children = node.children, !children.isEmpty { handle(state: InputState.Empty()) // 防止縱橫排選字窗同時出現 - handle( - state: InputState.SymbolTable(node: node, previous: state.node, isTypingVertical: state.isTypingVertical) - ) + handle(state: InputState.SymbolTable(node: node, previous: state.node)) } else { handle(state: InputState.Committing(textToCommit: node.title)) handle(state: InputState.Empty()) @@ -139,12 +137,11 @@ extension ctlInputMethod: ctlCandidateDelegate { let inputting = keyHandler.buildInputtingState if mgrPrefs.useSCPCTypingMode { - handle(state: InputState.Committing(textToCommit: inputting.composingBufferConverted)) + handle(state: InputState.Committing(textToCommit: inputting.displayedTextConverted)) // 此時是逐字選字模式,所以「selectedValue.1」是單個字、不用追加處理。 if mgrPrefs.associatedPhrasesEnabled, let associatePhrases = keyHandler.buildAssociatePhraseState( - withPair: .init(key: selectedValue.0, value: selectedValue.1), - isTypingVertical: state.isTypingVertical + withPair: .init(key: selectedValue.0, value: selectedValue.1) ), !associatePhrases.candidates.isEmpty { handle(state: associatePhrases) @@ -157,7 +154,7 @@ extension ctlInputMethod: ctlCandidateDelegate { return } - if let state = state as? InputState.AssociatedPhrases { + if let state = state as? InputState.Associates { let selectedValue = state.candidates[index] handle(state: InputState.Committing(textToCommit: selectedValue.1)) // 此時是聯想詞選字模式,所以「selectedValue.1」必須只保留最後一個字。 @@ -168,8 +165,7 @@ extension ctlInputMethod: ctlCandidateDelegate { } if mgrPrefs.associatedPhrasesEnabled, let associatePhrases = keyHandler.buildAssociatePhraseState( - withPair: .init(key: selectedValue.0, value: String(valueKept)), - isTypingVertical: state.isTypingVertical + withPair: .init(key: selectedValue.0, value: String(valueKept)) ), !associatePhrases.candidates.isEmpty { handle(state: associatePhrases) diff --git a/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift b/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift index 6424e402..e05d41bf 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift @@ -13,11 +13,11 @@ import Cocoa // MARK: - Tooltip Display and Candidate Display Methods extension ctlInputMethod { - func show(tooltip: String, composingBuffer: String, cursorIndex: Int) { + func show(tooltip: String, displayedText: String, cursorIndex: Int) { guard let client = client() else { return } var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) var cursor = cursorIndex - if cursor == composingBuffer.count, cursor != 0 { + if cursor == displayedText.count, cursor != 0 { cursor -= 1 } while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 { @@ -41,10 +41,10 @@ extension ctlInputMethod { func show(candidateWindowWith state: InputStateProtocol) { guard let client = client() else { return } var isTypingVertical: Bool { - if let state = state as? InputState.ChoosingCandidate { - return state.isTypingVertical - } else if let state = state as? InputState.AssociatedPhrases { - return state.isTypingVertical + if state.type == .ofCandidates { + return ctlInputMethod.isVerticalTypingSituation + } else if state.type == ..ofAssociates { + return ctlInputMethod.isVerticalTypingSituation } return false } @@ -52,7 +52,7 @@ extension ctlInputMethod { var candidates: [(String, String)] = .init() if let state = state as? InputState.ChoosingCandidate { candidates = state.candidates - } else if let state = state as? InputState.AssociatedPhrases { + } else if let state = state as? InputState.Associates { candidates = state.candidates } if isTypingVertical { return true } @@ -106,7 +106,7 @@ extension ctlInputMethod { let candidateKeys = mgrPrefs.candidateKeys let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) - let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" + let keyLabelSuffix = state is InputState.Associates ? "^" : "" ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map { CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) } @@ -128,7 +128,7 @@ extension ctlInputMethod { if let state = state as? InputState.ChoosingCandidate { cursor = state.cursorIndex - if cursor == state.composingBuffer.count, cursor != 0 { + if cursor == state.displayedText.count, cursor != 0 { cursor -= 1 } } diff --git a/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift b/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift index d182a85d..4d534fa5 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift @@ -27,7 +27,7 @@ extension ctlInputMethod { handle(state: newState, previous: prevState) case let newState as InputState.Empty: handle(state: newState, previous: prevState) - case let newState as InputState.EmptyIgnoringPreviousState: + case let newState as InputState.Abortion: handle(state: newState, previous: prevState) case let newState as InputState.Committing: handle(state: newState, previous: prevState) @@ -37,7 +37,7 @@ extension ctlInputMethod { handle(state: newState, previous: prevState) case let newState as InputState.ChoosingCandidate: handle(state: newState, previous: prevState) - case let newState as InputState.AssociatedPhrases: + case let newState as InputState.Associates: handle(state: newState, previous: prevState) case let newState as InputState.SymbolTable: handle(state: newState, previous: prevState) @@ -48,7 +48,7 @@ extension ctlInputMethod { /// 針對受 .NotEmpty() 管轄的非空狀態,在組字區內顯示游標。 func setInlineDisplayWithCursor() { guard let client = client() else { return } - if let state = state as? InputState.AssociatedPhrases { + if let state = state as? InputState.Associates { client.setMarkedText( state.attributedString, selectionRange: NSRange(location: 0, length: 0), replacementRange: NSRange(location: NSNotFound, length: NSNotFound) @@ -87,7 +87,7 @@ extension ctlInputMethod { [.languageIdentifier: identifier], range: NSRange( location: 0, - length: state.composingBuffer.utf16.count + length: state.displayedText.utf16.count ) ) } @@ -141,9 +141,9 @@ extension ctlInputMethod { _ = state // 防止格式整理工具毀掉與此對應的參數。 ctlInputMethod.ctlCandidateCurrent.visible = false ctlInputMethod.tooltipController.hide() - // 全專案用以判斷「.EmptyIgnoringPreviousState」的地方僅此一處。 + // 全專案用以判斷「.Abortion」的地方僅此一處。 if let previous = previous as? InputState.NotEmpty, - !(state is InputState.EmptyIgnoringPreviousState) + !(state is InputState.Abortion) { commit(text: previous.committingBufferConverted) } @@ -156,7 +156,7 @@ extension ctlInputMethod { } private func handle( - state: InputState.EmptyIgnoringPreviousState, previous: InputStateProtocol + state: InputState.Abortion, previous: InputStateProtocol ) { _ = state // 防止格式整理工具毀掉與此對應的參數。 _ = previous // 防止格式整理工具毀掉與此對應的參數。 @@ -188,7 +188,7 @@ extension ctlInputMethod { setInlineDisplayWithCursor() if !state.tooltip.isEmpty { show( - tooltip: state.tooltip, composingBuffer: state.composingBuffer, + tooltip: state.tooltip, displayedText: state.displayedText, cursorIndex: state.cursorIndex ) } @@ -202,7 +202,7 @@ extension ctlInputMethod { ctlInputMethod.tooltipController.hide() } else { show( - tooltip: state.tooltip, composingBuffer: state.composingBuffer, + tooltip: state.tooltip, displayedText: state.displayedText, cursorIndex: state.markerIndex ) } @@ -222,7 +222,7 @@ extension ctlInputMethod { show(candidateWindowWith: state) } - private func handle(state: InputState.AssociatedPhrases, previous: InputStateProtocol) { + private func handle(state: InputState.Associates, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 ctlInputMethod.tooltipController.hide() setInlineDisplayWithCursor() diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift b/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift index c8235ca8..b3615e68 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift @@ -212,11 +212,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n" - + { - mgrPrefs.toggleSCPCTypingModeEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleSCPCTypingModeEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -224,11 +222,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n" - + { - mgrPrefs.toggleChineseConversionEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleChineseConversionEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -236,11 +232,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n" - + { - mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -248,11 +242,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n" - + { - mgrPrefs.toggleCurrencyNumeralsEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleCurrencyNumeralsEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -260,11 +252,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n" - + { - mgrPrefs.toggleHalfWidthPunctuationEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleHalfWidthPunctuationEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -272,11 +262,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n" - + { - mgrPrefs.toggleCNS11643Enabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleCNS11643Enabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -284,11 +272,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n" - + { - mgrPrefs.toggleSymbolInputEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleSymbolInputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -296,11 +282,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n" - + { - mgrPrefs.toggleAssociatedPhrasesEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.toggleAssociatedPhrasesEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } @@ -308,11 +292,9 @@ extension ctlInputMethod { resetKeyHandler() NotifierController.notify( message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n" - + { - mgrPrefs.togglePhraseReplacementEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - }() + + mgrPrefs.togglePhraseReplacementEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") ) } diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 61cf321c..80401d7f 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -438,6 +438,7 @@ public enum mgrPrefs { @UserDefault(key: UserDef.kTrimUnfinishedReadingsOnCommit.rawValue, defaultValue: true) static var trimUnfinishedReadingsOnCommit: Bool + // MARK: - Settings (Tier 2) @UserDefault(key: UserDef.kUseIMKCandidateWindow.rawValue, defaultValue: false) @@ -458,6 +459,8 @@ public enum mgrPrefs { @UserDefault(key: UserDef.kMaxCandidateLength.rawValue, defaultValue: 10) static var maxCandidateLength: Int + static var allowedMarkRange: ClosedRange = mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength + @UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep.rawValue, defaultValue: true) static var shouldNotFartInLieuOfBeep: Bool diff --git a/Source/Modules/LangModelRelated/LMSymbolNode.swift b/Source/Modules/LangModelRelated/LMSymbolNode.swift index de46c8a6..19091d1b 100644 --- a/Source/Modules/LangModelRelated/LMSymbolNode.swift +++ b/Source/Modules/LangModelRelated/LMSymbolNode.swift @@ -10,7 +10,7 @@ import Foundation -class SymbolNode { +public class SymbolNode { var title: String var children: [SymbolNode]? var previous: SymbolNode? diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 133975ea..24d06b15 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -117,6 +117,8 @@ 5BEDB724283B4C250078EB25 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; }; 5BEDB725283B4C250078EB25 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; }; 5BF0B84C28C070B000795FC6 /* NSEventExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF0B84B28C070B000795FC6 /* NSEventExtension.swift */; }; + 5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF56F9728C39A2700DD6839 /* IMEState.swift */; }; + 5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF56F9928C39D1800DD6839 /* IMEStateData.swift */; }; 5BF9DA2728840E6200DBD48E /* template-usersymbolphrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */; }; 5BF9DA2828840E6200DBD48E /* template-exclusions.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2328840E6200DBD48E /* template-exclusions.txt */; }; 5BF9DA2928840E6200DBD48E /* template-associatedPhrases-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */; }; @@ -346,6 +348,8 @@ 5BEDB720283B4AEA0078EB25 /* data-cht.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "data-cht.plist"; path = "Data/data-cht.plist"; sourceTree = ""; }; 5BF0B84B28C070B000795FC6 /* NSEventExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEventExtension.swift; sourceTree = ""; }; 5BF255CD28B2694E003ECB60 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewing-Bridging-Header.h"; sourceTree = ""; }; + 5BF56F9728C39A2700DD6839 /* IMEState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMEState.swift; sourceTree = ""; }; + 5BF56F9928C39D1800DD6839 /* IMEStateData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMEStateData.swift; sourceTree = ""; }; 5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-usersymbolphrases.txt"; sourceTree = ""; usesTabs = 0; }; 5BF9DA2328840E6200DBD48E /* template-exclusions.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-exclusions.txt"; sourceTree = ""; usesTabs = 0; }; 5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; name = "template-associatedPhrases-chs.txt"; path = "../Data/components/chs/template-associatedPhrases-chs.txt"; sourceTree = ""; usesTabs = 0; }; @@ -498,6 +502,8 @@ 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */, 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */, 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */, + 5BF56F9728C39A2700DD6839 /* IMEState.swift */, + 5BF56F9928C39D1800DD6839 /* IMEStateData.swift */, D461B791279DAC010070E734 /* InputState.swift */, 5BD0113C2818543900609769 /* KeyHandler_Core.swift */, 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */, @@ -1206,6 +1212,7 @@ 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */, 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, D461B792279DAC010070E734 /* InputState.swift in Sources */, + 5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */, 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */, D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */, @@ -1232,6 +1239,7 @@ D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */, 5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */, + 5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */, 5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */, 5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */, 5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,