InputHandler // Implement stroke composition support.
This commit is contained in:
parent
23e02b9132
commit
c0ef70fe0d
|
@ -29,7 +29,7 @@ extension vChewingLM {
|
||||||
/// 是否已有資料載入。
|
/// 是否已有資料載入。
|
||||||
public var isLoaded: Bool { !charDefMap.isEmpty }
|
public var isLoaded: Bool { !charDefMap.isEmpty }
|
||||||
/// 返回「允許使用的敲字鍵」的
|
/// 返回「允許使用的敲字鍵」的
|
||||||
public var allowedKeys: [String] { Array(keyNameMap.keys) }
|
public var allowedKeys: [String] { Array(keyNameMap.keys + [" "]).deduplicated }
|
||||||
/// 將給定的按鍵字母轉換成要顯示的形態。
|
/// 將給定的按鍵字母轉換成要顯示的形態。
|
||||||
public func convertKeyToDisplay(char: String) -> String {
|
public func convertKeyToDisplay(char: String) -> String {
|
||||||
keyNameMap[char] ?? char
|
keyNameMap[char] ?? char
|
||||||
|
|
|
@ -57,6 +57,7 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
/// 半衰模組的衰減指數
|
/// 半衰模組的衰減指數
|
||||||
let kEpsilon: Double = 0.000_001
|
let kEpsilon: Double = 0.000_001
|
||||||
|
|
||||||
|
public var calligrapher = "" // 磁帶專用組筆區
|
||||||
public var composer: Tekkon.Composer = .init() // 注拼槽
|
public var composer: Tekkon.Composer = .init() // 注拼槽
|
||||||
public var compositor: Megrez.Compositor // 組字器
|
public var compositor: Megrez.Compositor // 組字器
|
||||||
public var currentUOM: vChewingLM.LMUserOverride
|
public var currentUOM: vChewingLM.LMUserOverride
|
||||||
|
@ -83,6 +84,7 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
public func clear() {
|
public func clear() {
|
||||||
composer.clear()
|
composer.clear()
|
||||||
compositor.clear()
|
compositor.clear()
|
||||||
|
calligrapher.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Functions dealing with Megrez.
|
// MARK: - Functions dealing with Megrez.
|
||||||
|
@ -336,6 +338,8 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
|
|
||||||
// MARK: - Extracted methods and functions (Tekkon).
|
// MARK: - Extracted methods and functions (Tekkon).
|
||||||
|
|
||||||
|
var isComposerOrCalligrapherEmpty: Bool { prefs.cassetteEnabled ? calligrapher.isEmpty : composer.isEmpty }
|
||||||
|
|
||||||
/// 獲取與當前注音排列或拼音輸入種類有關的標點索引鍵,以英數下畫線「_」結尾。
|
/// 獲取與當前注音排列或拼音輸入種類有關的標點索引鍵,以英數下畫線「_」結尾。
|
||||||
var currentKeyboardParser: String { currentKeyboardParserType.name + "_" }
|
var currentKeyboardParser: String { currentKeyboardParserType.name + "_" }
|
||||||
var currentKeyboardParserType: KeyboardParser { .init(rawValue: prefs.keyboardParser) ?? .ofStandard }
|
var currentKeyboardParserType: KeyboardParser { .init(rawValue: prefs.keyboardParser) ?? .ofStandard }
|
||||||
|
@ -363,6 +367,22 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
composer.phonabetCombinationCorrectionEnabled = prefs.autoCorrectReadingCombination
|
composer.phonabetCombinationCorrectionEnabled = prefs.autoCorrectReadingCombination
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearComposerAndCalligrapher() {
|
||||||
|
_ = prefs.cassetteEnabled ? calligrapher.removeAll() : composer.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func letComposerAndCalligrapherDoBackSpace() {
|
||||||
|
_ = prefs.cassetteEnabled ? calligrapher = String(calligrapher.dropLast(1)) : composer.doBackSpace()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 返回前一個游標位置的可解析的漢字筆畫。
|
||||||
|
/// 返回的內容分別是:「完整讀音」「去掉聲調的讀音」「是否有聲調」。
|
||||||
|
var previousParsableCalligraph: String? {
|
||||||
|
if compositor.cursor == 0 { return nil }
|
||||||
|
let cursorPrevious = max(compositor.cursor - 1, 0)
|
||||||
|
return compositor.keys[cursorPrevious]
|
||||||
|
}
|
||||||
|
|
||||||
/// 返回前一個游標位置的可解析的漢字讀音。
|
/// 返回前一個游標位置的可解析的漢字讀音。
|
||||||
/// 返回的內容分別是:「完整讀音」「去掉聲調的讀音」「是否有聲調」。
|
/// 返回的內容分別是:「完整讀音」「去掉聲調的讀音」「是否有聲調」。
|
||||||
var previousParsableReading: (String, String, Bool)? {
|
var previousParsableReading: (String, String, Bool)? {
|
||||||
|
@ -390,6 +410,18 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
return theComposer.hasIntonation(withNothingElse: true)
|
return theComposer.hasIntonation(withNothingElse: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var readingForDisplay: String {
|
||||||
|
if !prefs.cassetteEnabled {
|
||||||
|
return composer.getInlineCompositionForDisplay(isHanyuPinyin: prefs.showHanyuPinyinInCompositionBuffer)
|
||||||
|
}
|
||||||
|
if !prefs.showTranslatedStrokesInCompositionBuffer { return calligrapher }
|
||||||
|
var result = calligrapher.charComponents
|
||||||
|
for idx in 0..<result.count {
|
||||||
|
result[idx] = currentLM.currentCassette.convertKeyToDisplay(char: result[idx])
|
||||||
|
}
|
||||||
|
return result.joined()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Extracted methods and functions (Megrez).
|
// MARK: - Extracted methods and functions (Megrez).
|
||||||
|
|
||||||
/// 生成標點符號索引鍵。
|
/// 生成標點符號索引鍵。
|
||||||
|
|
|
@ -168,8 +168,12 @@ extension InputHandler {
|
||||||
]
|
]
|
||||||
let punctuation: String = arrPunctuations.joined()
|
let punctuation: String = arrPunctuations.joined()
|
||||||
|
|
||||||
|
let isInputValid: Bool =
|
||||||
|
prefs.cassetteEnabled
|
||||||
|
? currentLM.currentCassette.allowedKeys.contains(input.text) : composer.inputValidityCheck(key: input.charCode)
|
||||||
|
|
||||||
var shouldAutoSelectCandidate: Bool =
|
var shouldAutoSelectCandidate: Bool =
|
||||||
composer.inputValidityCheck(key: input.charCode) || currentLM.hasUnigramsFor(key: customPunctuation)
|
isInputValid || currentLM.hasUnigramsFor(key: customPunctuation)
|
||||||
|| currentLM.hasUnigramsFor(key: punctuation)
|
|| currentLM.hasUnigramsFor(key: punctuation)
|
||||||
|
|
||||||
if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey {
|
if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey {
|
||||||
|
|
|
@ -17,9 +17,11 @@ extension InputHandler {
|
||||||
/// - input: 輸入訊號。
|
/// - input: 輸入訊號。
|
||||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
func handleComposition(input: InputSignalProtocol) -> Bool? {
|
func handleComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
handlePhonabetComposition(input: input)
|
prefs.cassetteEnabled ? handleCassetteComposition(input: input) : handlePhonabetComposition(input: input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
||||||
|
|
||||||
/// 用來處理 InputHandler.HandleInput() 當中的與注音输入有關的組字行為。
|
/// 用來處理 InputHandler.HandleInput() 當中的與注音输入有關的組字行為。
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - input: 輸入訊號。
|
/// - input: 輸入訊號。
|
||||||
|
@ -27,8 +29,6 @@ extension InputHandler {
|
||||||
private func handlePhonabetComposition(input: InputSignalProtocol) -> Bool? {
|
private func handlePhonabetComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
guard let delegate = delegate else { return nil }
|
guard let delegate = delegate else { return nil }
|
||||||
|
|
||||||
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
|
||||||
|
|
||||||
var keyConsumedByReading = false
|
var keyConsumedByReading = false
|
||||||
let skipPhoneticHandling =
|
let skipPhoneticHandling =
|
||||||
input.isReservedKey || input.isNumericPadKey || input.isNonLaptopFunctionKey
|
input.isReservedKey || input.isNumericPadKey || input.isNonLaptopFunctionKey
|
||||||
|
@ -76,7 +76,7 @@ extension InputHandler {
|
||||||
|
|
||||||
var composeReading = composer.hasIntonation() && composer.inputValidityCheck(key: input.charCode) // 這裡不需要做排他性判斷。
|
var composeReading = composer.hasIntonation() && composer.inputValidityCheck(key: input.charCode) // 這裡不需要做排他性判斷。
|
||||||
|
|
||||||
// 如果當前的按鍵是 Enter 或 Space 的話,這時就可以取出 _composer 內的注音來做檢查了。
|
// 如果當前的按鍵是 Enter 或 Space 的話,這時就可以取出 composer 內的注音來做檢查了。
|
||||||
// 來看看詞庫內到底有沒有對應的讀音索引。這裡用了類似「|=」的判斷處理方式。
|
// 來看看詞庫內到底有沒有對應的讀音索引。這裡用了類似「|=」的判斷處理方式。
|
||||||
composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter))
|
composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter))
|
||||||
if composeReading {
|
if composeReading {
|
||||||
|
@ -160,3 +160,106 @@ extension InputHandler {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 磁帶模式的組字支援。
|
||||||
|
|
||||||
|
extension InputHandler {
|
||||||
|
/// 用來處理 InputHandler.HandleInput() 當中的與磁帶模組有關的組字行為。
|
||||||
|
/// - Parameters:
|
||||||
|
/// - input: 輸入訊號。
|
||||||
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
|
private func handleCassetteComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
|
guard let delegate = delegate else { return nil }
|
||||||
|
|
||||||
|
var keyConsumedByStrokes = false
|
||||||
|
let skipStrokeHandling =
|
||||||
|
input.isReservedKey || input.isNumericPadKey || input.isNonLaptopFunctionKey
|
||||||
|
|| input.isControlHold || input.isOptionHold || input.isShiftHold || input.isCommandHold
|
||||||
|
|
||||||
|
var isStrokesFull: Bool { calligrapher.count >= currentLM.currentCassette.maxKeyLength }
|
||||||
|
|
||||||
|
if !skipStrokeHandling && currentLM.currentCassette.allowedKeys.contains(input.text) {
|
||||||
|
if isStrokesFull {
|
||||||
|
calligrapher = String(calligrapher.dropLast(1))
|
||||||
|
}
|
||||||
|
calligrapher.append(input.text)
|
||||||
|
keyConsumedByStrokes = true
|
||||||
|
|
||||||
|
if !isStrokesFull {
|
||||||
|
delegate.switchState(generateStateOfInputting())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var compoundStrokes = isStrokesFull // 這裡不需要做排他性判斷。
|
||||||
|
|
||||||
|
// 如果當前的按鍵是 Enter 或 Space 的話,這時就可以取出 calligrapher 內的筆畫來做檢查了。
|
||||||
|
// 來看看詞庫內到底有沒有對應的讀音索引。這裡用了類似「|=」的判斷處理方式。
|
||||||
|
compoundStrokes = compoundStrokes || (!calligrapher.isEmpty && (input.isSpace || input.isEnter))
|
||||||
|
if compoundStrokes {
|
||||||
|
// 向語言模型詢問是否有對應的記錄。
|
||||||
|
if !currentLM.hasUnigramsFor(key: calligrapher) {
|
||||||
|
delegate.callError("B49C0979_Cassette:語彙庫內無「\(calligrapher)」的匹配記錄。")
|
||||||
|
|
||||||
|
calligrapher.removeAll()
|
||||||
|
// 根據「組字器是否為空」來判定回呼哪一種狀態。
|
||||||
|
switch compositor.isEmpty {
|
||||||
|
case false: delegate.switchState(generateStateOfInputting())
|
||||||
|
case true: delegate.switchState(IMEState.ofAbortion())
|
||||||
|
}
|
||||||
|
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將該讀音插入至組字器內的軌格當中。
|
||||||
|
compositor.insertKey(calligrapher)
|
||||||
|
|
||||||
|
// 讓組字器反爬軌格。
|
||||||
|
walk()
|
||||||
|
|
||||||
|
// 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。
|
||||||
|
let textToCommit = commitOverflownComposition
|
||||||
|
|
||||||
|
// 看看半衰記憶模組是否會對目前的狀態給出自動選字建議。
|
||||||
|
retrieveUOMSuggestions(apply: true)
|
||||||
|
|
||||||
|
// 之後就是更新組字區了。先清空注拼槽的內容。
|
||||||
|
calligrapher.removeAll()
|
||||||
|
|
||||||
|
// 再以回呼組字狀態的方式來執行 setInlineDisplayWithCursor()。
|
||||||
|
var inputting = generateStateOfInputting()
|
||||||
|
inputting.textToCommit = textToCommit
|
||||||
|
delegate.switchState(inputting)
|
||||||
|
|
||||||
|
/// 逐字選字模式的處理,與注音輸入的部分完全雷同。
|
||||||
|
if prefs.useSCPCTypingMode {
|
||||||
|
let candidateState: IMEStateProtocol = generateStateOfCandidates()
|
||||||
|
switch candidateState.candidates.count {
|
||||||
|
case 2...: delegate.switchState(candidateState)
|
||||||
|
case 1:
|
||||||
|
let firstCandidate = candidateState.candidates.first! // 一定會有,所以強制拆包也無妨。
|
||||||
|
let reading: String = firstCandidate.0
|
||||||
|
let text: String = firstCandidate.1
|
||||||
|
delegate.switchState(IMEState.ofCommitting(textToCommit: text))
|
||||||
|
|
||||||
|
if !prefs.associatedPhrasesEnabled {
|
||||||
|
delegate.switchState(IMEState.ofEmpty())
|
||||||
|
} else {
|
||||||
|
let associatedPhrases = generateStateOfAssociates(withPair: .init(key: reading, value: text))
|
||||||
|
delegate.switchState(associatedPhrases.candidates.isEmpty ? IMEState.ofEmpty() : associatedPhrases)
|
||||||
|
}
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 SessionCtl 回報給 IMK。
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 是說此時注拼槽並非為空、卻還沒組音。這種情況下只可能是「注拼槽內只有聲調」。
|
||||||
|
if keyConsumedByStrokes {
|
||||||
|
// 以回呼組字狀態的方式來執行 setInlineDisplayWithCursor()。
|
||||||
|
delegate.switchState(generateStateOfInputting())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ extension InputHandler {
|
||||||
|
|
||||||
// MARK: 用上下左右鍵呼叫選字窗 (Calling candidate window using Up / Down or PageUp / PageDn.)
|
// MARK: 用上下左右鍵呼叫選字窗 (Calling candidate window using Up / Down or PageUp / PageDn.)
|
||||||
|
|
||||||
if state.hasComposition, composer.isEmpty, !input.isOptionHold,
|
if state.hasComposition, isComposerOrCalligrapherEmpty, !input.isOptionHold,
|
||||||
input.isCursorClockLeft || input.isCursorClockRight || input.isSpace
|
input.isCursorClockLeft || input.isCursorClockRight || input.isSpace
|
||||||
|| input.isPageDown || input.isPageUp || (input.isTab && prefs.specifyShiftTabKeyBehavior)
|
|| input.isPageDown || input.isPageUp || (input.isTab && prefs.specifyShiftTabKeyBehavior)
|
||||||
{
|
{
|
||||||
|
@ -181,7 +181,7 @@ extension InputHandler {
|
||||||
if input.isSymbolMenuPhysicalKey, !input.isShiftHold, !input.isControlHold, state.type != .ofDeactivated {
|
if input.isSymbolMenuPhysicalKey, !input.isShiftHold, !input.isControlHold, state.type != .ofDeactivated {
|
||||||
if input.isOptionHold {
|
if input.isOptionHold {
|
||||||
if currentLM.hasUnigramsFor(key: "_punctuation_list") {
|
if currentLM.hasUnigramsFor(key: "_punctuation_list") {
|
||||||
if composer.isEmpty {
|
if isComposerOrCalligrapherEmpty {
|
||||||
compositor.insertKey("_punctuation_list")
|
compositor.insertKey("_punctuation_list")
|
||||||
walk()
|
walk()
|
||||||
// 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。
|
// 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。
|
||||||
|
@ -268,7 +268,7 @@ extension InputHandler {
|
||||||
/// 否則的話,可能會導致輸入法行為異常:部分應用會阻止輸入法完全攔截某些按鍵訊號。
|
/// 否則的話,可能會導致輸入法行為異常:部分應用會阻止輸入法完全攔截某些按鍵訊號。
|
||||||
/// 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。
|
/// 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。
|
||||||
/// 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。
|
/// 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。
|
||||||
if state.hasComposition || !composer.isEmpty {
|
if state.hasComposition || !isComposerOrCalligrapherEmpty {
|
||||||
delegate.callError("Blocked data: charCode: \(input.charCode), keyCode: \(input.keyCode)")
|
delegate.callError("Blocked data: charCode: \(input.charCode), keyCode: \(input.keyCode)")
|
||||||
delegate.callError("A9BFF20E")
|
delegate.callError("A9BFF20E")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
|
|
|
@ -23,7 +23,7 @@ extension InputHandler {
|
||||||
/// 換成由此處重新生成的原始資料在 IMEStateData 當中生成的 NSAttributeString。
|
/// 換成由此處重新生成的原始資料在 IMEStateData 當中生成的 NSAttributeString。
|
||||||
var displayTextSegments: [String] = compositor.walkedNodes.values
|
var displayTextSegments: [String] = compositor.walkedNodes.values
|
||||||
var cursor = convertCursorForDisplay(compositor.cursor)
|
var cursor = convertCursorForDisplay(compositor.cursor)
|
||||||
let reading = composer.getInlineCompositionForDisplay(isHanyuPinyin: prefs.showHanyuPinyinInCompositionBuffer)
|
let reading: String = readingForDisplay // 先提出來,減輕運算負擔。
|
||||||
if !reading.isEmpty {
|
if !reading.isEmpty {
|
||||||
var newDisplayTextSegments = [String]()
|
var newDisplayTextSegments = [String]()
|
||||||
var temporaryNode = ""
|
var temporaryNode = ""
|
||||||
|
@ -67,11 +67,12 @@ extension InputHandler {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !theNode.isReadingMismatched {
|
if !theNode.isReadingMismatched {
|
||||||
for _ in 0..<strNodeValue.count {
|
strNodeValue.forEach { _ in
|
||||||
guard readingCursorIndex < rawCursor else { continue }
|
if readingCursorIndex < rawCursor {
|
||||||
composedStringCursorIndex += 1
|
composedStringCursorIndex += 1
|
||||||
readingCursorIndex += 1
|
readingCursorIndex += 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
guard readingCursorIndex < rawCursor else { continue }
|
guard readingCursorIndex < rawCursor else { continue }
|
||||||
|
@ -227,7 +228,7 @@ extension InputHandler {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
guard composer.isEmpty else {
|
guard isComposerOrCalligrapherEmpty else {
|
||||||
// 注音沒敲完的情況下,無視標點輸入。
|
// 注音沒敲完的情況下,無視標點輸入。
|
||||||
delegate.callError("A9B69908D")
|
delegate.callError("A9B69908D")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
|
@ -243,7 +244,7 @@ extension InputHandler {
|
||||||
delegate.switchState(inputting)
|
delegate.switchState(inputting)
|
||||||
|
|
||||||
// 從這一行之後開始,就是針對逐字選字模式的單獨處理。
|
// 從這一行之後開始,就是針對逐字選字模式的單獨處理。
|
||||||
guard prefs.useSCPCTypingMode, composer.isEmpty else { return true }
|
guard prefs.useSCPCTypingMode, isComposerOrCalligrapherEmpty else { return true }
|
||||||
|
|
||||||
let candidateState = generateStateOfCandidates()
|
let candidateState = generateStateOfCandidates()
|
||||||
switch candidateState.candidates.count {
|
switch candidateState.candidates.count {
|
||||||
|
@ -339,14 +340,22 @@ extension InputHandler {
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
// 引入 macOS 內建注音輸入法的行為,允許用 Shift+BackSpace 解構前一個漢字的讀音。
|
// 引入 macOS 內建注音輸入法的行為,允許用 Shift+BackSpace 解構前一個漢字的讀音。
|
||||||
switch prefs.specifyShiftBackSpaceKeyBehavior {
|
shiftBksp: switch prefs.specifyShiftBackSpaceKeyBehavior {
|
||||||
case 0:
|
case 0:
|
||||||
guard input.isShiftHold, composer.isEmpty else { break }
|
if prefs.cassetteEnabled {
|
||||||
guard let prevReading = previousParsableReading else { break }
|
guard input.isShiftHold, calligrapher.isEmpty else { break shiftBksp }
|
||||||
|
guard let prevReading = previousParsableCalligraph else { break shiftBksp }
|
||||||
|
compositor.dropKey(direction: .rear)
|
||||||
|
walk() // 這裡必須 Walk 一次、來更新目前被 walk 的內容。
|
||||||
|
calligrapher = prevReading
|
||||||
|
} else {
|
||||||
|
guard input.isShiftHold, isComposerOrCalligrapherEmpty else { break shiftBksp }
|
||||||
|
guard let prevReading = previousParsableReading else { break shiftBksp }
|
||||||
// prevReading 的內容分別是:「完整讀音」「去掉聲調的讀音」「是否有聲調」。
|
// prevReading 的內容分別是:「完整讀音」「去掉聲調的讀音」「是否有聲調」。
|
||||||
compositor.dropKey(direction: .rear)
|
compositor.dropKey(direction: .rear)
|
||||||
walk() // 這裡必須 Walk 一次、來更新目前被 walk 的內容。
|
walk() // 這裡必須 Walk 一次、來更新目前被 walk 的內容。
|
||||||
prevReading.1.charComponents.forEach { composer.receiveKey(fromPhonabet: $0) }
|
prevReading.1.charComponents.forEach { composer.receiveKey(fromPhonabet: $0) }
|
||||||
|
}
|
||||||
delegate.switchState(generateStateOfInputting())
|
delegate.switchState(generateStateOfInputting())
|
||||||
return true
|
return true
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -360,9 +369,11 @@ extension InputHandler {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if composer.hasIntonation(withNothingElse: true) {
|
let isConfirm: Bool = prefs.cassetteEnabled ? input.isSpace : composer.hasIntonation(withNothingElse: true)
|
||||||
composer.clear()
|
|
||||||
} else if composer.isEmpty {
|
if isConfirm {
|
||||||
|
clearComposerAndCalligrapher()
|
||||||
|
} else if isComposerOrCalligrapherEmpty {
|
||||||
if compositor.cursor > 0 {
|
if compositor.cursor > 0 {
|
||||||
compositor.dropKey(direction: .rear)
|
compositor.dropKey(direction: .rear)
|
||||||
walk()
|
walk()
|
||||||
|
@ -372,10 +383,10 @@ extension InputHandler {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
composer.doBackSpace()
|
letComposerAndCalligrapherDoBackSpace()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch composer.isEmpty && compositor.isEmpty {
|
switch isComposerOrCalligrapherEmpty && compositor.isEmpty {
|
||||||
case false: delegate.switchState(generateStateOfInputting())
|
case false: delegate.switchState(generateStateOfInputting())
|
||||||
case true: delegate.switchState(IMEState.ofAbortion())
|
case true: delegate.switchState(IMEState.ofAbortion())
|
||||||
}
|
}
|
||||||
|
@ -398,17 +409,17 @@ extension InputHandler {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if compositor.cursor == compositor.length, composer.isEmpty {
|
if compositor.cursor == compositor.length, isComposerOrCalligrapherEmpty {
|
||||||
delegate.callError("9B69938D")
|
delegate.callError("9B69938D")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if composer.isEmpty {
|
if isComposerOrCalligrapherEmpty {
|
||||||
compositor.dropKey(direction: .front)
|
compositor.dropKey(direction: .front)
|
||||||
walk()
|
walk()
|
||||||
} else {
|
} else {
|
||||||
composer.clear()
|
clearComposerAndCalligrapher()
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputting = generateStateOfInputting()
|
let inputting = generateStateOfInputting()
|
||||||
|
@ -428,7 +439,7 @@ extension InputHandler {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
if !composer.isEmpty { delegate.callError("9B6F908D") }
|
if !isComposerOrCalligrapherEmpty { delegate.callError("9B6F908D") }
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -442,7 +453,7 @@ extension InputHandler {
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
if !composer.isEmpty {
|
if !isComposerOrCalligrapherEmpty {
|
||||||
delegate.callError("ABC44080")
|
delegate.callError("ABC44080")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
return true
|
return true
|
||||||
|
@ -468,7 +479,7 @@ extension InputHandler {
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
if !composer.isEmpty {
|
if !isComposerOrCalligrapherEmpty {
|
||||||
delegate.callError("9B69908D")
|
delegate.callError("9B69908D")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
return true
|
return true
|
||||||
|
@ -499,9 +510,9 @@ extension InputHandler {
|
||||||
/// 此乃 macOS 內建注音輸入法預設之行為,但不太受 Windows 使用者群體之待見。
|
/// 此乃 macOS 內建注音輸入法預設之行為,但不太受 Windows 使用者群體之待見。
|
||||||
delegate.switchState(IMEState.ofAbortion())
|
delegate.switchState(IMEState.ofAbortion())
|
||||||
} else {
|
} else {
|
||||||
if composer.isEmpty { return true }
|
if isComposerOrCalligrapherEmpty { return true }
|
||||||
/// 如果注拼槽不是空的話,則清空之。
|
/// 如果注拼槽或組筆區不是空的話,則清空之。
|
||||||
composer.clear()
|
clearComposerAndCalligrapher()
|
||||||
switch compositor.isEmpty {
|
switch compositor.isEmpty {
|
||||||
case false: delegate.switchState(generateStateOfInputting())
|
case false: delegate.switchState(generateStateOfInputting())
|
||||||
case true: delegate.switchState(IMEState.ofAbortion())
|
case true: delegate.switchState(IMEState.ofAbortion())
|
||||||
|
@ -521,7 +532,7 @@ extension InputHandler {
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
if !composer.isEmpty {
|
if !isComposerOrCalligrapherEmpty {
|
||||||
delegate.callError("B3BA5257")
|
delegate.callError("B3BA5257")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
return true
|
return true
|
||||||
|
@ -584,7 +595,7 @@ extension InputHandler {
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
if !composer.isEmpty {
|
if !isComposerOrCalligrapherEmpty {
|
||||||
delegate.callError("6ED95318")
|
delegate.callError("6ED95318")
|
||||||
delegate.switchState(state)
|
delegate.switchState(state)
|
||||||
return true
|
return true
|
||||||
|
@ -643,7 +654,7 @@ extension InputHandler {
|
||||||
func rotateCandidate(reverseOrder: Bool) -> Bool {
|
func rotateCandidate(reverseOrder: Bool) -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
if composer.isEmpty, compositor.isEmpty || compositor.walkedNodes.isEmpty { return false }
|
if isComposerOrCalligrapherEmpty, compositor.isEmpty || compositor.walkedNodes.isEmpty { return false }
|
||||||
guard state.type == .ofInputting else {
|
guard state.type == .ofInputting else {
|
||||||
guard state.type == .ofEmpty else {
|
guard state.type == .ofEmpty else {
|
||||||
delegate.callError("6044F081")
|
delegate.callError("6044F081")
|
||||||
|
@ -653,7 +664,7 @@ extension InputHandler {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
guard composer.isEmpty else {
|
guard isComposerOrCalligrapherEmpty else {
|
||||||
delegate.callError("A2DAF7BC")
|
delegate.callError("A2DAF7BC")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue