1.7.2 SP1 // Bug fixes. Merge Gitee PR!52 from upd/1.7.2sp1
This commit is contained in:
commit
9acbdaebe4
21
AUTHORS
21
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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 97d518cac19c96c5cd397bbdbcf8f95d2e967e73
|
||||
Subproject commit c9fba699065cd3a427e6156e12ba3c2ec223b38c
|
|
@ -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 ?? "")
|
||||
|
|
|
@ -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 {
|
||||
"<InputState.Deactivated>"
|
||||
}
|
||||
|
@ -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 {
|
||||
"<InputState.EmptyIgnoringPreviousState>"
|
||||
}
|
||||
|
@ -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<Int> = mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength
|
||||
private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } }
|
||||
private(set) var markedRange: Range<Int>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/// 向半衰引擎詢問可能的選字建議、且套用給組字器內的當前游標位置。
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -184,7 +190,7 @@ extension KeyHandler {
|
|||
errorCallback()
|
||||
composer.clear()
|
||||
// 根據「組字器是否為空」來判定回呼哪一種狀態。
|
||||
stateCallback((compositorLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
|
||||
stateCallback((compositor.isEmpty) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
|
||||
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
|
|
@ -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[..<self.index(startIndex, offsetBy: fixedIndex)].utf16.count
|
||||
}
|
||||
|
||||
func utf16SubString(with r: Range<Int>) -> String {
|
||||
let arr = Array(self.utf16)[r].map { $0 }
|
||||
internal func utf16SubString(with r: Range<Int>) -> String {
|
||||
let arr = Array(utf16)[r].map { $0 }
|
||||
return String(utf16CodeUnits: arr, count: arr.count)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -51,6 +51,7 @@ public class ctlCandidate: NSWindowController {
|
|||
case horizontal
|
||||
case vertical
|
||||
}
|
||||
|
||||
public var currentLayout: Layout = .horizontal
|
||||
public weak var delegate: ctlCandidateDelegate? {
|
||||
didSet {
|
||||
|
|
|
@ -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 = "<group>"; };
|
||||
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = "<group>"; };
|
||||
5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5B3A87BB28597CDB0090E163 /* LMSymbolNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMSymbolNode.swift; sourceTree = "<group>"; };
|
||||
|
@ -259,6 +266,11 @@
|
|||
5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPreInstall.sh; sourceTree = "<group>"; };
|
||||
5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = "<group>"; };
|
||||
5BC2652127E04B7B00700291 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = uninstall.sh; sourceTree = "<group>"; };
|
||||
5BC447A228656A1900EDC323 /* PrefManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefManagerTests.swift; sourceTree = "<group>"; };
|
||||
5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsSCPCCHT.swift; sourceTree = "<group>"; };
|
||||
5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsNormalCHS.swift; sourceTree = "<group>"; };
|
||||
5BC447A528656A1900EDC323 /* UpdateAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAPITests.swift; sourceTree = "<group>"; };
|
||||
5BC447AB2865BEF500EDC323 /* AUTHORS */ = {isa = PBXFileReference; lastKnownFileType = text; path = AUTHORS; sourceTree = "<group>"; };
|
||||
5BD0113A28180D6100609769 /* LMInstantiator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMInstantiator.swift; sourceTree = "<group>"; usesTabs = 0; };
|
||||
5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = "<group>"; usesTabs = 0; };
|
||||
5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingPhraseEditor.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -376,6 +388,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
5BC2652127E04B7B00700291 /* uninstall.sh */,
|
||||
5BC447AB2865BEF500EDC323 /* AUTHORS */,
|
||||
5BE8A8C4281EE65300197741 /* CONTRIBUTING.md */,
|
||||
5B18BA6F27C7BD8B0056EB19 /* LICENSE-CHS.txt */,
|
||||
5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */,
|
||||
|
@ -390,7 +403,10 @@
|
|||
5B2F2BB4286216A500B8557B /* vChewingTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5B2F2BB5286216A500B8557B /* vChewingTests.swift */,
|
||||
5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */,
|
||||
5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */,
|
||||
5BC447A228656A1900EDC323 /* PrefManagerTests.swift */,
|
||||
5BC447A528656A1900EDC323 /* UpdateAPITests.swift */,
|
||||
);
|
||||
path = vChewingTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -968,6 +984,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 +1111,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;
|
||||
};
|
||||
|
@ -1651,6 +1675,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 +1745,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;
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6A38BC2115FC12FD00A8A51F"
|
||||
BuildableName = "Data"
|
||||
BlueprintName = "Data"
|
||||
ReferencedContainer = "container:vChewing.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6A38BC2115FC12FD00A8A51F"
|
||||
BuildableName = "Data"
|
||||
BlueprintName = "Data"
|
||||
ReferencedContainer = "container:vChewing.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
File diff suppressed because it is too large
Load Diff
|
@ -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<String.Index>] {
|
||||
var startIndex = startIndex
|
||||
return split(separator: separator).reduce(into: []) { ranges, substring in
|
||||
_ = range(of: substring, range: startIndex..<endIndex).map { range in
|
||||
ranges.append(range)
|
||||
startIndex = range.upperBound
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension vChewing.LMAssociates {
|
||||
public mutating func forceOpenStringInstead(_ strData: String) {
|
||||
strData.ranges(splitBy: "\n").forEach {
|
||||
let neta = strData[$0].split(separator: " ")
|
||||
if neta.count >= 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")
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// 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
|
||||
|
@ -24,6 +26,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
||||
import XCTest
|
||||
|
||||
class vChewingTests: XCTestCase {
|
||||
@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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue