KeyHandler // Rewrite documentation in Chinese.

- Also "reinitCompositor() -> ensureCompositor()".
- Other term fixes may apply.
This commit is contained in:
ShikiSuen 2022-06-18 08:03:43 +08:00
parent a6aefb9cef
commit b9107b04e5
6 changed files with 389 additions and 218 deletions

View File

@ -96,15 +96,15 @@ class InputState {
/// .Committing: /// .Committing:
class Committing: InputState { class Committing: InputState {
private(set) var poppedText: String = "" private(set) var textToCommit: String = ""
convenience init(poppedText: String) { convenience init(textToCommit: String) {
self.init() self.init()
self.poppedText = poppedText self.textToCommit = textToCommit
} }
var description: String { var description: String {
"<InputState.Committing poppedText:\(poppedText)>" "<InputState.Committing textToCommit:\(textToCommit)>"
} }
} }
@ -164,7 +164,7 @@ class InputState {
/// .Inputting: 使Compositor /// .Inputting: 使Compositor
class Inputting: NotEmpty { class Inputting: NotEmpty {
var poppedText: String = "" var textToCommit: String = ""
var tooltip: String = "" var tooltip: String = ""
override init(composingBuffer: String, cursorIndex: Int) { override init(composingBuffer: String, cursorIndex: Int) {
@ -172,7 +172,7 @@ class InputState {
} }
override var description: String { override var description: String {
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, poppedText:\(poppedText)>" "<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, textToCommit:\(textToCommit)>"
} }
} }

View File

@ -24,10 +24,15 @@ 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. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
/// 調
/// Megrez Tekkon
/// composer compositor
import Cocoa import Cocoa
// MARK: - Delegate. // MARK: - (Delegate).
/// KeyHandler
protocol KeyHandlerDelegate { protocol KeyHandlerDelegate {
func ctlCandidate() -> ctlCandidate func ctlCandidate() -> ctlCandidate
func keyHandler( func keyHandler(
@ -38,45 +43,52 @@ protocol KeyHandlerDelegate {
-> Bool -> Bool
} }
// MARK: - Kernel. // MARK: - (Kernel).
/// KeyHandler 調
class KeyHandler { class KeyHandler {
///
let kEpsilon: Double = 0.000001 let kEpsilon: Double = 0.000001
let kMaxComposingBufferNeedsToWalkSize = Int(max(12, ceil(Double(mgrPrefs.composingBufferSize) / 2)))
var composer: Tekkon.Composer = .init()
var compositor: Megrez.Compositor
var currentLM: vChewing.LMInstantiator = .init()
var currentUOM: vChewing.LMUserOverride = .init()
var walkedAnchors: [Megrez.NodeAnchor] = []
///
let kMaxComposingBufferNeedsToWalkSize = Int(max(12, ceil(Double(mgrPrefs.composingBufferSize) / 2)))
var composer: Tekkon.Composer = .init() //
var compositor: Megrez.Compositor //
var currentLM: vChewing.LMInstantiator = .init() //
var currentUOM: vChewing.LMUserOverride = .init() //
var walkedAnchors: [Megrez.NodeAnchor] = [] //
/// (ctlInputMethod)便
var delegate: KeyHandlerDelegate? var delegate: KeyHandlerDelegate?
/// InputMode
/// IME UserPrefs
var inputMode: InputMode = IME.currentInputMode { var inputMode: InputMode = IME.currentInputMode {
willSet { willSet {
// ctlInputMethod: //
let isCHS: Bool = (newValue == InputMode.imeModeCHS)
/// ctlInputMethod IME
IME.currentInputMode = newValue IME.currentInputMode = newValue
mgrPrefs.mostRecentInputMode = IME.currentInputMode.rawValue mgrPrefs.mostRecentInputMode = IME.currentInputMode.rawValue
///
let isCHS: Bool = (newValue == InputMode.imeModeCHS)
// Reinitiate language models if necessary
currentLM = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT currentLM = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT
currentUOM = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT currentUOM = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT
///
// Synchronize the sub-languageModel state settings to the new LM.
syncBaseLMPrefs() syncBaseLMPrefs()
///
// Create new compositor and clear the composer. ///
// When it recreates, it adapts to the latest imeMode settings. ensureCompositor()
// This allows it to work with correct LMs.
reinitCompositor()
ensureParser() ensureParser()
} }
} }
///
public init() { public init() {
/// ensureCompositor()
compositor = Megrez.Compositor(lm: currentLM, separator: "-") compositor = Megrez.Compositor(lm: currentLM, separator: "-")
///
ensureParser() ensureParser()
// defer willSet /// inputMode
/// defer willSet
defer { inputMode = IME.currentInputMode } defer { inputMode = IME.currentInputMode }
} }
@ -88,18 +100,22 @@ class KeyHandler {
// MARK: - Functions dealing with Megrez. // MARK: - Functions dealing with Megrez.
/// Megrez 使便
///
/// 使 Node Crossing
var actualCandidateCursorIndex: Int { var actualCandidateCursorIndex: Int {
mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1) mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1)
} }
///
///
/// Viterbi
///
///
func walk() { func walk() {
// Retrieve the most likely grid, i.e. a Maximum Likelihood Estimation
// of the best possible Mandarin characters given the input syllables,
// using the Viterbi algorithm implemented in the Megrez library.
// The walk() traces the grid to the end.
walkedAnchors = compositor.walk() walkedAnchors = compositor.walk()
// if DEBUG mode is enabled, a GraphViz file is written to kGraphVizOutputfile. // GraphViz
if mgrPrefs.isDebugModeEnabled { if mgrPrefs.isDebugModeEnabled {
let result = compositor.grid.dumpDOT let result = compositor.grid.dumpDOT
do { do {
@ -113,29 +129,31 @@ class KeyHandler {
} }
} }
///
///
/// Viterbi 使 O(N^2)
/// 使
/// 使
///
var popOverflowComposingTextAndWalk: String { var popOverflowComposingTextAndWalk: String {
// In ideal situations we can allow users to type infinitely in a buffer. var textToCommit = ""
// However, Viberti algorithm has a complexity of O(N^2), the walk will
// become slower as the number of nodes increase. Therefore, we need to
// auto-commit overflown texts which usually lose their influence over
// the whole MLE anyway -- so that when the user type along, the already
// composed text in the rear side of the buffer will be committed out.
// (i.e. popped out.)
var poppedText = ""
if compositor.grid.width > mgrPrefs.composingBufferSize { if compositor.grid.width > mgrPrefs.composingBufferSize {
if !walkedAnchors.isEmpty { if !walkedAnchors.isEmpty {
let anchor: Megrez.NodeAnchor = walkedAnchors[0] let anchor: Megrez.NodeAnchor = walkedAnchors[0]
if let theNode = anchor.node { if let theNode = anchor.node {
poppedText = theNode.currentKeyValue.value textToCommit = theNode.currentKeyValue.value
} }
compositor.removeHeadReadings(count: anchor.spanningLength) compositor.removeHeadReadings(count: anchor.spanningLength)
} }
} }
walk() walk()
return poppedText return textToCommit
} }
///
/// - Parameter key:
/// - Returns:
/// nil
func buildAssociatePhraseArray(withKey key: String) -> [String] { func buildAssociatePhraseArray(withKey key: String) -> [String] {
var arrResult: [String] = [] var arrResult: [String] = []
if currentLM.hasAssociatedPhrasesForKey(key) { if currentLM.hasAssociatedPhrasesForKey(key) {
@ -144,6 +162,11 @@ class KeyHandler {
return arrResult return arrResult
} }
///
///
/// - Parameters:
/// - value:
/// - respectCursorPushing: true
func fixNode(value: String, respectCursorPushing: Bool = true) { func fixNode(value: String, respectCursorPushing: Bool = true) {
let cursorIndex = min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength) let cursorIndex = min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength)
compositor.grid.fixNodeSelectedCandidate(location: cursorIndex, value: value) compositor.grid.fixNodeSelectedCandidate(location: cursorIndex, value: value)
@ -153,9 +176,7 @@ class KeyHandler {
// ) // )
// // // //
// if !mgrPrefs.useSCPCTypingMode { // if !mgrPrefs.useSCPCTypingMode {
// // If the length of the readings and the characters do not match, // //
// // it often means it is a special symbol and it should not be stored
// // in the user override model.
// var addToUserOverrideModel = true // var addToUserOverrideModel = true
// if selectedNode.spanningLength != value.count { // if selectedNode.spanningLength != value.count {
// IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.") // IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
@ -163,7 +184,7 @@ class KeyHandler {
// } // }
// if addToUserOverrideModel { // if addToUserOverrideModel {
// if let theNode = selectedNode.node { // if let theNode = selectedNode.node {
// // SymbolLM Score -12 // // SymbolLM Score -12
// if theNode.scoreFor(candidate: value) <= -12 { // if theNode.scoreFor(candidate: value) <= -12 {
// IME.prtDebugIntel("UOM: Score <= -12, dismissing.") // IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
// addToUserOverrideModel = false // addToUserOverrideModel = false
@ -172,6 +193,8 @@ class KeyHandler {
// } // }
// if addToUserOverrideModel { // if addToUserOverrideModel {
// IME.prtDebugIntel("UOM: Start Observation.") // IME.prtDebugIntel("UOM: Start Observation.")
// // trigram
// // trigram
// currentUOM.observe( // currentUOM.observe(
// walkedNodes: walkedAnchors, cursorIndex: cursorIndex, candidate: value, // walkedNodes: walkedAnchors, cursorIndex: cursorIndex, candidate: value,
// timestamp: NSDate().timeIntervalSince1970 // timestamp: NSDate().timeIntervalSince1970
@ -180,6 +203,7 @@ class KeyHandler {
// } // }
walk() walk()
///
if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing { if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing {
var nextPosition = 0 var nextPosition = 0
for node in walkedAnchors { for node in walkedAnchors {
@ -192,23 +216,26 @@ class KeyHandler {
} }
} }
///
func markNodesFixedIfNecessary() { func markNodesFixedIfNecessary() {
let width = compositor.grid.width let width = compositor.grid.width
if width <= kMaxComposingBufferNeedsToWalkSize { if width <= kMaxComposingBufferNeedsToWalkSize {
return return
} }
var index: Int = 0 var index = 0
for anchor in walkedAnchors { for anchor in walkedAnchors {
guard let node = anchor.node else { break } guard let node = anchor.node else { break }
if index >= width - kMaxComposingBufferNeedsToWalkSize { break } if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
if node.score < node.kSelectedCandidateScore { if node.score < node.kSelectedCandidateScore {
compositor.grid.fixNodeSelectedCandidate( compositor.grid.fixNodeSelectedCandidate(
location: index + anchor.spanningLength, value: node.currentKeyValue.value) location: index + anchor.spanningLength, value: node.currentKeyValue.value
)
} }
index += anchor.spanningLength index += anchor.spanningLength
} }
} }
///
var candidatesArray: [String] { var candidatesArray: [String] {
var arrCandidates: [String] = [] var arrCandidates: [String] = []
var arrNodes: [Megrez.NodeAnchor] = [] var arrNodes: [Megrez.NodeAnchor] = []
@ -234,7 +261,9 @@ class KeyHandler {
return arrCandidates return arrCandidates
} }
///
func dealWithOverrideModelSuggestions() { func dealWithOverrideModelSuggestions() {
/// trigram
let overrideValue = let overrideValue =
mgrPrefs.useSCPCTypingMode mgrPrefs.useSCPCTypingMode
? "" ? ""
@ -243,6 +272,7 @@ class KeyHandler {
timestamp: NSDate().timeIntervalSince1970 timestamp: NSDate().timeIntervalSince1970
) )
///
if !overrideValue.isEmpty { if !overrideValue.isEmpty {
IME.prtDebugIntel( IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.") "UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
@ -256,6 +286,11 @@ class KeyHandler {
} }
} }
///
/// - Parameters:
/// - nodes:
/// - epsilon:
/// - Returns:
func findHighestScore(nodes: [Megrez.NodeAnchor], epsilon: Double) -> Double { func findHighestScore(nodes: [Megrez.NodeAnchor], epsilon: Double) -> Double {
var highestScore: Double = 0 var highestScore: Double = 0
for currentAnchor in nodes { for currentAnchor in nodes {
@ -271,10 +306,12 @@ class KeyHandler {
// MARK: - Extracted methods and functions (Tekkon). // MARK: - Extracted methods and functions (Tekkon).
/// _
var currentMandarinParser: String { var currentMandarinParser: String {
mgrPrefs.mandarinParserName + "_" mgrPrefs.mandarinParserName + "_"
} }
///
func ensureParser() { func ensureParser() {
switch mgrPrefs.mandarinParser { switch mgrPrefs.mandarinParser {
case MandarinParser.ofStandard.rawValue: case MandarinParser.ofStandard.rawValue:
@ -310,7 +347,11 @@ class KeyHandler {
composer.clear() composer.clear()
} }
/// Ruby /// Ruby
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
func cnvZhuyinKeyToTextbookReading(target: String, newSeparator: String = "-") -> String { func cnvZhuyinKeyToTextbookReading(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = [] var arrReturn: [String] = []
for neta in target.split(separator: "-") { for neta in target.split(separator: "-") {
@ -324,7 +365,11 @@ class KeyHandler {
return arrReturn.joined(separator: newSeparator) return arrReturn.joined(separator: newSeparator)
} }
// Ruby /// Ruby
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
func restoreToneOneInZhuyinKey(target: String, newSeparator: String = "-") -> String { func restoreToneOneInZhuyinKey(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = [] var arrReturn: [String] = []
for neta in target.split(separator: "-") { for neta in target.split(separator: "-") {
@ -339,8 +384,10 @@ class KeyHandler {
// MARK: - Extracted methods and functions (Megrez). // MARK: - Extracted methods and functions (Megrez).
var isCompositorEmpty: Bool { compositor.grid.width == 0 } ///
var isCompositorEmpty: Bool { compositor.isEmpty }
///
var rawNodes: [Megrez.NodeAnchor] { var rawNodes: [Megrez.NodeAnchor] {
/// 使 nodesCrossing macOS /// 使 nodesCrossing macOS
/// nodeCrossing /// nodeCrossing
@ -349,45 +396,54 @@ class KeyHandler {
: compositor.grid.nodesEndingAt(location: actualCandidateCursorIndex) : compositor.grid.nodesEndingAt(location: actualCandidateCursorIndex)
} }
///
func syncBaseLMPrefs() { func syncBaseLMPrefs() {
currentLM.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled currentLM.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled
currentLM.isCNSEnabled = mgrPrefs.cns11643Enabled currentLM.isCNSEnabled = mgrPrefs.cns11643Enabled
currentLM.isSymbolEnabled = mgrPrefs.symbolInputEnabled currentLM.isSymbolEnabled = mgrPrefs.symbolInputEnabled
} }
func reinitCompositor() { /// 使
// Each Mandarin syllable is separated by a hyphen. func ensureCompositor() {
// 西
compositor = Megrez.Compositor(lm: currentLM, separator: "-") compositor = Megrez.Compositor(lm: currentLM, separator: "-")
} }
///
var currentReadings: [String] { compositor.readings } var currentReadings: [String] { compositor.readings }
///
func ifLangModelHasUnigrams(forKey reading: String) -> Bool { func ifLangModelHasUnigrams(forKey reading: String) -> Bool {
currentLM.hasUnigramsFor(key: reading) currentLM.hasUnigramsFor(key: reading)
} }
///
func insertToCompositorAtCursor(reading: String) { func insertToCompositorAtCursor(reading: String) {
compositor.insertReadingAtCursor(reading: reading) compositor.insertReadingAtCursor(reading: reading)
} }
///
var compositorCursorIndex: Int { var compositorCursorIndex: Int {
get { compositor.cursorIndex } get { compositor.cursorIndex }
set { compositor.cursorIndex = newValue } set { compositor.cursorIndex = newValue }
} }
///
var compositorLength: Int { var compositorLength: Int {
compositor.length compositor.length
} }
func deleteBuilderReadingInFrontOfCursor() { ///
///
/// Rear
func deleteCompositorReadingAtTheRearOfCursor() {
compositor.deleteReadingAtTheRearOfCursor() compositor.deleteReadingAtTheRearOfCursor()
} }
func deleteBuilderReadingToTheFrontOfCursor() { ///
///
/// Front
func deleteCompositorReadingToTheFrontOfCursor() {
compositor.deleteReadingToTheFrontOfCursor() compositor.deleteReadingToTheFrontOfCursor()
} }
var keyLengthAtIndexZero: Int {
walkedAnchors[0].node?.currentKeyValue.value.count ?? 0
}
} }

View File

@ -24,9 +24,11 @@ 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. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
/// 調
import Cocoa import Cocoa
// MARK: - § Handle Candidate State. // MARK: - § 調 (Handle Candidate State).
extension KeyHandler { extension KeyHandler {
func handleCandidate( func handleCandidate(
@ -43,7 +45,7 @@ extension KeyHandler {
return true return true
} }
// MARK: Cancel Candidate // MARK: (Cancel Candidate)
let cancelCandidateKey = let cancelCandidateKey =
input.isBackSpace || input.isESC || input.isDelete input.isBackSpace || input.isESC || input.isDelete
@ -286,7 +288,7 @@ extension KeyHandler {
} }
} }
// MARK: - Associated Phrases // MARK: (Associated Phrases)
if state is InputState.AssociatedPhrases { if state is InputState.AssociatedPhrases {
if !input.isShiftHold { return false } if !input.isShiftHold { return false }
@ -322,9 +324,13 @@ extension KeyHandler {
if state is InputState.AssociatedPhrases { return false } if state is InputState.AssociatedPhrases { return false }
// MARK: SCPC Mode Processing // MARK: (SCPC Mode Processing)
if mgrPrefs.useSCPCTypingMode { if mgrPrefs.useSCPCTypingMode {
///
/// - /
/// -
var punctuationNamePrefix = "" var punctuationNamePrefix = ""
if input.isOptionHold && !input.isControlHold { if input.isOptionHold && !input.isControlHold {
@ -346,6 +352,8 @@ extension KeyHandler {
] ]
let customPunctuation: String = arrCustomPunctuations.joined(separator: "") let customPunctuation: String = arrCustomPunctuations.joined(separator: "")
///
let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))]
let punctuation: String = arrPunctuations.joined(separator: "") let punctuation: String = arrPunctuations.joined(separator: "")

View File

@ -24,9 +24,12 @@ 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. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
/// 調 IMK 調
/// 調
import Cocoa import Cocoa
// MARK: - § Handle Input with States. // MARK: - § 調 (Handle Input with States)
extension KeyHandler { extension KeyHandler {
func handle( func handle(
@ -36,10 +39,9 @@ extension KeyHandler {
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
let charCode: UniChar = input.charCode let charCode: UniChar = input.charCode
var state = state // Turn this incoming constant into variable. var state = state //
// Ignore the input if its inputText is empty. // inputTest
// Reason: such inputs may be functional key combinations.
guard let inputText: String = input.inputText, !inputText.isEmpty else { guard let inputText: String = input.inputText, !inputText.isEmpty else {
return false return false
} }
@ -52,8 +54,7 @@ extension KeyHandler {
return true return true
} }
// Ignore the input if the composing buffer is empty with no reading //
// and there is some function key combination.
let isFunctionKey: Bool = let isFunctionKey: Bool =
input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNumericPad) input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNumericPad)
if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey { if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey {
@ -62,37 +63,40 @@ extension KeyHandler {
// MARK: Caps Lock processing. // MARK: Caps Lock processing.
// If Caps Lock is ON, temporarily disable phonetic reading. /// Caps Lock
// Note: Alphanumerical mode processing. /// Shift Chromium
/// IMK Shift 使
/// Caps Lock
if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey
|| input.isExtraChooseCandidateKeyReverse || input.isCursorForward || input.isCursorBackward || input.isExtraChooseCandidateKeyReverse || input.isCursorForward || input.isCursorBackward
{ {
// Do nothing if backspace is pressed -- we ignore the key // BackSpace
} else if input.isCapsLockOn { } else if input.isCapsLockOn {
// Process all possible combination, we hope. //
clear() clear()
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
// When shift is pressed, don't do further processing... // Shift
// ...since it outputs capital letter anyway.
if input.isShiftHold { if input.isShiftHold {
return false return false
} }
// If ASCII but not printable, don't use insertText:replacementRange: /// ASCII 使insertText:replacementRange:
// Certain apps don't handle non-ASCII char insertions. /// ASCII
/// Objective-C isPrintable()
/// CTools.h Swift
if charCode < 0x80, !CTools.isPrintable(charCode) { if charCode < 0x80, !CTools.isPrintable(charCode) {
return false return false
} }
// Commit the entire input buffer. //
stateCallback(InputState.Committing(poppedText: inputText.lowercased())) stateCallback(InputState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
return true return true
} }
// MARK: Numeric Pad Processing. // MARK: (Numeric Pad Processing)
if input.isNumericPad { if input.isNumericPad {
if !input.isLeft, !input.isRight, !input.isDown, if !input.isLeft, !input.isRight, !input.isDown,
@ -100,13 +104,13 @@ extension KeyHandler {
{ {
clear() clear()
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
stateCallback(InputState.Committing(poppedText: inputText.lowercased())) stateCallback(InputState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
return true return true
} }
} }
// MARK: Handle Candidates. // MARK: (Handle Candidates)
if state is InputState.ChoosingCandidate { if state is InputState.ChoosingCandidate {
return handleCandidate( return handleCandidate(
@ -114,7 +118,7 @@ extension KeyHandler {
) )
} }
// MARK: Handle Associated Phrases. // MARK: (Handle Associated Phrases)
if state is InputState.AssociatedPhrases { if state is InputState.AssociatedPhrases {
if handleCandidate( if handleCandidate(
@ -126,7 +130,7 @@ extension KeyHandler {
} }
} }
// MARK: Handle Marking. // MARK: 便使() (Handle Marking)
if let marking = state as? InputState.Marking { if let marking = state as? InputState.Marking {
if handleMarkingState( if handleMarkingState(
@ -139,19 +143,20 @@ extension KeyHandler {
stateCallback(state) stateCallback(state)
} }
// MARK: Handle BPMF Keys. // MARK: (Handle BPMF Keys)
var keyConsumedByReading = false var keyConsumedByReading = false
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
// See if Phonetic reading is valid. // inputValidityCheck() charCode UniChar
// keyConsumedByReading
// composer.receiveKey() String UniChar
if !skipPhoneticHandling && composer.inputValidityCheck(key: charCode) { if !skipPhoneticHandling && composer.inputValidityCheck(key: charCode) {
composer.receiveKey(fromCharCode: charCode) composer.receiveKey(fromCharCode: charCode)
keyConsumedByReading = true keyConsumedByReading = true
// If we have a tone marker, we have to insert the reading to the // 調 updateClientComposingBuffer() return true
// compositor in other words, if we don't have a tone marker, we just // 調
// update the composing buffer.
let composeReading = composer.hasToneMarker() let composeReading = composer.hasToneMarker()
if !composeReading { if !composeReading {
stateCallback(buildInputtingState) stateCallback(buildInputtingState)
@ -161,44 +166,49 @@ extension KeyHandler {
var composeReading = composer.hasToneMarker() // var composeReading = composer.hasToneMarker() //
// See if we have composition if Enter/Space is hit and buffer is not empty. // Enter Space _composer
// We use "|=" conditioning so that the tone marker key is also taken into account. // |=
// However, Swift does not support "|=".
composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter)) composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter))
if composeReading { if composeReading {
if input.isSpace, !composer.hasToneMarker() { if input.isSpace, !composer.hasToneMarker() {
composer.receiveKey(fromString: " ") // // 調
// 使 OVMandarin調
composer.receiveKey(fromString: " ")
} }
let reading = composer.getComposition() let reading = composer.getComposition() //
//
// See whether we have a unigram for this... //
if !ifLangModelHasUnigrams(forKey: reading) { if !ifLangModelHasUnigrams(forKey: reading) {
IME.prtDebugIntel("B49C0979語彙庫內無「\(reading)」的匹配記錄。") IME.prtDebugIntel("B49C0979語彙庫內無「\(reading)」的匹配記錄。")
errorCallback() errorCallback()
composer.clear() composer.clear()
//
stateCallback((compositorLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState) stateCallback((compositorLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
return true return true // IMK
} }
// ... and insert it into the grid... //
insertToCompositorAtCursor(reading: reading) insertToCompositorAtCursor(reading: reading)
// ... then walk the grid... //
let poppedText = popOverflowComposingTextAndWalk let textToCommit = popOverflowComposingTextAndWalk
// ... get and tweak override model suggestion if possible... //
// dealWithOverrideModelSuggestions() // 使 // dealWithOverrideModelSuggestions() // 使
// ... fix nodes if necessary... //
markNodesFixedIfNecessary() markNodesFixedIfNecessary()
// ... then update the text. //
composer.clear() composer.clear()
// updateClientComposingBuffer()
let inputting = buildInputtingState let inputting = buildInputtingState
inputting.poppedText = poppedText inputting.textToCommit = textToCommit
stateCallback(inputting) stateCallback(inputting)
///
if mgrPrefs.useSCPCTypingMode { if mgrPrefs.useSCPCTypingMode {
let choosingCandidates: InputState.ChoosingCandidate = buildCandidate( let choosingCandidates: InputState.ChoosingCandidate = buildCandidate(
state: inputting, state: inputting,
@ -207,7 +217,7 @@ extension KeyHandler {
if choosingCandidates.candidates.count == 1 { if choosingCandidates.candidates.count == 1 {
clear() clear()
let text: String = choosingCandidates.candidates.first ?? "" let text: String = choosingCandidates.candidates.first ?? ""
stateCallback(InputState.Committing(poppedText: text)) stateCallback(InputState.Committing(textToCommit: text))
if !mgrPrefs.associatedPhrasesEnabled { if !mgrPrefs.associatedPhrasesEnabled {
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
@ -227,42 +237,44 @@ extension KeyHandler {
stateCallback(choosingCandidates) stateCallback(choosingCandidates)
} }
} }
return true // Telling the client that the key is consumed. // ctlInputMethod IMK
return true
} }
// The only possibility for this to be true is that the Phonetic reading /// true 調調
// already has a tone marker but the last key is *not* a tone marker key. An /// 6jˊˊ調
// example is the sequence "6u" with the Standard layout, which produces "ˊ" /// 調ˊˊˊˊˇˊ
// but does not compose. Only sequences such as "ˊ", "ˊˊ", "ˊˇ", or "ˊ "
// would compose.
if keyConsumedByReading { if keyConsumedByReading {
// updateClientComposingBuffer()
stateCallback(buildInputtingState) stateCallback(buildInputtingState)
return true return true
} }
// MARK: Calling candidate window using Up / Down or PageUp / PageDn. // MARK: Calling candidate window using Up / Down or PageUp / PageDn.
//
if let currentState = state as? InputState.NotEmpty, composer.isEmpty, if let currentState = state as? InputState.NotEmpty, composer.isEmpty,
input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace
|| input.isPageDown || input.isPageUp || (input.isTab && mgrPrefs.specifyShiftTabKeyBehavior) || input.isPageDown || input.isPageUp || (input.isTab && mgrPrefs.specifyShiftTabKeyBehavior)
|| (input.isTypingVertical && (input.isverticalTypingOnlyChooseCandidateKey)) || (input.isTypingVertical && (input.isverticalTypingOnlyChooseCandidateKey))
{ {
if input.isSpace { if input.isSpace {
// If the Space key is NOT set to be a selection key /// Space
if !mgrPrefs.chooseCandidateUsingSpace { if !mgrPrefs.chooseCandidateUsingSpace {
if compositorCursorIndex >= compositorLength { if compositorCursorIndex >= compositorLength {
let composingBuffer = currentState.composingBuffer let composingBuffer = currentState.composingBuffer
if !composingBuffer.isEmpty { if !composingBuffer.isEmpty {
stateCallback(InputState.Committing(poppedText: composingBuffer)) stateCallback(InputState.Committing(textToCommit: composingBuffer))
} }
clear() clear()
stateCallback(InputState.Committing(poppedText: " ")) stateCallback(InputState.Committing(textToCommit: " "))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
} else if ifLangModelHasUnigrams(forKey: " ") { } else if ifLangModelHasUnigrams(forKey: " ") {
insertToCompositorAtCursor(reading: " ") insertToCompositorAtCursor(reading: " ")
let poppedText = popOverflowComposingTextAndWalk let textToCommit = popOverflowComposingTextAndWalk
let inputting = buildInputtingState let inputting = buildInputtingState
inputting.poppedText = poppedText inputting.textToCommit = textToCommit
stateCallback(inputting) stateCallback(inputting)
} }
return true return true
@ -371,9 +383,9 @@ extension KeyHandler {
if ifLangModelHasUnigrams(forKey: "_punctuation_list") { if ifLangModelHasUnigrams(forKey: "_punctuation_list") {
if composer.isEmpty { if composer.isEmpty {
insertToCompositorAtCursor(reading: "_punctuation_list") insertToCompositorAtCursor(reading: "_punctuation_list")
let poppedText: String! = popOverflowComposingTextAndWalk let textToCommit: String! = popOverflowComposingTextAndWalk
let inputting = buildInputtingState let inputting = buildInputtingState
inputting.poppedText = poppedText inputting.textToCommit = textToCommit
stateCallback(inputting) stateCallback(inputting)
stateCallback(buildCandidate(state: inputting, isTypingVertical: input.isTypingVertical)) stateCallback(buildCandidate(state: inputting, isTypingVertical: input.isTypingVertical))
} else { // If there is still unfinished bpmf reading, ignore the punctuation } else { // If there is still unfinished bpmf reading, ignore the punctuation
@ -394,7 +406,9 @@ extension KeyHandler {
// MARK: Punctuation // MARK: Punctuation
// If nothing is matched, see if it's a punctuation key for current layout. ///
/// - /
/// -
var punctuationNamePrefix = "" var punctuationNamePrefix = ""
@ -425,7 +439,8 @@ extension KeyHandler {
return true return true
} }
// if nothing is matched, see if it's a punctuation key. ///
let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))]
let punctuation: String = arrPunctuations.joined(separator: "") let punctuation: String = arrPunctuations.joined(separator: "")
@ -453,13 +468,12 @@ extension KeyHandler {
} }
} }
// MARK: - Still Nothing. // MARK: - (Still Nothing)
// Still nothing? Then we update the composing buffer. /// ctlInputMethod
// Note that some app has strange behavior if we don't do this, ///
// "thinking" that the key is not actually consumed. /// F1-F12
// F1-F12 /// 便
// 便
if (state is InputState.NotEmpty) || !composer.isEmpty { if (state is InputState.NotEmpty) || !composer.isEmpty {
IME.prtDebugIntel( IME.prtDebugIntel(
"Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)") "Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)")

View File

@ -24,35 +24,35 @@ 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. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
/// 調調
import Cocoa import Cocoa
// MARK: - § State managements. // MARK: - § 調 (Functions Interact With States).
extension KeyHandler { extension KeyHandler {
// MARK: - State Building // MARK: - State Building
///
var buildInputtingState: InputState.Inputting { var buildInputtingState: InputState.Inputting {
// "Updating the composing buffer" means to request the client /// (Update the composing buffer)
// to "refresh" the text input buffer with our "composing text" /// NSAttributeString
var tooltipParameterRef: [String] = ["", ""] var tooltipParameterRef: [String] = ["", ""]
var composingBuffer = "" var composingBuffer = ""
var composedStringCursorIndex = 0 var composedStringCursorIndex = 0
var readingCursorIndex = 0 var readingCursorIndex = 0
// We must do some Unicode codepoint counting to find the actual cursor location for the client /// IMK UTF8 emoji
// i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars /// Swift.utf16NSString.length()
// locations. Since we are using Swift, we use .utf16 as the equivalent of NSString.length(). ///
for walkedNode in walkedAnchors { for walkedNode in walkedAnchors {
if let theNode = walkedNode.node { if let theNode = walkedNode.node {
let strNodeValue = theNode.currentKeyValue.value let strNodeValue = theNode.currentKeyValue.value
composingBuffer += strNodeValue composingBuffer += strNodeValue
let arrSplit: [String] = Array(strNodeValue).map { String($0) } let arrSplit: [String] = Array(strNodeValue).map { String($0) }
let codepointCount = arrSplit.count let codepointCount = arrSplit.count
// This re-aligns the cursor index in the composed string ///
// (the actual cursor on the screen) with the compositor's logical /// NodeAnchorspanningLength
// cursor (reading) cursor; each built node has a "spanning length" ///
// (e.g. two reading blocks has a spanning length of 2), and we
// accumulate those lengths to calculate the displayed cursor
// index.
let spanningLength: Int = walkedNode.spanningLength let spanningLength: Int = walkedNode.spanningLength
if readingCursorIndex + spanningLength <= compositorCursorIndex { if readingCursorIndex + spanningLength <= compositorCursorIndex {
composedStringCursorIndex += strNodeValue.utf16.count composedStringCursorIndex += strNodeValue.utf16.count
@ -69,14 +69,12 @@ extension KeyHandler {
if readingCursorIndex < compositorCursorIndex { if readingCursorIndex < compositorCursorIndex {
composedStringCursorIndex += strNodeValue.utf16.count composedStringCursorIndex += strNodeValue.utf16.count
readingCursorIndex += spanningLength readingCursorIndex += spanningLength
if readingCursorIndex > compositorCursorIndex { readingCursorIndex = min(readingCursorIndex, compositorCursorIndex)
readingCursorIndex = compositorCursorIndex ///
} ///
// Now we start preparing the contents of the tooltips used ///
// in cases of moving cursors across certain emojis which emoji ///
// char count is inequal to the reading count. ///
// Example in McBopomofo: Typing (3 readings) gets a tree emoji.
// Example in vChewing: Typing (2 readings) gets a pasta emoji.
switch compositorCursorIndex { switch compositorCursorIndex {
case compositor.readings.count...: case compositor.readings.count...:
tooltipParameterRef[0] = compositor.readings[compositor.readings.count - 1] tooltipParameterRef[0] = compositor.readings[compositor.readings.count - 1]
@ -94,9 +92,8 @@ extension KeyHandler {
} }
} }
// Now, we gather all the intel, separate the composing buffer to two parts (head and tail), ///
// and insert the reading text (the Mandarin syllable) in between them. /// 便 composer
// The reading text is what the user is typing.
var arrHead = [String.UTF16View.Element]() var arrHead = [String.UTF16View.Element]()
var arrTail = [String.UTF16View.Element]() var arrTail = [String.UTF16View.Element]()
@ -108,34 +105,38 @@ extension KeyHandler {
} }
} }
/// stringview
///
let head = String(utf16CodeUnits: arrHead, count: arrHead.count) let head = String(utf16CodeUnits: arrHead, count: arrHead.count)
let reading = composer.getInlineCompositionForIMK(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer) let reading = composer.getInlineCompositionForIMK(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer)
let tail = String(utf16CodeUnits: arrTail, count: arrTail.count) let tail = String(utf16CodeUnits: arrTail, count: arrTail.count)
let composedText = head + reading + tail let composedText = head + reading + tail
let cursorIndex = composedStringCursorIndex + reading.utf16.count let cursorIndex = composedStringCursorIndex + reading.utf16.count
/// 使
let stateResult = InputState.Inputting(composingBuffer: composedText, cursorIndex: cursorIndex) let stateResult = InputState.Inputting(composingBuffer: composedText, cursorIndex: cursorIndex)
// Now we start weaving the contents of the tooltip. ///
if tooltipParameterRef[0].isEmpty, tooltipParameterRef[1].isEmpty { switch (tooltipParameterRef[0].isEmpty, tooltipParameterRef[1].isEmpty) {
stateResult.tooltip = "" case (true, true): stateResult.tooltip.removeAll()
} else if tooltipParameterRef[0].isEmpty { case (true, false):
stateResult.tooltip = String( stateResult.tooltip = String(
format: NSLocalizedString("Cursor is to the rear of \"%@\".", comment: ""), format: NSLocalizedString("Cursor is to the rear of \"%@\".", comment: ""),
tooltipParameterRef[1] tooltipParameterRef[1]
) )
} else if tooltipParameterRef[1].isEmpty { case (false, true):
stateResult.tooltip = String( stateResult.tooltip = String(
format: NSLocalizedString("Cursor is in front of \"%@\".", comment: ""), format: NSLocalizedString("Cursor is in front of \"%@\".", comment: ""),
tooltipParameterRef[0] tooltipParameterRef[0]
) )
} else { case (false, false):
stateResult.tooltip = String( stateResult.tooltip = String(
format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""), format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""),
tooltipParameterRef[0], tooltipParameterRef[1] tooltipParameterRef[0], tooltipParameterRef[1]
) )
} }
///
if !stateResult.tooltip.isEmpty { if !stateResult.tooltip.isEmpty {
ctlInputMethod.tooltipController.setColor(state: .denialOverflow) ctlInputMethod.tooltipController.setColor(state: .denialOverflow)
} }
@ -145,6 +146,11 @@ extension KeyHandler {
// MARK: - // MARK: -
///
/// - Parameters:
/// - currentState:
/// - isTypingVertical:
/// - Returns:
func buildCandidate( func buildCandidate(
state currentState: InputState.NotEmpty, state currentState: InputState.NotEmpty,
isTypingVertical: Bool = false isTypingVertical: Bool = false
@ -159,13 +165,19 @@ extension KeyHandler {
// MARK: - // MARK: -
// buildAssociatePhraseStateWithKey ///
// 使 ///
// Core buildAssociatePhraseArray /// buildAssociatePhraseStateWithKey
// String Swift /// 使
// nil /// Core buildAssociatePhraseArray
// /// String Swift
// /// nil
///
///
/// - Parameters:
/// - key:
/// - isTypingVertical:
/// - Returns:
func buildAssociatePhraseState( func buildAssociatePhraseState(
withKey key: String!, withKey key: String!,
isTypingVertical: Bool isTypingVertical: Bool
@ -178,6 +190,13 @@ extension KeyHandler {
// MARK: - // MARK: -
///
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleMarkingState( func handleMarkingState(
_ state: InputState.Marking, _ state: InputState.Marking,
input: InputSignal, input: InputSignal,
@ -246,8 +265,16 @@ extension KeyHandler {
return false return false
} }
// MARK: - // MARK: -
///
/// - Parameters:
/// - customPunctuation:
/// - state:
/// - isTypingVertical:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handlePunctuation( func handlePunctuation(
_ customPunctuation: String, _ customPunctuation: String,
state: InputState, state: InputState,
@ -261,9 +288,9 @@ extension KeyHandler {
if composer.isEmpty { if composer.isEmpty {
insertToCompositorAtCursor(reading: customPunctuation) insertToCompositorAtCursor(reading: customPunctuation)
let poppedText = popOverflowComposingTextAndWalk let textToCommit = popOverflowComposingTextAndWalk
let inputting = buildInputtingState let inputting = buildInputtingState
inputting.poppedText = poppedText inputting.textToCommit = textToCommit
stateCallback(inputting) stateCallback(inputting)
if mgrPrefs.useSCPCTypingMode, composer.isEmpty { if mgrPrefs.useSCPCTypingMode, composer.isEmpty {
@ -273,8 +300,8 @@ extension KeyHandler {
) )
if candidateState.candidates.count == 1 { if candidateState.candidates.count == 1 {
clear() clear()
if let strPoppedText: String = candidateState.candidates.first { if let strtextToCommit: String = candidateState.candidates.first {
stateCallback(InputState.Committing(poppedText: strPoppedText) as InputState.Committing) stateCallback(InputState.Committing(textToCommit: strtextToCommit) as InputState.Committing)
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
} else { } else {
stateCallback(candidateState) stateCallback(candidateState)
@ -293,8 +320,13 @@ extension KeyHandler {
} }
} }
// MARK: - Enter // MARK: - Enter
/// Enter
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleEnter( func handleEnter(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -303,13 +335,18 @@ extension KeyHandler {
guard let currentState = state as? InputState.Inputting else { return false } guard let currentState = state as? InputState.Inputting else { return false }
clear() clear()
stateCallback(InputState.Committing(poppedText: currentState.composingBuffer)) stateCallback(InputState.Committing(textToCommit: currentState.composingBuffer))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
return true return true
} }
// MARK: - CMD+Enter // MARK: - CMD+Enter
/// CMD+Enter
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleCtrlCommandEnter( func handleCtrlCommandEnter(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -329,13 +366,18 @@ extension KeyHandler {
clear() clear()
stateCallback(InputState.Committing(poppedText: composingBuffer)) stateCallback(InputState.Committing(textToCommit: composingBuffer))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
return true return true
} }
// MARK: - CMD+Alt+Enter Ruby // MARK: - CMD+Alt+Enter Ruby
/// CMD+Alt+Enter Ruby
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleCtrlOptionCommandEnter( func handleCtrlOptionCommandEnter(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -368,13 +410,19 @@ extension KeyHandler {
clear() clear()
stateCallback(InputState.Committing(poppedText: composed)) stateCallback(InputState.Committing(textToCommit: composed))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
return true return true
} }
// MARK: - Backspace (macOS Delete) // MARK: - Backspace (macOS Delete)
/// Backspace (macOS Delete)
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackspace( func handleBackspace(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -386,7 +434,7 @@ extension KeyHandler {
composer.clear() composer.clear()
} else if composer.isEmpty { } else if composer.isEmpty {
if compositorCursorIndex >= 0 { if compositorCursorIndex >= 0 {
deleteBuilderReadingInFrontOfCursor() deleteCompositorReadingAtTheRearOfCursor()
walk() walk()
} else { } else {
IME.prtDebugIntel("9D69908D") IME.prtDebugIntel("9D69908D")
@ -408,6 +456,12 @@ extension KeyHandler {
// MARK: - PC Delete (macOS Fn+BackSpace) // MARK: - PC Delete (macOS Fn+BackSpace)
/// PC Delete (macOS Fn+BackSpace)
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleDelete( func handleDelete(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -417,7 +471,7 @@ extension KeyHandler {
if composer.isEmpty { if composer.isEmpty {
if compositorCursorIndex != compositorLength { if compositorCursorIndex != compositorLength {
deleteBuilderReadingToTheFrontOfCursor() deleteCompositorReadingToTheFrontOfCursor()
walk() walk()
let inputting = buildInputtingState let inputting = buildInputtingState
// count > 0!isEmpty滿 // count > 0!isEmpty滿
@ -442,6 +496,12 @@ extension KeyHandler {
// MARK: - 90 // MARK: - 90
/// 90
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleAbsorbedArrowKey( func handleAbsorbedArrowKey(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -456,8 +516,14 @@ extension KeyHandler {
return true return true
} }
// MARK: - Home // MARK: - Home
/// Home
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleHome( func handleHome(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -484,8 +550,14 @@ extension KeyHandler {
return true return true
} }
// MARK: - End // MARK: - End
/// End
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleEnd( func handleEnd(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -512,8 +584,13 @@ extension KeyHandler {
return true return true
} }
// MARK: - Esc // MARK: - Esc
/// Esc
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleEsc( func handleEsc(
state: InputState, state: InputState,
stateCallback: @escaping (InputState) -> Void, stateCallback: @escaping (InputState) -> Void,
@ -521,17 +598,13 @@ extension KeyHandler {
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state is InputState.Inputting else { return false }
let escToClearInputBufferEnabled: Bool = mgrPrefs.escToCleanInputBuffer if mgrPrefs.escToCleanInputBuffer {
///
if escToClearInputBufferEnabled { /// macOS Windows 使
// If the option is enabled, we clear everything in the buffer.
// This includes walked nodes and the reading. Note that this convention
// is by default in macOS 10.0-10.5 built-in Panasonic Hanin and later macOS Zhuyin.
// Some Windows users hate this design, hence the option here to disable it.
clear() clear()
stateCallback(InputState.EmptyIgnoringPreviousState()) stateCallback(InputState.EmptyIgnoringPreviousState())
} else { } else {
// If reading is not empty, we cancel the reading. ///
if !composer.isEmpty { if !composer.isEmpty {
composer.clear() composer.clear()
if compositorLength == 0 { if compositorLength == 0 {
@ -546,6 +619,13 @@ extension KeyHandler {
// MARK: - // MARK: -
///
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleForward( func handleForward(
state: InputState, state: InputState,
input: InputSignal, input: InputSignal,
@ -595,6 +675,13 @@ extension KeyHandler {
// MARK: - // MARK: -
///
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackward( func handleBackward(
state: InputState, state: InputState,
input: InputSignal, input: InputSignal,
@ -644,6 +731,13 @@ extension KeyHandler {
// MARK: - Tab Shift+Space // MARK: - Tab Shift+Space
///
/// - Parameters:
/// - state:
/// - reverseModifier:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleInlineCandidateRotation( func handleInlineCandidateRotation(
state: InputState, state: InputState,
reverseModifier: Bool, reverseModifier: Bool,
@ -697,18 +791,17 @@ extension KeyHandler {
var currentIndex = 0 var currentIndex = 0
if currentNode.score < currentNode.kSelectedCandidateScore { 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 /// 2 使
// 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 /// (Shift+)Tab ()
// than a phrase with two characters, the user can just /// Shift(+CMD)+Space Tab
// use the longer phrase by tapping the tab key.
if candidates[0] == currentValue { if candidates[0] == currentValue {
// If the first candidate is the value of the ///
// current node, we use next one. ///
if reverseModifier { if reverseModifier {
currentIndex = candidates.count - 1 currentIndex = candidates.count - 1
} else { } else {

View File

@ -178,7 +178,7 @@ class ctlInputMethod: IMKInputController {
override func commitComposition(_ sender: Any!) { override func commitComposition(_ sender: Any!) {
_ = sender // Stop clang-format from ruining the parameters of this function. _ = sender // Stop clang-format from ruining the parameters of this function.
if let state = state as? InputState.NotEmpty { if let state = state as? InputState.NotEmpty {
handle(state: InputState.Committing(poppedText: state.composingBuffer)) handle(state: InputState.Committing(textToCommit: state.composingBuffer))
} }
resetKeyHandler() resetKeyHandler()
} }
@ -306,9 +306,9 @@ extension ctlInputMethod {
ctlCandidateCurrent.visible = false ctlCandidateCurrent.visible = false
hideTooltip() hideTooltip()
let poppedText = state.poppedText let textToCommit = state.textToCommit
if !poppedText.isEmpty { if !textToCommit.isEmpty {
commit(text: poppedText) commit(text: textToCommit)
} }
client().setMarkedText( client().setMarkedText(
"", selectionRange: NSRange(location: 0, length: 0), "", selectionRange: NSRange(location: 0, length: 0),
@ -321,9 +321,9 @@ extension ctlInputMethod {
ctlCandidateCurrent.visible = false ctlCandidateCurrent.visible = false
hideTooltip() hideTooltip()
let poppedText = state.poppedText let textToCommit = state.textToCommit
if !poppedText.isEmpty { if !textToCommit.isEmpty {
commit(text: poppedText) commit(text: textToCommit)
} }
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
@ -622,7 +622,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
state: .SymbolTable(node: node, isTypingVertical: state.isTypingVertical) state: .SymbolTable(node: node, isTypingVertical: state.isTypingVertical)
) )
} else { } else {
handle(state: .Committing(poppedText: node.title)) handle(state: .Committing(textToCommit: node.title))
handle(state: .Empty()) handle(state: .Empty())
} }
return return
@ -637,7 +637,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if mgrPrefs.useSCPCTypingMode { if mgrPrefs.useSCPCTypingMode {
keyHandler.clear() keyHandler.clear()
let composingBuffer = inputting.composingBuffer let composingBuffer = inputting.composingBuffer
handle(state: .Committing(poppedText: composingBuffer)) handle(state: .Committing(textToCommit: composingBuffer))
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState( let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: composingBuffer, isTypingVertical: state.isTypingVertical withKey: composingBuffer, isTypingVertical: state.isTypingVertical
@ -655,7 +655,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if let state = state as? InputState.AssociatedPhrases { if let state = state as? InputState.AssociatedPhrases {
let selectedValue = state.candidates[index] let selectedValue = state.candidates[index]
handle(state: .Committing(poppedText: selectedValue)) handle(state: .Committing(textToCommit: selectedValue))
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState( let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, isTypingVertical: state.isTypingVertical withKey: selectedValue, isTypingVertical: state.isTypingVertical