KeyHandler // Add Tab key handling with states.
This commit is contained in:
parent
2967109116
commit
68b82164db
|
@ -25,6 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
// MARK: - § Handle Input with States.
|
// MARK: - § Handle Input with States.
|
||||||
|
|
||||||
|
@ -275,6 +276,14 @@ extension KeyHandler {
|
||||||
|
|
||||||
if input.isESC { return handleEsc(state: state, stateCallback: stateCallback, errorCallback: errorCallback) }
|
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
|
// MARK: Cursor backward
|
||||||
|
|
||||||
if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward {
|
if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward {
|
||||||
|
|
|
@ -94,7 +94,7 @@ extension KeyHandler {
|
||||||
|
|
||||||
func buildCandidate(
|
func buildCandidate(
|
||||||
state currentState: InputState.NotEmpty,
|
state currentState: InputState.NotEmpty,
|
||||||
useVerticalMode: Bool
|
useVerticalMode: Bool = false
|
||||||
) -> InputState.ChoosingCandidate {
|
) -> InputState.ChoosingCandidate {
|
||||||
InputState.ChoosingCandidate(
|
InputState.ChoosingCandidate(
|
||||||
composingBuffer: currentState.composingBuffer,
|
composingBuffer: currentState.composingBuffer,
|
||||||
|
@ -590,4 +590,102 @@ extension KeyHandler {
|
||||||
|
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -694,7 +694,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
let selectedValue = state.candidates[Int(index)]
|
let selectedValue = state.candidates[Int(index)]
|
||||||
keyHandler.fixNode(value: selectedValue)
|
keyHandler.fixNode(value: selectedValue, respectCursorPushing: true)
|
||||||
|
|
||||||
let inputting = keyHandler.buildInputtingState
|
let inputting = keyHandler.buildInputtingState
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue