From 68b82164db8f2c9c76b65b6b28b74c0a3b9d60c9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 30 May 2022 17:03:59 +0800 Subject: [PATCH] KeyHandler // Add Tab key handling with states. --- .../KeyHandler_HandleInput.swift | 9 ++ .../ControllerModules/KeyHandler_States.swift | 100 +++++++++++++++++- .../Modules/IMEModules/ctlInputMethod.swift | 2 +- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index d5662a6a..acb9633f 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -25,6 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa +import SwiftUI // MARK: - § Handle Input with States. @@ -275,6 +276,14 @@ extension KeyHandler { if input.isESC { return handleEsc(state: state, stateCallback: stateCallback, errorCallback: errorCallback) } + // MARK: Tab + + if input.isTab { + return handleTab( + state: state, isShiftHold: input.isShiftHold, stateCallback: stateCallback, errorCallback: errorCallback + ) + } + // MARK: Cursor backward if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward { diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index 0f96f902..e0f7de2a 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -94,7 +94,7 @@ extension KeyHandler { func buildCandidate( state currentState: InputState.NotEmpty, - useVerticalMode: Bool + useVerticalMode: Bool = false ) -> InputState.ChoosingCandidate { InputState.ChoosingCandidate( composingBuffer: currentState.composingBuffer, @@ -590,4 +590,102 @@ extension KeyHandler { return true } + + // MARK: - 處理 Tab 按鍵行為 + + func handleTab( + state: InputState, + isShiftHold: Bool, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + guard let state = state as? InputState.Inputting else { + guard state is InputState.Empty else { + IME.prtDebugIntel("6044F081") + errorCallback() + return true + } + // 不妨礙使用者平時輸入 Tab 的需求。 + return false + } + + guard _composer.isEmpty else { + IME.prtDebugIntel("A2DAF7BC") + errorCallback() + return true + } + + // 此處僅借用該函數生成結果內的某個物件,不用糾結「是否縱排輸入」。 + let candidates = buildCandidate(state: state).candidates + guard !candidates.isEmpty else { + IME.prtDebugIntel("3378A6DF") + errorCallback() + return true + } + + var length = 0 + var currentAnchor = Megrez.NodeAnchor() + for anchor in _walkedNodes { + length += anchor.spanningLength + if length >= actualCandidateCursorIndex { + currentAnchor = anchor + break + } + } + + guard let currentNode = currentAnchor.node else { + IME.prtDebugIntel("4F2DEC2F") + errorCallback() + return true + } + + let currentValue = currentNode.currentKeyValue.value + + var currentIndex = 0 + if currentNode.score < currentNode.kSelectedCandidateScore { + // Once the user never select a candidate for the node, + // we start from the first candidate, so the user has a + // chance to use the unigram with two or more characters + // when type the tab key for the first time. + // + // In other words, if a user type two BPMF readings, + // but the score of seeing them as two unigrams is higher + // than a phrase with two characters, the user can just + // use the longer phrase by tapping the tab key. + if candidates[0] == currentValue { + // If the first candidate is the value of the + // current node, we use next one. + if isShiftHold { + currentIndex = candidates.count - 1 + } else { + currentIndex = 1 + } + } + } else { + for candidate in candidates { + if candidate == currentValue { + if isShiftHold { + if currentIndex == 0 { + currentIndex = candidates.count - 1 + } else { + currentIndex -= 1 + } + } else { + currentIndex += 1 + } + break + } + currentIndex += 1 + } + } + + if currentIndex >= candidates.count { + currentIndex = 0 + } + + fixNode(value: candidates[currentIndex], respectCursorPushing: false) + + stateCallback(buildInputtingState) + return true + } } diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 54b6654a..d6ad6e05 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -694,7 +694,7 @@ extension ctlInputMethod: ctlCandidateDelegate { if let state = state as? InputState.ChoosingCandidate { let selectedValue = state.candidates[Int(index)] - keyHandler.fixNode(value: selectedValue) + keyHandler.fixNode(value: selectedValue, respectCursorPushing: true) let inputting = keyHandler.buildInputtingState