From 4dc17d8a2c0d0a9b50b36a0608437d14d0a0083a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 11:13:02 +0800 Subject: [PATCH 01/16] UOM // Add a forgotten kanji to the whitelist. --- Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift index 89adccea..8d50fb04 100644 --- a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift +++ b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift @@ -131,7 +131,7 @@ extension vChewing { walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false ) -> String { let arrEndingPunctuation = [",", "。", "!", "?", "」", "』", "”", "’"] - let whiteList = "你他妳她祢她它牠再在" + let whiteList = "你他妳她祢衪它牠再在" var arrNodes: [Megrez.NodeAnchor] = [] var intLength = 0 for theNodeAnchor in walkedAnchors { From dedc8770cf7470687f7715117015e029e5f2ad97 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 15:34:28 +0800 Subject: [PATCH 02/16] KeyHandler // Let invalid input pass when the state is Empty. --- .../Modules/ControllerModules/KeyHandler_HandleInput.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 519b6e58..78e9ee4e 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -48,6 +48,12 @@ extension KeyHandler { // 提前過濾掉一些不合規的按鍵訊號輸入,免得相關按鍵訊號被送給 Megrez 引發輸入法崩潰。 if input.isInvalidInput { + // 在「.Empty(IgnoringPreviousState) 與 .Deactivated」狀態下的首次不合規按鍵輸入可以直接放行。 + if state is InputState.Empty || state is InputState.Deactivated + || state is InputState.EmptyIgnoringPreviousState + { + return false + } IME.prtDebugIntel("550BCF7B: KeyHandler just refused an invalid input.") errorCallback() stateCallback(state) From 63659ee66235b3c60213e76af162298b7ae7fb90 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 17:36:29 +0800 Subject: [PATCH 03/16] Repo // InputState -> InputStateProtocol, plus clang-format. --- .../ControllerModules/InputState.swift | 44 ++++++++++++--- .../ControllerModules/KeyHandler_Core.swift | 7 ++- .../KeyHandler_HandleCandidate.swift | 4 +- .../KeyHandler_HandleInput.swift | 4 +- .../ControllerModules/KeyHandler_States.swift | 56 +++++++++---------- .../ControllerModules/StringUtils.swift | 6 +- .../ctlInputMethod_Core.swift | 50 ++++++++--------- Source/UI/CandidateUI/ctlCandidate.swift | 1 + 8 files changed, 101 insertions(+), 71 deletions(-) diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index 9a6a2df1..342cba31 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -28,6 +28,25 @@ import Cocoa // 註:所有 InputState 型別均不適合使用 Struct,因為 Struct 無法相互繼承派生。 +// 用以讓每個狀態自描述的 enum。 +enum StateType { + case ofDeactivated + case ofAssociatedPhrases + case ofEmpty + case ofEmptyIgnorePreviousState + case ofCommitting + case ofNotEmpty + case ofInputting + case ofMarking + case ofChooseCandidate + case ofSymbolTable +} + +// 所有 InputState 均遵守该协定: +protocol InputStateProtocol { + var type: StateType { get } +} + /// 此型別用以呈現輸入法控制器(ctlInputMethod)的各種狀態。 /// /// 從實際角度來看,輸入法屬於有限態械(Finite State Machine)。其藉由滑鼠/鍵盤 @@ -60,9 +79,10 @@ import Cocoa /// 詞音組合放入語彙濾除清單。 /// - .ChoosingCandidate: 叫出選字窗、允許使用者選字。 /// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。 -class InputState { +enum InputState { /// .Deactivated: 使用者沒在使用輸入法。 - class Deactivated: InputState { + class Deactivated: InputStateProtocol { + public var type: StateType { .ofDeactivated } var description: String { "" } @@ -72,7 +92,9 @@ class InputState { /// .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。 /// 抑或是剛剛敲字遞交給客體應用、準備新的輸入行為。 - class Empty: InputState { + class Empty: InputStateProtocol { + public var type: StateType { .ofEmpty } + var composingBuffer: String { "" } @@ -87,6 +109,7 @@ class InputState { /// .EmptyIgnorePreviousState: 與 Empty 類似, /// 但會扔掉上一個狀態的內容、不將這些內容遞交給客體應用。 class EmptyIgnoringPreviousState: Empty { + override public var type: StateType { .ofEmptyIgnorePreviousState } override var description: String { "" } @@ -95,7 +118,8 @@ class InputState { // MARK: - /// .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。 - class Committing: InputState { + class Committing: InputStateProtocol { + public var type: StateType { .ofCommitting } private(set) var textToCommit: String = "" convenience init(textToCommit: String) { @@ -112,13 +136,13 @@ class InputState { /// .AssociatedPhrases: 逐字選字模式內的聯想詞輸入狀態。 /// 因為逐字選字模式不需要在組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。 - class AssociatedPhrases: InputState { + class AssociatedPhrases: InputStateProtocol { + public var type: StateType { .ofAssociatedPhrases } private(set) var candidates: [String] = [] private(set) var isTypingVertical: Bool = false init(candidates: [String], isTypingVertical: Bool) { self.candidates = candidates self.isTypingVertical = isTypingVertical - super.init() } var description: String { @@ -134,7 +158,8 @@ class InputState { /// 還是將這個範圍的詞音組合放入語彙濾除清單。 /// - .ChoosingCandidate: 叫出選字窗、允許使用者選字。 /// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。 - class NotEmpty: InputState { + class NotEmpty: InputStateProtocol { + public var type: StateType { .ofNotEmpty } private(set) var composingBuffer: String private(set) var cursorIndex: Int = 0 { didSet { cursorIndex = max(cursorIndex, 0) } } public var composingBufferConverted: String { @@ -149,7 +174,6 @@ class InputState { init(composingBuffer: String, cursorIndex: Int) { self.composingBuffer = composingBuffer - super.init() defer { self.cursorIndex = cursorIndex } } @@ -175,6 +199,7 @@ class InputState { /// .Inputting: 使用者輸入了內容。此時會出現組字區(Compositor)。 class Inputting: NotEmpty { + override public var type: StateType { .ofInputting } var textToCommit: String = "" var tooltip: String = "" @@ -192,6 +217,7 @@ class InputState { /// .Marking: 使用者在組字區內標記某段範圍,可以決定是添入新詞、 /// 還是將這個範圍的詞音組合放入語彙濾除清單。 class Marking: NotEmpty { + override public var type: StateType { .ofMarking } private var allowedMarkRange: ClosedRange = mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } } private(set) var markedRange: Range @@ -358,6 +384,7 @@ class InputState { /// .ChoosingCandidate: 叫出選字窗、允許使用者選字。 class ChoosingCandidate: NotEmpty { + override public var type: StateType { .ofChooseCandidate } private(set) var candidates: [String] private(set) var isTypingVertical: Bool @@ -376,6 +403,7 @@ class InputState { /// .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。 class SymbolTable: ChoosingCandidate { + override public var type: StateType { .ofSymbolTable } var node: SymbolNode init(node: SymbolNode, isTypingVertical: Bool) { diff --git a/Source/Modules/ControllerModules/KeyHandler_Core.swift b/Source/Modules/ControllerModules/KeyHandler_Core.swift index 31c918b5..58d06d9e 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Core.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Core.swift @@ -39,7 +39,7 @@ protocol KeyHandlerDelegate { _: KeyHandler, didSelectCandidateAt index: Int, ctlCandidate controller: ctlCandidate ) - func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) + func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol) -> Bool } @@ -264,7 +264,7 @@ class KeyHandler { } if mgrPrefs.fetchSuggestionsFromUserOverrideModel, !mgrPrefs.useSCPCTypingMode { let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score } - let arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map { $0.keyValue.value } + let arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map(\.keyValue.value) arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates arrCandidates = arrCandidates.deduplicate arrCandidates = arrCandidates.stableSort { $0.count > $1.count } @@ -276,7 +276,8 @@ class KeyHandler { func fetchSuggestedCandidates() -> [Megrez.Unigram] { currentUOM.suggest( walkedAnchors: walkedAnchors, cursorIndex: compositorCursorIndex, - timestamp: NSDate().timeIntervalSince1970) + timestamp: NSDate().timeIntervalSince1970 + ) } /// 向半衰引擎詢問可能的選字建議、且套用給組字器內的當前游標位置。 diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift index a861c828..8747a0b5 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift @@ -32,9 +32,9 @@ import Cocoa extension KeyHandler { func handleCandidate( - state: InputState, + state: InputStateProtocol, input: InputSignal, - stateCallback: @escaping (InputState) -> Void, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { let inputText = input.inputText diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 78e9ee4e..049676f5 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -34,8 +34,8 @@ import Cocoa extension KeyHandler { func handle( input: InputSignal, - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { let charCode: UniChar = input.charCode diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index f40b4762..92bb805c 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -211,7 +211,7 @@ extension KeyHandler { func handleMarkingState( _ state: InputState.Marking, input: InputSignal, - stateCallback: @escaping (InputState) -> Void, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { if input.isESC { @@ -288,9 +288,9 @@ extension KeyHandler { /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handlePunctuation( _ customPunctuation: String, - state: InputState, + state: InputStateProtocol, usingVerticalTyping isTypingVertical: Bool, - stateCallback: @escaping (InputState) -> Void, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { if !ifLangModelHasUnigrams(forKey: customPunctuation) { @@ -339,8 +339,8 @@ extension KeyHandler { /// - stateCallback: 狀態回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleEnter( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback _: @escaping () -> Void ) -> Bool { guard let currentState = state as? InputState.Inputting else { return false } @@ -359,8 +359,8 @@ extension KeyHandler { /// - stateCallback: 狀態回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleCtrlCommandEnter( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback _: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -390,8 +390,8 @@ extension KeyHandler { /// - stateCallback: 狀態回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleCtrlOptionCommandEnter( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback _: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -435,8 +435,8 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleBackspace( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -474,8 +474,8 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleDelete( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -514,8 +514,8 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleAbsorbedArrowKey( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -536,8 +536,8 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleHome( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -570,8 +570,8 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleEnd( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -603,8 +603,8 @@ extension KeyHandler { /// - stateCallback: 狀態回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleEsc( - state: InputState, - stateCallback: @escaping (InputState) -> Void, + state: InputStateProtocol, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback _: @escaping () -> Void ) -> Bool { guard state is InputState.Inputting else { return false } @@ -638,9 +638,9 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleForward( - state: InputState, + state: InputStateProtocol, input: InputSignal, - stateCallback: @escaping (InputState) -> Void, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard let currentState = state as? InputState.Inputting else { return false } @@ -694,9 +694,9 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleBackward( - state: InputState, + state: InputStateProtocol, input: InputSignal, - stateCallback: @escaping (InputState) -> Void, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { guard let currentState = state as? InputState.Inputting else { return false } @@ -750,12 +750,12 @@ extension KeyHandler { /// - errorCallback: 錯誤回呼。 /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 func handleInlineCandidateRotation( - state: InputState, + state: InputStateProtocol, reverseModifier: Bool, - stateCallback: @escaping (InputState) -> Void, + stateCallback: @escaping (InputStateProtocol) -> Void, errorCallback: @escaping () -> Void ) -> Bool { - if composer.isEmpty && (compositor.isEmpty || walkedAnchors.isEmpty) { return false } + if composer.isEmpty, compositor.isEmpty || walkedAnchors.isEmpty { return false } guard state is InputState.Inputting else { guard state is InputState.Empty else { IME.prtDebugIntel("6044F081") diff --git a/Source/Modules/ControllerModules/StringUtils.swift b/Source/Modules/ControllerModules/StringUtils.swift index d2e33224..fc118117 100644 --- a/Source/Modules/ControllerModules/StringUtils.swift +++ b/Source/Modules/ControllerModules/StringUtils.swift @@ -46,7 +46,7 @@ extension String { /// in an NSString (or .utf16). public func charIndexLiteral(from utf16Index: Int) -> Int { var length = 0 - for (i, character) in self.enumerated() { + for (i, character) in enumerated() { length += character.utf16.count if length > utf16Index { return (i) @@ -65,8 +65,8 @@ extension String { return self[..) -> String { - let arr = Array(self.utf16)[r].map { $0 } + internal func utf16SubString(with r: Range) -> String { + let arr = Array(utf16)[r].map { $0 } return String(utf16CodeUnits: arr, count: arr.count) } } diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift index 685093e6..9d1657e8 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift @@ -54,7 +54,7 @@ class ctlInputMethod: IMKInputController { /// 按鍵調度模組的副本。 private var keyHandler: KeyHandler = .init() /// 用以記錄當前輸入法狀態的變數。 - private var state: InputState = .Empty() + private var state: InputStateProtocol = InputState.Empty() // MARK: - 工具函式 @@ -107,7 +107,7 @@ class ctlInputMethod: IMKInputController { if client().bundleIdentifier() != Bundle.main.bundleIdentifier { // 強制重設當前鍵盤佈局、使其與偏好設定同步。 setKeyLayout() - handle(state: .Empty()) + handle(state: InputState.Empty()) } // 除此之外就不要動了,免得在點開輸入法自身的視窗時卡死。 (NSApp.delegate as? AppDelegate)?.checkForUpdate() } @@ -117,8 +117,8 @@ class ctlInputMethod: IMKInputController { override func deactivateServer(_ sender: Any!) { _ = sender // 防止格式整理工具毀掉與此對應的參數。 keyHandler.clear() - handle(state: .Empty()) - handle(state: .Deactivated()) + handle(state: InputState.Empty()) + handle(state: InputState.Deactivated()) } /// 切換至某一個輸入法的某個副本時(比如威注音的簡體輸入法副本與繁體輸入法副本),會觸發該函式。 @@ -149,7 +149,7 @@ class ctlInputMethod: IMKInputController { if client().bundleIdentifier() != Bundle.main.bundleIdentifier { // 強制重設當前鍵盤佈局、使其與偏好設定同步。這裡的這一步也不能省略。 setKeyLayout() - handle(state: .Empty()) + handle(state: InputState.Empty()) } // 除此之外就不要動了,免得在點開輸入法自身的視窗時卡死。 } @@ -245,7 +245,7 @@ extension ctlInputMethod { /// 先將舊狀態單獨記錄起來,再將新舊狀態作為參數, /// 根據新狀態本身的狀態種類來判斷交給哪一個專門的函式來處理。 /// - Parameter newState: 新狀態。 - private func handle(state newState: InputState) { + private func handle(state newState: InputStateProtocol) { let prevState = state state = newState @@ -341,7 +341,7 @@ extension ctlInputMethod { ) } - private func handle(state: InputState.Deactivated, previous: InputState) { + private func handle(state: InputState.Deactivated, previous: InputStateProtocol) { _ = state // 防止格式整理工具毀掉與此對應的參數。 ctlCandidateCurrent.delegate = nil ctlCandidateCurrent.visible = false @@ -352,7 +352,7 @@ extension ctlInputMethod { clearInlineDisplay() } - private func handle(state: InputState.Empty, previous: InputState) { + private func handle(state: InputState.Empty, previous: InputStateProtocol) { _ = state // 防止格式整理工具毀掉與此對應的參數。 ctlCandidateCurrent.visible = false hideTooltip() @@ -365,7 +365,7 @@ extension ctlInputMethod { } private func handle( - state: InputState.EmptyIgnoringPreviousState, previous: InputState + state: InputState.EmptyIgnoringPreviousState, previous: InputStateProtocol ) { _ = state // 防止格式整理工具毀掉與此對應的參數。 _ = previous // 防止格式整理工具毀掉與此對應的參數。 @@ -374,7 +374,7 @@ extension ctlInputMethod { clearInlineDisplay() } - private func handle(state: InputState.Committing, previous: InputState) { + private func handle(state: InputState.Committing, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 ctlCandidateCurrent.visible = false hideTooltip() @@ -385,7 +385,7 @@ extension ctlInputMethod { clearInlineDisplay() } - private func handle(state: InputState.Inputting, previous: InputState) { + private func handle(state: InputState.Inputting, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 ctlCandidateCurrent.visible = false hideTooltip() @@ -402,7 +402,7 @@ extension ctlInputMethod { } } - private func handle(state: InputState.Marking, previous: InputState) { + private func handle(state: InputState.Marking, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 ctlCandidateCurrent.visible = false setInlineDisplayWithCursor() @@ -416,21 +416,21 @@ extension ctlInputMethod { } } - private func handle(state: InputState.ChoosingCandidate, previous: InputState) { + private func handle(state: InputState.ChoosingCandidate, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 hideTooltip() setInlineDisplayWithCursor() show(candidateWindowWith: state) } - private func handle(state: InputState.SymbolTable, previous: InputState) { + private func handle(state: InputState.SymbolTable, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 hideTooltip() setInlineDisplayWithCursor() show(candidateWindowWith: state) } - private func handle(state: InputState.AssociatedPhrases, previous: InputState) { + private func handle(state: InputState.AssociatedPhrases, previous: InputStateProtocol) { _ = previous // 防止格式整理工具毀掉與此對應的參數。 hideTooltip() clearInlineDisplay() @@ -441,7 +441,7 @@ extension ctlInputMethod { // MARK: - extension ctlInputMethod { - private func show(candidateWindowWith state: InputState) { + private func show(candidateWindowWith state: InputStateProtocol) { var isTypingVertical: Bool { if let state = state as? InputState.ChoosingCandidate { return state.isTypingVertical @@ -595,7 +595,7 @@ extension ctlInputMethod: KeyHandlerDelegate { ctlCandidate(controller, didSelectCandidateAtIndex: index) } - func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) + func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol) -> Bool { guard let state = state as? InputState.Marking else { @@ -656,13 +656,13 @@ extension ctlInputMethod: ctlCandidateDelegate { let node = state.node.children?[index] { if let children = node.children, !children.isEmpty { - handle(state: .Empty()) // 防止縱橫排選字窗同時出現 + handle(state: InputState.Empty()) // 防止縱橫排選字窗同時出現 handle( - state: .SymbolTable(node: node, isTypingVertical: state.isTypingVertical) + state: InputState.SymbolTable(node: node, isTypingVertical: state.isTypingVertical) ) } else { - handle(state: .Committing(textToCommit: node.title)) - handle(state: .Empty()) + handle(state: InputState.Committing(textToCommit: node.title)) + handle(state: InputState.Empty()) } return } @@ -676,7 +676,7 @@ extension ctlInputMethod: ctlCandidateDelegate { if mgrPrefs.useSCPCTypingMode { keyHandler.clear() let composingBuffer = inputting.composingBuffer - handle(state: .Committing(textToCommit: composingBuffer)) + handle(state: InputState.Committing(textToCommit: composingBuffer)) if mgrPrefs.associatedPhrasesEnabled, let associatePhrases = keyHandler.buildAssociatePhraseState( withKey: composingBuffer, isTypingVertical: state.isTypingVertical @@ -684,7 +684,7 @@ extension ctlInputMethod: ctlCandidateDelegate { { handle(state: associatePhrases) } else { - handle(state: .Empty()) + handle(state: InputState.Empty()) } } else { handle(state: inputting) @@ -694,7 +694,7 @@ extension ctlInputMethod: ctlCandidateDelegate { if let state = state as? InputState.AssociatedPhrases { let selectedValue = state.candidates[index] - handle(state: .Committing(textToCommit: selectedValue)) + handle(state: InputState.Committing(textToCommit: selectedValue)) if mgrPrefs.associatedPhrasesEnabled, let associatePhrases = keyHandler.buildAssociatePhraseState( withKey: selectedValue, isTypingVertical: state.isTypingVertical @@ -702,7 +702,7 @@ extension ctlInputMethod: ctlCandidateDelegate { { handle(state: associatePhrases) } else { - handle(state: .Empty()) + handle(state: InputState.Empty()) } } } diff --git a/Source/UI/CandidateUI/ctlCandidate.swift b/Source/UI/CandidateUI/ctlCandidate.swift index 1bd93c26..29e06964 100644 --- a/Source/UI/CandidateUI/ctlCandidate.swift +++ b/Source/UI/CandidateUI/ctlCandidate.swift @@ -51,6 +51,7 @@ public class ctlCandidate: NSWindowController { case horizontal case vertical } + public var currentLayout: Layout = .horizontal public weak var delegate: ctlCandidateDelegate? { didSet { From 6d2d3dc277de3cca2780b40ebdb78b96ba064747 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 18:55:19 +0800 Subject: [PATCH 04/16] KeyHandler // Change condition of state returning in handle(). --- 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 049676f5..fc84e551 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -190,7 +190,7 @@ extension KeyHandler { errorCallback() composer.clear() // 根據「組字器是否為空」來判定回呼哪一種狀態。 - stateCallback((compositorLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState) + stateCallback((compositor.isEmpty) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState) return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。 } From 99e84da69c0a7ec7971c199a3c39052b399ea6a9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 19:33:38 +0800 Subject: [PATCH 05/16] KeyHandler // Fix conditioning error in handleBackspace(). --- Source/Modules/ControllerModules/KeyHandler_States.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index 92bb805c..a9e5c5e4 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -444,7 +444,7 @@ extension KeyHandler { if composer.hasToneMarker(withNothingElse: true) { composer.clear() } else if composer.isEmpty { - if compositorCursorIndex >= 0 { + if compositorCursorIndex > 0 { deleteCompositorReadingAtTheRearOfCursor() walk() } else { From 79296645c73f89b1650f7ee9977940536d14f2f6 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 22:37:11 +0800 Subject: [PATCH 06/16] InputSignal // Set default value of "isVerticalTyping" on init. --- Source/Modules/ControllerModules/InputSignal.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Modules/ControllerModules/InputSignal.swift b/Source/Modules/ControllerModules/InputSignal.swift index fa4e2d2c..f20951ec 100644 --- a/Source/Modules/ControllerModules/InputSignal.swift +++ b/Source/Modules/ControllerModules/InputSignal.swift @@ -138,7 +138,7 @@ struct InputSignal: CustomStringConvertible { public init( inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, - isVerticalTyping: Bool, inputTextIgnoringModifiers: String? = nil + isVerticalTyping: Bool = false, inputTextIgnoringModifiers: String? = nil ) { self.inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") self.inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( @@ -155,7 +155,7 @@ struct InputSignal: CustomStringConvertible { defineArrowKeys() } - public init(event: NSEvent, isVerticalTyping: Bool) { + public init(event: NSEvent, isVerticalTyping: Bool = false) { inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( event.charactersIgnoringModifiers ?? "") From 53c72f40929dcd7d3a6bc6bad15d9df20795c558 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 25 Jun 2022 08:45:40 +0800 Subject: [PATCH 07/16] mgrPrefs // Add Snapshot extension. --- Source/Modules/IMEModules/mgrPrefs.swift | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 21ee8a22..d62688d9 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -562,3 +562,49 @@ public enum mgrPrefs { @UserDefault(key: UserDef.kUsingHotKeyHalfWidthASCII, defaultValue: true) static var usingHotKeyHalfWidthASCII: Bool } + +// MARK: Snapshot Extension + +var snapshot: [String: Any]? + +extension mgrPrefs { + static var allKeys: [String] { + [ + UserDef.kIsDebugModeEnabled, UserDef.kMostRecentInputMode, UserDef.kUserDataFolderSpecified, + UserDef.kCheckUpdateAutomatically, UserDef.kMandarinParser, UserDef.kBasicKeyboardLayout, + UserDef.kShowPageButtonsInCandidateWindow, UserDef.kCandidateListTextSize, UserDef.kAppleLanguages, + UserDef.kShouldAutoReloadUserDataFiles, UserDef.kuseRearCursorMode, UserDef.kUseHorizontalCandidateList, + UserDef.kComposingBufferSize, UserDef.kChooseCandidateUsingSpace, UserDef.kCNS11643Enabled, + UserDef.kSymbolInputEnabled, UserDef.kChineseConversionEnabled, UserDef.kShiftJISShinjitaiOutputEnabled, + UserDef.kHalfWidthPunctuationEnabled, UserDef.kMoveCursorAfterSelectingCandidate, UserDef.kEscToCleanInputBuffer, + UserDef.kSpecifyShiftTabKeyBehavior, UserDef.kSpecifyShiftSpaceKeyBehavior, + UserDef.kAllowBoostingSingleKanjiAsUserPhrase, UserDef.kUseSCPCTypingMode, UserDef.kMaxCandidateLength, + UserDef.kShouldNotFartInLieuOfBeep, UserDef.kShowHanyuPinyinInCompositionBuffer, + UserDef.kInlineDumpPinyinInLieuOfZhuyin, UserDef.kFetchSuggestionsFromUserOverrideModel, + UserDef.kCandidateTextFontName, UserDef.kCandidateKeyLabelFontName, UserDef.kCandidateKeys, + UserDef.kAssociatedPhrasesEnabled, UserDef.kPhraseReplacementEnabled, UserDef.kUsingHotKeySCPC, + UserDef.kUsingHotKeyAssociates, UserDef.kUsingHotKeyCNS, UserDef.kUsingHotKeyKangXi, UserDef.kUsingHotKeyJIS, + UserDef.kUsingHotKeyHalfWidthASCII, + ] + } + + func reset() { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.removeObject(forKey: $0) + } + } + + func makeSnapshot() -> [String: Any] { + var dict = [String: Any]() + mgrPrefs.allKeys.forEach { + dict[$0] = UserDefaults.standard.object(forKey: $0) + } + return dict + } + + func restore(from snapshot: [String: Any]) { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.set(snapshot[$0], forKey: $0) + } + } +} From 62a41f4ea9807dec10bb8f77d9f21db5e118e65f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 25 Jun 2022 08:43:43 +0800 Subject: [PATCH 08/16] Main // Use switch to manage incoming commandline arguments. --- Source/Modules/main.swift | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Source/Modules/main.swift b/Source/Modules/main.swift index bf266f8f..e6a3a8b8 100644 --- a/Source/Modules/main.swift +++ b/Source/Modules/main.swift @@ -29,15 +29,30 @@ import InputMethodKit let kConnectionName = "vChewing_1_Connection" -if CommandLine.arguments.count > 1 { - if CommandLine.arguments[1] == "install" { - let exitCode = IME.registerInputMethod() - exit(exitCode) - } - if CommandLine.arguments[1] == "uninstall" { - let exitCode = IME.uninstall(isSudo: IME.isSudoMode) - exit(exitCode) - } +switch max(CommandLine.arguments.count - 1, 0) { + case 0: break + case 1, 2: + do { + switch CommandLine.arguments[1] { + case "install": + do { + if CommandLine.arguments[1] == "install" { + let exitCode = IME.registerInputMethod() + exit(exitCode) + } + } + case "uninstall": + do { + if CommandLine.arguments[1] == "uninstall" { + let exitCode = IME.uninstall(isSudo: IME.isSudoMode) + exit(exitCode) + } + } + default: break + } + } + exit(0) + default: exit(0) } guard let mainNibName = Bundle.main.infoDictionary?["NSMainNibFile"] as? String else { From f62937492fe3356c69655747f95cca2eeed7a0cf Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 11:57:22 +0800 Subject: [PATCH 09/16] Repo // Enable testability. --- vChewing.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 2f88d7fb..8d4fc221 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -1651,6 +1651,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -1720,6 +1721,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = z; From d5cbd10a34ad108fd9ee702e236b135e58cd1ccd Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 11:58:06 +0800 Subject: [PATCH 10/16] Repo // Prepare for writing unit tests. --- vChewing.xcodeproj/project.pbxproj | 30 ++++++++++++++++++++++++++---- vChewingTests/vChewingTests.swift | 29 ----------------------------- 2 files changed, 26 insertions(+), 33 deletions(-) delete mode 100644 vChewingTests/vChewingTests.swift diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 8d4fc221..89ee1958 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 5B0AF8B527B2C8290096FE54 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */; }; 5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */; }; 5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */; }; - 5B2F2BB6286216A500B8557B /* vChewingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2F2BB5286216A500B8557B /* vChewingTests.swift */; }; 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; }; 5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */; }; 5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */; }; @@ -79,6 +78,15 @@ 5BBBB77627AED70B0023B93A /* MenuIcon-TCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */; }; 5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBBB77927AEDC690023B93A /* clsSFX.swift */; }; 5BC2652227E04B7E00700291 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2652127E04B7B00700291 /* uninstall.sh */; }; + 5BC4479D2865686500EDC323 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; }; + 5BC4479E2865686500EDC323 /* data-cht.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB720283B4AEA0078EB25 /* data-cht.plist */; }; + 5BC4479F2865686500EDC323 /* data-cns.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71D283B4AEA0078EB25 /* data-cns.plist */; }; + 5BC447A02865686500EDC323 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; }; + 5BC447A12865686500EDC323 /* data-zhuyinwen.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.plist */; }; + 5BC447A628656A1900EDC323 /* PrefManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */; }; + 5BC447A928656A1900EDC323 /* UpdateAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */; }; + 5BC447AE2865CFC200EDC323 /* KeyHandlerTestsNormalCHS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */; }; + 5BC6E03C286692BE00291771 /* KeyHandlerTestsSCPCCHT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */; }; 5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113A28180D6100609769 /* LMInstantiator.swift */; }; 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113C2818543900609769 /* KeyHandler_Core.swift */; }; 5BD05BCA27B2A43D004C4F1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; @@ -194,7 +202,6 @@ 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateUniversal.swift; sourceTree = ""; }; 5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = ""; }; 5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 5B2F2BB5286216A500B8557B /* vChewingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = vChewingTests.swift; sourceTree = ""; }; 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = ""; }; 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B3A87BB28597CDB0090E163 /* LMSymbolNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMSymbolNode.swift; sourceTree = ""; }; @@ -259,6 +266,10 @@ 5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPreInstall.sh; sourceTree = ""; }; 5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = ""; }; 5BC2652127E04B7B00700291 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = uninstall.sh; sourceTree = ""; }; + 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefManagerTests.swift; sourceTree = ""; }; + 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsSCPCCHT.swift; sourceTree = ""; }; + 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsNormalCHS.swift; sourceTree = ""; }; + 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAPITests.swift; sourceTree = ""; }; 5BD0113A28180D6100609769 /* LMInstantiator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMInstantiator.swift; sourceTree = ""; usesTabs = 0; }; 5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = ""; usesTabs = 0; }; 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingPhraseEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -390,7 +401,10 @@ 5B2F2BB4286216A500B8557B /* vChewingTests */ = { isa = PBXGroup; children = ( - 5B2F2BB5286216A500B8557B /* vChewingTests.swift */, + 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */, + 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */, + 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */, + 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */, ); path = vChewingTests; sourceTree = ""; @@ -968,6 +982,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5BC4479D2865686500EDC323 /* data-chs.plist in Resources */, + 5BC4479E2865686500EDC323 /* data-cht.plist in Resources */, + 5BC4479F2865686500EDC323 /* data-cns.plist in Resources */, + 5BC447A02865686500EDC323 /* data-symbols.plist in Resources */, + 5BC447A12865686500EDC323 /* data-zhuyinwen.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1090,7 +1109,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5B2F2BB6286216A500B8557B /* vChewingTests.swift in Sources */, + 5BC447A628656A1900EDC323 /* PrefManagerTests.swift in Sources */, + 5BC6E03C286692BE00291771 /* KeyHandlerTestsSCPCCHT.swift in Sources */, + 5BC447A928656A1900EDC323 /* UpdateAPITests.swift in Sources */, + 5BC447AE2865CFC200EDC323 /* KeyHandlerTestsNormalCHS.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/vChewingTests/vChewingTests.swift b/vChewingTests/vChewingTests.swift deleted file mode 100644 index a0c5d878..00000000 --- a/vChewingTests/vChewingTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL 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. -*/ - -import XCTest - -class vChewingTests: XCTestCase { - -} From 0b848f6305ad561c6b0826269f850392005309af Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 12:16:30 +0800 Subject: [PATCH 11/16] Tests // Add unit tests for certain components. Tests // +PrefManagerTests. Tests // +KeyHandlerTestsNormalCHS. Tests // +UpdateAPITests. Tests // KeyHandlerTestsSCPCCHT (not finished). --- vChewingTests/KeyHandlerTestsNormalCHS.swift | 1617 ++++++++++++++++++ vChewingTests/KeyHandlerTestsSCPCCHT.swift | 383 +++++ vChewingTests/PrefManagerTests.swift | 276 +++ vChewingTests/UpdateAPITests.swift | 45 + 4 files changed, 2321 insertions(+) create mode 100644 vChewingTests/KeyHandlerTestsNormalCHS.swift create mode 100644 vChewingTests/KeyHandlerTestsSCPCCHT.swift create mode 100644 vChewingTests/PrefManagerTests.swift create mode 100644 vChewingTests/UpdateAPITests.swift diff --git a/vChewingTests/KeyHandlerTestsNormalCHS.swift b/vChewingTests/KeyHandlerTestsNormalCHS.swift new file mode 100644 index 00000000..8eeec80b --- /dev/null +++ b/vChewingTests/KeyHandlerTestsNormalCHS.swift @@ -0,0 +1,1617 @@ +// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL 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. +*/ + +import XCTest + +@testable import vChewing + +func charCode(_ string: String) -> UInt16 { + let scalars = string.unicodeScalars + return UInt16(scalars[scalars.startIndex].value) +} + +class KeyHandlerTestsNormalCHS: XCTestCase { + func reset() { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.removeObject(forKey: $0) + } + } + + func makeSnapshot() -> [String: Any] { + var dict = [String: Any]() + mgrPrefs.allKeys.forEach { + dict[$0] = UserDefaults.standard.object(forKey: $0) + } + return dict + } + + func restore(from snapshot: [String: Any]) { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.set(snapshot[$0], forKey: $0) + } + } + + var snapshot: [String: Any]? + + var handler = KeyHandler() + + override func setUpWithError() throws { + snapshot = makeSnapshot() + reset() + mgrPrefs.basicKeyboardLayout = "com.apple.keylayout.ABC" + mgrPrefs.mandarinParser = 0 + mgrPrefs.useSCPCTypingMode = false + mgrLangModel.loadDataModel(.imeModeCHS) + handler = KeyHandler() + handler.inputMode = .imeModeCHS + } + + override func tearDownWithError() throws { + if let snapshot = snapshot { + restore(from: snapshot) + } + } + + func testIgnoreEmpty() { + let input = InputSignal(inputText: "", keyCode: 0, charCode: 0, flags: [], isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreEnterCR() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kCarriageReturn.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreEnterLF() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kLineFeed.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreUp() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kUpArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result, "\(state)") + } + + func testIgnoreDown() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kDownArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result, "\(state)") + } + + func testIgnoreLeft() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreRight() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kRightArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnorePageUp() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kPageUp.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnorePageDown() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kPageDown.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreHome() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kHome.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreEnd() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kEnd.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreDelete() { + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kWindowsDelete.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreCommand() { + let input = InputSignal(inputText: "A", keyCode: 0, charCode: 0, flags: .command, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreOption() { + let input = InputSignal(inputText: "A", keyCode: 0, charCode: 0, flags: .option, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreNumericPad() { + let input = InputSignal(inputText: "A", keyCode: 0, charCode: 0, flags: .numericPad, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreCapslock() { + let input = InputSignal(inputText: "A", keyCode: 0, charCode: 0, flags: .capsLock, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testCapslock() { + var input = InputSignal(inputText: "b", keyCode: 0, charCode: charCode("b"), flags: [], isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + var count = 0 + + input = InputSignal(inputText: "a", keyCode: 0, charCode: charCode("a"), flags: .capsLock, isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + if count == 1 { + state = newState + } + count += 1 + } errorCallback: { + } + XCTAssertTrue(state is InputState.Committing, "\(state)") + } + + func testCapslockShift() { + var input = InputSignal(inputText: "b", keyCode: 0, charCode: charCode("b"), flags: [], isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + input = InputSignal( + inputText: "a", keyCode: 0, charCode: charCode("a"), flags: [.capsLock, .shift], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Empty, "\(state)") + } + + func testisNumericPad() { + var input = InputSignal(inputText: "b", keyCode: 0, charCode: charCode("b"), flags: [], isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + input = InputSignal( + inputText: "1", keyCode: 0, charCode: charCode("1"), flags: .numericPad, isVerticalTyping: false + ) + var count = 0 + var empty: InputStateProtocol = InputState.Empty() + var target: InputStateProtocol = InputState.Committing() + _ = handler.handle(input: input, state: state) { newState in + switch count { + case 0: + state = newState + case 1: + target = newState + case 2: + empty = newState + default: + break + } + count += 1 + + } errorCallback: { + } + XCTAssertEqual(count, 3) + XCTAssertTrue(state is InputState.Empty, "\(state)") + XCTAssertTrue(empty is InputState.Empty, "\(empty)") + XCTAssertTrue(target is InputState.Committing, "\(target)") + if let state = target as? InputState.Committing { + XCTAssertEqual(state.textToCommit, "1") + } + } + + func testPunctuationTable1() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kSymbolMenuPhysicalKey.rawValue, charCode: 0, flags: [], + isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testPunctuationTable2() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kSymbolMenuPhysicalKey.rawValue, charCode: 0, flags: .option, + isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains("!")) + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testIgnorePunctuationTable() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + var state: InputStateProtocol = InputState.Empty() + var input = InputSignal(inputText: "1", keyCode: 0, charCode: charCode("1"), flags: .shift, isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + input = InputSignal(inputText: "`", keyCode: 0, charCode: charCode("`"), flags: .shift, isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄅ") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testHalfPunctuationComma() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = true + let input = InputSignal(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, ",") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testPunctuationComma() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + let input = InputSignal(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, ",") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testHalfPunctuationPeriod() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = true + let input = InputSignal(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, ".") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testPunctuationPeriod() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + + let input = InputSignal(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalTyping: false) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "。") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testCtrlPunctuationPeriod() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + + let input = InputSignal( + inputText: ".", keyCode: 0, charCode: charCode("."), flags: .control, isVerticalTyping: false + ) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "。") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testInvalidBpmf() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("eul4").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testInputting() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("18 m,45j/ fu. g0 xup6xu;6").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "八月中秋山林凉") + } + } + + func testInputtingNihao() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + } + } + + func testInputtingTianKong() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("wu0 dj/ ").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "天空") + } + } + + func testCommittingNihao() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + } + + let enter = InputSignal( + inputText: " ", keyCode: KeyCode.kCarriageReturn.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var committing: InputStateProtocol = InputState.Committing() + var empty: InputStateProtocol = InputState.Empty() + var count = 0 + + _ = handler.handle(input: enter, state: state) { newState in + switch count { + case 0: + committing = newState + case 1: + empty = newState + default: + break + } + count += 1 + } errorCallback: { + } + + XCTAssertTrue(committing is InputState.Committing, "\(state)") + if let committing = committing as? InputState.Committing { + XCTAssertEqual(committing.textToCommit, "你好") + } + XCTAssertTrue(empty is InputState.Empty, "\(state)") + } + + func testDelete() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let left = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + let delete = InputSignal( + inputText: " ", keyCode: KeyCode.kWindowsDelete.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var errorCalled = false + + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 1) + } + + _ = handler.handle(input: delete, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + _ = handler.handle(input: delete, state: state) { newState in + state = newState + } errorCallback: { + errorCalled = true + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + XCTAssertTrue(errorCalled) + + errorCalled = false + + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 0) + } + + _ = handler.handle(input: delete, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testBackspaceToDeleteReading() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let backspace = InputSignal( + inputText: " ", keyCode: KeyCode.kBackSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋ") + XCTAssertEqual(state.cursorIndex, 1) + } + + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testBackspaceAtBegin() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let left = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + + let backspace = InputSignal( + inputText: " ", keyCode: KeyCode.kBackSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var errorCall = false + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + errorCall = true + } + XCTAssertTrue(errorCall) + } + + func testBackspaceToDeleteReadingWithText() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let backspace = InputSignal( + inputText: " ", keyCode: KeyCode.kBackSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏ") + XCTAssertEqual(state.cursorIndex, 2) + } + + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + } + + func testBackspace() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let backspace = InputSignal( + inputText: " ", keyCode: KeyCode.kBackSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + _ = handler.handle(input: backspace, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testCursorWithReading() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + let left = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + let right = InputSignal( + inputText: " ", keyCode: KeyCode.kRightArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var leftErrorCalled = false + var rightErrorCalled = false + + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + leftErrorCalled = true + } + + _ = handler.handle(input: right, state: state) { newState in + state = newState + } errorCallback: { + rightErrorCalled = true + } + + XCTAssertTrue(leftErrorCalled) + XCTAssertTrue(rightErrorCalled) + } + + func testCursor() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let left = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + let right = InputSignal( + inputText: " ", keyCode: KeyCode.kRightArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + + var errorCalled = false + + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 1) + } + + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + XCTAssertTrue(errorCalled) + + _ = handler.handle(input: right, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 1) + } + + _ = handler.handle(input: right, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + errorCalled = false + _ = handler.handle(input: right, state: state) { newState in + state = newState + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + XCTAssertTrue(errorCalled) + } + + func testCandidateWithDown() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + let down = InputSignal( + inputText: " ", keyCode: KeyCode.kDownArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: down, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + let candidates = state.candidates + XCTAssertTrue(candidates.contains("你")) + } + } + + func testCandidateWithSpace() { + let enabled = mgrPrefs.chooseCandidateUsingSpace + mgrPrefs.chooseCandidateUsingSpace = true + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + let space = InputSignal( + inputText: " ", keyCode: KeyCode.kSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: space, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + let candidates = state.candidates + XCTAssertTrue(candidates.contains("你")) + } + mgrPrefs.chooseCandidateUsingSpace = enabled + } + + func testInputSpace() { + let enabled = mgrPrefs.chooseCandidateUsingSpace + mgrPrefs.chooseCandidateUsingSpace = false + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: InputState.Empty()) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting) + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + + var count = 0 + var target: InputStateProtocol = InputState.Committing() + var empty: InputStateProtocol = InputState.Empty() + var state: InputStateProtocol = state + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + switch count { + case 0: + if let newState = newState as? InputState.Committing { + state = newState + } + case 1: + target = newState + case 2: + empty = newState + default: + break + } + count += 1 + } errorCallback: { + } + + XCTAssertEqual(count, 3) + XCTAssertTrue(state is InputState.Committing, "\(state)") + if let state = state as? InputState.Committing { + XCTAssertEqual(state.textToCommit, "你") + } + XCTAssertTrue(target is InputState.Committing) + if let target = target as? InputState.Committing { + XCTAssertEqual(target.textToCommit, " ") + } + XCTAssertTrue(empty is InputState.Empty) + } + mgrPrefs.chooseCandidateUsingSpace = enabled + } + + func testInputSpaceInBetween() { + let enabled = mgrPrefs.chooseCandidateUsingSpace + mgrPrefs.chooseCandidateUsingSpace = false + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + } + + var input = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + input = InputSignal( + inputText: " ", keyCode: KeyCode.kSpace.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你 好") + } + mgrPrefs.chooseCandidateUsingSpace = enabled + } + + func testHomeAndEnd() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let home = InputSignal( + inputText: " ", keyCode: KeyCode.kHome.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + let end = InputSignal( + inputText: " ", keyCode: KeyCode.kEnd.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + + _ = handler.handle(input: home, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + + var homeErrorCalled = false + _ = handler.handle(input: home, state: state) { newState in + state = newState + } errorCallback: { + homeErrorCalled = true + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + XCTAssertTrue(homeErrorCalled) + + _ = handler.handle(input: end, state: state) { newState in + state = newState + } errorCallback: { + } + + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + var endErrorCalled = false + _ = handler.handle(input: end, state: state) { newState in + state = newState + } errorCallback: { + endErrorCalled = true + } + + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + XCTAssertTrue(endErrorCalled) + } + + func testHomeAndEndWithReading() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + + let home = InputSignal( + inputText: " ", keyCode: KeyCode.kHome.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + let end = InputSignal( + inputText: " ", keyCode: KeyCode.kEnd.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + var homeErrorCalled = false + var endErrorCalled = false + + _ = handler.handle(input: home, state: state) { newState in + state = newState + } errorCallback: { + homeErrorCalled = true + } + + XCTAssertTrue(homeErrorCalled) + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + + _ = handler.handle(input: end, state: state) { newState in + state = newState + } errorCallback: { + endErrorCalled = true + } + + XCTAssertTrue(endErrorCalled) + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + } + + func testMarkingLeftAtBegin() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + var errorCalled = false + + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(errorCalled) + } + + func testMarkingRightAtEnd() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kRightArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + var errorCalled = false + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(errorCalled) + } + + func testMarkingLeft() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + var input = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + XCTAssertEqual(state.markerIndex, 1) + XCTAssertEqual(state.markedRange, 1..<2) + } + + input = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + XCTAssertEqual(state.markerIndex, 0) + XCTAssertEqual(state.markedRange, 0..<2) + } + + var stateForGoingRight: InputStateProtocol = state + + let right = InputSignal( + inputText: " ", keyCode: KeyCode.kRightArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + _ = handler.handle(input: right, state: stateForGoingRight) { newState in + stateForGoingRight = newState + } errorCallback: { + } + _ = handler.handle(input: right, state: stateForGoingRight) { newState in + stateForGoingRight = newState + } errorCallback: { + } + + XCTAssertTrue(stateForGoingRight is InputState.Inputting, "\(stateForGoingRight)") + } + + func testMarkingRight() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let left = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + _ = handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + + let errorInput = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + var errorCalled = false + _ = handler.handle(input: errorInput, state: state) { newState in + state = newState + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(errorCalled) + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kRightArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + XCTAssertEqual(state.markerIndex, 1) + XCTAssertEqual(state.markedRange, 0..<1) + } + + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + XCTAssertEqual(state.markerIndex, 2) + XCTAssertEqual(state.markedRange, 0..<2) + } + + var stateForGoingLeft: InputStateProtocol = state + + _ = handler.handle(input: left, state: stateForGoingLeft) { newState in + stateForGoingLeft = newState + } errorCallback: { + } + _ = handler.handle(input: left, state: stateForGoingLeft) { newState in + stateForGoingLeft = newState + } errorCallback: { + } + + XCTAssertTrue(stateForGoingLeft is InputState.Inputting, "\(stateForGoingLeft)") + } + + func testCancelMarking() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + var input = InputSignal( + inputText: " ", keyCode: KeyCode.kLeftArrow.rawValue, charCode: 0, flags: .shift, isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + XCTAssertEqual(state.markerIndex, 1) + XCTAssertEqual(state.markedRange, 1..<2) + } + + input = InputSignal(inputText: "1", keyCode: 0, charCode: charCode("1"), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好ㄅ") + } + } + + func testEscToClearReadingAndGoToEmpty() { + let enabled = mgrPrefs.escToCleanInputBuffer + mgrPrefs.escToCleanInputBuffer = false + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋㄧ") + XCTAssertEqual(state.cursorIndex, 2) + } + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kEscape.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + mgrPrefs.escToCleanInputBuffer = enabled + } + + func testEscToClearReadingAndGoToInputting() { + let enabled = mgrPrefs.escToCleanInputBuffer + mgrPrefs.escToCleanInputBuffer = false + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kEscape.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + mgrPrefs.escToCleanInputBuffer = enabled + } + + func testEscToClearAll() { + let enabled = mgrPrefs.escToCleanInputBuffer + mgrPrefs.escToCleanInputBuffer = true + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalTyping: false) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kEscape.rawValue, charCode: 0, flags: [], isVerticalTyping: false + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + mgrPrefs.escToCleanInputBuffer = enabled + } +} diff --git a/vChewingTests/KeyHandlerTestsSCPCCHT.swift b/vChewingTests/KeyHandlerTestsSCPCCHT.swift new file mode 100644 index 00000000..0c6ef6a1 --- /dev/null +++ b/vChewingTests/KeyHandlerTestsSCPCCHT.swift @@ -0,0 +1,383 @@ +// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL 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. +*/ + +import XCTest + +@testable import vChewing + +class KeyHandlerTestsSCPCCHT: XCTestCase { + func reset() { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.removeObject(forKey: $0) + } + } + + func makeSnapshot() -> [String: Any] { + var dict = [String: Any]() + mgrPrefs.allKeys.forEach { + dict[$0] = UserDefaults.standard.object(forKey: $0) + } + return dict + } + + func restore(from snapshot: [String: Any]) { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.set(snapshot[$0], forKey: $0) + } + } + + var snapshot: [String: Any]? + + var handler = KeyHandler() + + override func setUpWithError() throws { + snapshot = makeSnapshot() + reset() + mgrPrefs.basicKeyboardLayout = "com.apple.keylayout.ABC" + mgrPrefs.mandarinParser = 0 + mgrPrefs.useSCPCTypingMode = false + mgrPrefs.associatedPhrasesEnabled = false + mgrLangModel.loadDataModel(.imeModeCHT) + handler = KeyHandler() + handler.inputMode = .imeModeCHT + _ = mgrPrefs.toggleSCPCTypingModeEnabled() + _ = mgrPrefs.toggleAssociatedPhrasesEnabled() + } + + override func tearDownWithError() throws { + if let snapshot = snapshot { + restore(from: snapshot) + } + } + + func testPunctuationTable() { + let input = InputSignal( + inputText: "`", keyCode: KeyCode.kSymbolMenuPhysicalKey.rawValue, charCode: 0, flags: .option + ) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains(",")) + } + } + + func testPunctuationComma() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + let input = InputSignal(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, ",") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testPunctuationPeriod() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = false + let input = InputSignal(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "。") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testHalfPunctuationPeriod() { + let enabled = mgrPrefs.halfWidthPunctuationEnabled + mgrPrefs.halfWidthPunctuationEnabled = true + let input = InputSignal(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, ".") + } + mgrPrefs.halfWidthPunctuationEnabled = enabled + } + + func testControlPunctuationPeriod() { + let input = InputSignal( + inputText: ".", keyCode: 0, charCode: charCode("."), flags: [.shift, .control] + ) + var state: InputStateProtocol = InputState.Empty() + var count = 0 + _ = handler.handle(input: input, state: state) { newState in + if count == 0 { + state = newState + } + count += 1 + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "。") + } + } + + func testEnterWithReading() { + let input = InputSignal(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋ") + } + + let enter = InputSignal(inputText: " ", keyCode: 0, charCode: 13, flags: []) + var count = 0 + + _ = handler.handle(input: enter, state: state) { newState in + if count == 0 { + state = newState + } + count += 1 + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋ") + } + } + + func testInputNe() { + let input = InputSignal(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift) + var state: InputStateProtocol = InputState.Empty() + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋ") + } + } + + func testInputNi() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋㄧ") + } + } + + func testInputNi3() { + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains("你")) + } + } + + // TODO: Further bug-hunting needed. + func testCancelCandidateUsingDelete() { + mgrPrefs.useSCPCTypingMode = true + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let input = InputSignal( + inputText: " ", keyCode: KeyCode.kWindowsDelete.rawValue, charCode: charCode(" "), flags: [] + ) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + print("Expecting EmptyIgnoringPreviousState.") + print("\(state)") + // XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + // TODO: Further bug-hunting needed. + func testCancelCandidateUsingEsc() { + mgrPrefs.useSCPCTypingMode = true + var state: InputStateProtocol = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let input = InputSignal(inputText: " ", keyCode: KeyCode.kEscape.rawValue, charCode: charCode(" "), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + print("Expecting EmptyIgnoringPreviousState.") + print("\(state)") + // XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + // TODO: Further bug-hunting needed. + func testAssociatedPhrases() { + let enabled = mgrPrefs.associatedPhrasesEnabled + mgrPrefs.associatedPhrasesEnabled = true + mgrPrefs.useSCPCTypingMode = true + handler.forceOpenStringInsteadForAssociatePhrases("二 百五") + var state: InputStateProtocol = InputState.Empty() + let keys = Array("-41").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + print("Expecting AssociatedPhrases.") + print("\(state)") + // XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)") + if let state = state as? InputState.AssociatedPhrases { + // XCTAssertTrue(state.candidates.contains("百五")) + } + mgrPrefs.associatedPhrasesEnabled = enabled + } + + func testNoAssociatedPhrases() { + let enabled = mgrPrefs.associatedPhrasesEnabled + mgrPrefs.associatedPhrasesEnabled = false + var state: InputStateProtocol = InputState.Empty() + let keys = Array("aul ").map { + String($0) + } + for key in keys { + let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: []) + _ = handler.handle(input: input, state: state) { newState in + state = newState + + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.Empty, "\(state)") + mgrPrefs.associatedPhrasesEnabled = enabled + } +} + +// MARK: - StringView Ranges Extension (by Isaac Xen) + +extension String { + fileprivate func ranges(splitBy separator: Element) -> [Range] { + var startIndex = startIndex + return split(separator: separator).reduce(into: []) { ranges, substring in + _ = range(of: substring, range: startIndex..= 2 { + let theKey = String(neta[0]) + if !neta[0].isEmpty, !neta[1].isEmpty, theKey.first != "#" { + let theValue = $0 + rangeMap[theKey, default: []].append(theValue) + } + } + } + } +} + +extension vChewing.LMInstantiator { + public func forceOpenStringInsteadForAssociatePhrases(_ strData: String) { + lmAssociates.forceOpenStringInstead(strData) + } +} + +extension KeyHandler { + public func forceOpenStringInsteadForAssociatePhrases(_ strData: String) { + currentLM.forceOpenStringInsteadForAssociatePhrases(strData + "\n") + } +} diff --git a/vChewingTests/PrefManagerTests.swift b/vChewingTests/PrefManagerTests.swift new file mode 100644 index 00000000..7976b267 --- /dev/null +++ b/vChewingTests/PrefManagerTests.swift @@ -0,0 +1,276 @@ +// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL 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. +*/ + +import XCTest + +@testable import vChewing + +class PrefManagerTests: XCTestCase { + func reset() { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.removeObject(forKey: $0) + } + } + + func makeSnapshot() -> [String: Any] { + var dict = [String: Any]() + mgrPrefs.allKeys.forEach { + dict[$0] = UserDefaults.standard.object(forKey: $0) + } + return dict + } + + func restore(from snapshot: [String: Any]) { + mgrPrefs.allKeys.forEach { + UserDefaults.standard.set(snapshot[$0], forKey: $0) + } + } + + var snapshot: [String: Any]? + + override func setUpWithError() throws { + snapshot = makeSnapshot() + reset() + } + + override func tearDownWithError() throws { + if let snapshot = snapshot { + restore(from: snapshot) + } + } + + func testMandarinParser() { + XCTAssert(mgrPrefs.mandarinParser == 0) + mgrPrefs.mandarinParser = 1 + XCTAssert(mgrPrefs.mandarinParser == 1) + } + + func testMandarinParserName() { + XCTAssert(mgrPrefs.mandarinParserName == "Standard") + mgrPrefs.mandarinParser = 1 + XCTAssert(mgrPrefs.mandarinParserName == "ETen") + } + + func testBasisKeyboardLayoutPreferenceKey() { + XCTAssert(mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinBopomofo") + mgrPrefs.basicKeyboardLayout = "com.apple.keylayout.ABC" + XCTAssert(mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ABC") + } + + func testCandidateTextSize() { + XCTAssert(mgrPrefs.candidateListTextSize == 18) + + mgrPrefs.candidateListTextSize = 16 + XCTAssert(mgrPrefs.candidateListTextSize == 16) + + mgrPrefs.candidateListTextSize = 11 + XCTAssert(mgrPrefs.candidateListTextSize == 12) + mgrPrefs.candidateListTextSize = 197 + XCTAssert(mgrPrefs.candidateListTextSize == 196) + + mgrPrefs.candidateListTextSize = 12 + XCTAssert(mgrPrefs.candidateListTextSize == 12) + mgrPrefs.candidateListTextSize = 196 + XCTAssert(mgrPrefs.candidateListTextSize == 196) + + mgrPrefs.candidateListTextSize = 13 + XCTAssert(mgrPrefs.candidateListTextSize == 13) + mgrPrefs.candidateListTextSize = 195 + XCTAssert(mgrPrefs.candidateListTextSize == 195) + } + + func testUseRearCursorMode() { + XCTAssert(mgrPrefs.useRearCursorMode == false) + mgrPrefs.useRearCursorMode = true + XCTAssert(mgrPrefs.useRearCursorMode == true) + } + + func testUseHorizontalCandidateList() { + XCTAssert(mgrPrefs.useHorizontalCandidateList == true) + mgrPrefs.useHorizontalCandidateList = false + XCTAssert(mgrPrefs.useHorizontalCandidateList == false) + } + + func testComposingBufferSize() { + XCTAssert(mgrPrefs.composingBufferSize == 20) + mgrPrefs.composingBufferSize = 10 + XCTAssert(mgrPrefs.composingBufferSize == 10) + mgrPrefs.composingBufferSize = 4 + XCTAssert(mgrPrefs.composingBufferSize == 10) + mgrPrefs.composingBufferSize = 50 + XCTAssert(mgrPrefs.composingBufferSize == 40) + } + + func testChooseCandidateUsingSpace() { + XCTAssert(mgrPrefs.chooseCandidateUsingSpace == true) + mgrPrefs.chooseCandidateUsingSpace = false + XCTAssert(mgrPrefs.chooseCandidateUsingSpace == false) + } + + func testChineseConversionEnabled() { + XCTAssert(mgrPrefs.chineseConversionEnabled == false) + mgrPrefs.chineseConversionEnabled = true + XCTAssert(mgrPrefs.chineseConversionEnabled == true) + _ = mgrPrefs.toggleChineseConversionEnabled() + XCTAssert(mgrPrefs.chineseConversionEnabled == false) + } + + func testHalfWidthPunctuationEnabled() { + XCTAssert(mgrPrefs.halfWidthPunctuationEnabled == false) + mgrPrefs.halfWidthPunctuationEnabled = true + XCTAssert(mgrPrefs.halfWidthPunctuationEnabled == true) + _ = mgrPrefs.toggleHalfWidthPunctuationEnabled() + XCTAssert(mgrPrefs.halfWidthPunctuationEnabled == false) + } + + func testEscToCleanInputBuffer() { + XCTAssert(mgrPrefs.escToCleanInputBuffer == true) + mgrPrefs.escToCleanInputBuffer = false + XCTAssert(mgrPrefs.escToCleanInputBuffer == false) + } + + func testCandidateTextFontName() { + XCTAssert(mgrPrefs.candidateTextFontName == nil) + mgrPrefs.candidateTextFontName = "Helvetica" + XCTAssert(mgrPrefs.candidateTextFontName == "Helvetica") + } + + func testCandidateKeyLabelFontName() { + XCTAssert(mgrPrefs.candidateKeyLabelFontName == nil) + mgrPrefs.candidateKeyLabelFontName = "Helvetica" + XCTAssert(mgrPrefs.candidateKeyLabelFontName == "Helvetica") + } + + func testCandidateKeys() { + XCTAssert(mgrPrefs.candidateKeys == mgrPrefs.defaultCandidateKeys) + mgrPrefs.candidateKeys = "abcd" + XCTAssert(mgrPrefs.candidateKeys == "abcd") + } + + func testPhraseReplacementEnabledKey() { + XCTAssert(mgrPrefs.phraseReplacementEnabled == false) + mgrPrefs.phraseReplacementEnabled = true + XCTAssert(mgrPrefs.phraseReplacementEnabled == true) + } +} + +class CandidateKeyValidationTests: XCTestCase { + func testEmpty() { + do { + try mgrPrefs.validate(candidateKeys: "") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.empty { + } catch { + XCTFail("exception not thrown") + } + } + + func testSpaces() { + do { + try mgrPrefs.validate(candidateKeys: " ") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.empty { + } catch { + XCTFail("exception not thrown") + } + } + + func testInvalidKeys() { + do { + try mgrPrefs.validate(candidateKeys: "中文字元") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.invalidCharacters { + } catch { + XCTFail("exception not thrown") + } + } + + func testInvalidLatinLetters() { + do { + try mgrPrefs.validate(candidateKeys: "üåçøöacpo") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.invalidCharacters { + } catch { + XCTFail("exception not thrown") + } + } + + func testSpaceInBetween() { + do { + try mgrPrefs.validate(candidateKeys: "1 2 3 4") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.containSpace { + } catch { + XCTFail("exception not thrown") + } + } + + func testDuplicatedKeys() { + do { + try mgrPrefs.validate(candidateKeys: "aabbccdd") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.duplicatedCharacters { + } catch { + XCTFail("exception not thrown") + } + } + + func testTooShort1() { + do { + try mgrPrefs.validate(candidateKeys: "abc") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.tooShort { + } catch { + XCTFail("exception not thrown") + } + } + + func testTooShort2() { + do { + try mgrPrefs.validate(candidateKeys: "abcd") + } catch { + XCTFail("Should be safe") + } + } + + func testTooLong1() { + do { + try mgrPrefs.validate(candidateKeys: "qwertyuiopasdfgh") + XCTFail("exception not thrown") + } catch mgrPrefs.CandidateKeyError.tooLong { + } catch { + XCTFail("exception not thrown") + } + } + + func testTooLong2() { + do { + try mgrPrefs.validate(candidateKeys: "qwertyuiopasdfg") + } catch { + XCTFail("Should be safe") + } + } +} diff --git a/vChewingTests/UpdateAPITests.swift b/vChewingTests/UpdateAPITests.swift new file mode 100644 index 00000000..1720726b --- /dev/null +++ b/vChewingTests/UpdateAPITests.swift @@ -0,0 +1,45 @@ +// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL 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. +*/ + +import XCTest + +@testable import vChewing + +class VersionUpdateApiTests: XCTestCase { + func testFetchVersionUpdateInfo() { + let exp = expectation(description: "wait for 3 seconds") + _ = VersionUpdateApi.check(forced: true) { result in + exp.fulfill() + switch result { + case .success: + break + case .failure(let error): + XCTFail(error.localizedDescription) + } + } + wait(for: [exp], timeout: 20.0) + } +} From 9f304af3ae24bb614f975e4b7677e587a4ba206a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 17:46:14 +0800 Subject: [PATCH 12/16] Authors // Update descriptions of UOM. --- AUTHORS | 21 +++++++++++++++------ vChewing.xcodeproj/project.pbxproj | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index fd7435bb..b2646fc9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,7 +4,15 @@ $ Main contributors and volunteers of this repository (vChewing for macOS): - Hiraku Wang // Technical reinforcement in Cocoa during the Object-Cpp dev period of this project. - Isaac Xen // Technical reinforcement in Swift: SFX Module (NSSound ver.) and StringView Ranges Extension. -$ Contributors and volunteeres of the upstream repo, having no responsibility in discussing anything in the current repo: +$ 3rd-Party Modules Used: + +- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License). +- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license). +- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License). +- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License). +- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0) + +$ Contributors and volunteers of the upstream repo, having no responsibility in discussing anything in the current repo: - Zonble Yang: - McBopomofo for macOS 2.x architect, especially state-based IME behavior management. @@ -13,19 +21,20 @@ $ Contributors and volunteeres of the upstream repo, having no responsibility in - Notifier window and Tooltip UI. - NSStringUtils and FSEventStreamHelper. - App-style installer (only preserved for developer purposes). -- Mengjuei Hsieh + - etc. +- Mengjuei Hsieh: - McBopomofo for macOS 1.x main developer and architect. - - User Override Module (not enabled at this moment). - - Shiki Suen is trying to rewrite this module in Swift but it is not working yet. + - The original C++ version of the User Override Module. + - Shiki Suen is trying to rewrite this module in Swift (and CSharp) with further development. Although there is no Lukhnos's codes left in the current repository, we still credit him for his previous work: - Lukhnos Liu: - Developer of Gramambular language engine (removed since vChewing 1.5.4). - - Shiki Suen's Megrez engine is basically a Swift-rewritten version of Gramambular. + - Shiki Suen's Megrez engine is basically a Swift-rewritten version of Gramambular with further development. - Developer of Mandarin syllable composer (removed since vChewing 1.5.7). - Shiki Suen's Tekkon engine is made from scratch and has no relationship to Mandarin syllable composer. $ Special thanks to: -- All supporters from Cocoaheads Taipei and Mobile01 community. +- All supporters from Cocoaheads Taipei and Mobile01 community, etc. diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 89ee1958..66f78204 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -270,6 +270,7 @@ 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsSCPCCHT.swift; sourceTree = ""; }; 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsNormalCHS.swift; sourceTree = ""; }; 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAPITests.swift; sourceTree = ""; }; + 5BC447AB2865BEF500EDC323 /* AUTHORS */ = {isa = PBXFileReference; lastKnownFileType = text; path = AUTHORS; sourceTree = ""; }; 5BD0113A28180D6100609769 /* LMInstantiator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMInstantiator.swift; sourceTree = ""; usesTabs = 0; }; 5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = ""; usesTabs = 0; }; 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingPhraseEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -387,6 +388,7 @@ isa = PBXGroup; children = ( 5BC2652127E04B7B00700291 /* uninstall.sh */, + 5BC447AB2865BEF500EDC323 /* AUTHORS */, 5BE8A8C4281EE65300197741 /* CONTRIBUTING.md */, 5B18BA6F27C7BD8B0056EB19 /* LICENSE-CHS.txt */, 5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */, From 272c6279d00ede52d3eecb5e3f12d965eb014c77 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 18:12:18 +0800 Subject: [PATCH 13/16] LICENSE // Credit all 3rd-party modules used in this project. --- LICENSE-CHS.txt | 8 ++++++++ LICENSE-CHT.txt | 8 ++++++++ LICENSE-JPN.txt | 8 ++++++++ LICENSE.txt | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/LICENSE-CHS.txt b/LICENSE-CHS.txt index b76078c6..960c3c12 100644 --- a/LICENSE-CHS.txt +++ b/LICENSE-CHS.txt @@ -13,3 +13,11 @@ vChewing macOS: MIT-NTL License 麻理(去商标)授权合约 乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。 因麻理软件程式之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程式皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程式之所有风险均由使用者自行担负。假如所使用之麻理程式发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程式之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程式之不适用性,均须由使用者自行负担。 + +$ 在本专案内用到了下述第三方模组: + +- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License). +- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license). +- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License). +- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License). +- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0) diff --git a/LICENSE-CHT.txt b/LICENSE-CHT.txt index a392729f..378c50cd 100644 --- a/LICENSE-CHT.txt +++ b/LICENSE-CHT.txt @@ -13,3 +13,11 @@ vChewing macOS: MIT-NTL License 麻理(去商標)授權合約 乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。 因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。 + +$ 在該專案內用到了下述第三方: + +- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License). +- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license). +- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License). +- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License). +- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0) diff --git a/LICENSE-JPN.txt b/LICENSE-JPN.txt index f1c5a835..f0f919db 100644 --- a/LICENSE-JPN.txt +++ b/LICENSE-JPN.txt @@ -13,3 +13,11 @@ macOS 版威注音の開発:Shiki Suen, Isaac Xen, Hiraku Wang, など。 ロ)上記の通知要件を満たすために必要な場合を除き、コントリビューターの商号、商標、サービスマーク、または製品名を使用するための商標ライセンスは付与されていません。 ソフトウェアは「現状のまま」で、明示であるか暗黙であるかを問わず、何らの保証もなく提供されます。ここでいう保証とは、商品性、特定の目的への適合性、および権利非侵害についての保証も含みますが、それに限定されるものではありません。 作者または著作権者は、契約行為、不法行為、またはそれ以外であろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用またはその他の扱いによって生じる一切の請求、損害、その他の義務について何らの責任も負わないものとします。 + +$ 他の用いたモジュール: + +- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License). +- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license). +- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License). +- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License). +- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0) diff --git a/LICENSE.txt b/LICENSE.txt index b663cec9..e84ad14d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -13,3 +13,11 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of 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. + +$ 3rd-Party Modules Used: + +- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License). +- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license). +- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License). +- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License). +- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0) From 3c69e777a1cbd21c438b740bc0bb693a79e5dd72 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 24 Jun 2022 18:17:31 +0800 Subject: [PATCH 14/16] Repo // Remove Data scheme. --- .../xcshareddata/xcschemes/Data.xcscheme | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme diff --git a/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme b/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme deleted file mode 100644 index e3eda545..00000000 --- a/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - From 02e7bc26c0eac48e90e34da34fc1a05c4d39fd60 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 25 Jun 2022 10:05:07 +0800 Subject: [PATCH 15/16] IME // +NSApplication.shell(). --- Source/Modules/IMEModules/IME.swift | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 390250ae..270b5398 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -443,3 +443,29 @@ extension Sequence { .map(\.element) } } + +extension NSApplication { + public static func shell(_ command: String) throws -> String { + let task = Process() + let pipe = Pipe() + + task.standardOutput = pipe + task.standardError = pipe + task.arguments = ["-c", command] + if #available(macOS 10.13, *) { + task.executableURL = URL(fileURLWithPath: "/bin/zsh") + } else { + task.launchPath = "/bin/zsh" + } + task.standardInput = nil + + if #available(macOS 10.13, *) { + try task.run() + } + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)! + + return output + } +} From 7c9fff717af8a00e6ec5bf561e0d84508c7675bc Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 25 Jun 2022 15:13:53 +0800 Subject: [PATCH 16/16] Update Data - 20220625 --- Source/Data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Data b/Source/Data index 97d518ca..c9fba699 160000 --- a/Source/Data +++ b/Source/Data @@ -1 +1 @@ -Subproject commit 97d518cac19c96c5cd397bbdbcf8f95d2e967e73 +Subproject commit c9fba699065cd3a427e6156e12ba3c2ec223b38c